mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-04-28 13:00:13 +00:00
Merge branch 'develop' into feat/hyperopt_progressbar
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
@@ -85,6 +86,22 @@ def make_rl_config(conf):
|
||||
return conf
|
||||
|
||||
|
||||
def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
|
||||
return {
|
||||
"learning_rate": 3e-4,
|
||||
"trainer_kwargs": {
|
||||
"max_iters": 1,
|
||||
"batch_size": 64,
|
||||
"max_n_eval_batches": 1,
|
||||
},
|
||||
"model_kwargs": {
|
||||
"hidden_dim": 32,
|
||||
"dropout_percent": 0.2,
|
||||
"n_layer": 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_patched_data_kitchen(mocker, freqaiconf):
|
||||
dk = FreqaiDataKitchen(freqaiconf)
|
||||
return dk
|
||||
|
||||
@@ -15,7 +15,8 @@ from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
|
||||
from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config
|
||||
from tests.freqai.conftest import (get_patched_freqai_strategy, make_rl_config,
|
||||
mock_pytorch_mlp_model_training_parameters)
|
||||
|
||||
|
||||
def is_py11() -> bool:
|
||||
@@ -34,13 +35,14 @@ def is_mac() -> bool:
|
||||
|
||||
def can_run_model(model: str) -> None:
|
||||
if (is_arm() or is_py11()) and "Catboost" in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
pytest.skip("CatBoost is not supported on ARM.")
|
||||
|
||||
if is_mac() and not is_arm() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
|
||||
if is_pytorch_model and is_mac() and not is_arm():
|
||||
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
|
||||
|
||||
if is_py11() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.11.")
|
||||
if is_pytorch_model and is_py11():
|
||||
pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
|
||||
@@ -48,11 +50,12 @@ def can_run_model(model: str) -> None:
|
||||
('XGBoostRegressor', False, True, False, True, False, 10),
|
||||
('XGBoostRFRegressor', False, False, False, True, False, 0),
|
||||
('CatboostRegressor', False, False, False, True, True, 0),
|
||||
('PyTorchMLPRegressor', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner', False, True, False, True, False, 0),
|
||||
('ReinforcementLearner_multiproc', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner_test_3ac', False, False, False, False, False, 0),
|
||||
('ReinforcementLearner_test_3ac', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0)
|
||||
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0),
|
||||
])
|
||||
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
dbscan, float32, can_short, shuffle, buffer):
|
||||
@@ -79,6 +82,11 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
|
||||
|
||||
if 'PyTorchMLPRegressor' in model:
|
||||
model_save_ext = 'zip'
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
@@ -123,8 +131,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat")
|
||||
])
|
||||
def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat):
|
||||
if (is_arm() or is_py11()) and 'Catboost' in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
can_run_model(model)
|
||||
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
freqai_conf.update({"strategy": strat})
|
||||
@@ -164,10 +171,10 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
||||
'CatboostClassifier',
|
||||
'XGBoostClassifier',
|
||||
'XGBoostRFClassifier',
|
||||
'PyTorchMLPClassifier',
|
||||
])
|
||||
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
if (is_arm() or is_py11()) and model == 'CatboostClassifier':
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
can_run_model(model)
|
||||
|
||||
freqai_conf.update({"freqaimodel": model})
|
||||
freqai_conf.update({"strategy": "freqai_test_classifier"})
|
||||
@@ -193,7 +200,20 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
|
||||
strategy, freqai.dk, data_load_timerange)
|
||||
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists()
|
||||
if 'PyTorchMLPClassifier':
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
if freqai.dd.model_type == 'joblib':
|
||||
model_file_extension = ".joblib"
|
||||
elif freqai.dd.model_type == "pytorch":
|
||||
model_file_extension = ".zip"
|
||||
else:
|
||||
raise Exception(f"Unsupported model type: {freqai.dd.model_type},"
|
||||
f" can't assign model_file_extension")
|
||||
|
||||
assert Path(freqai.dk.data_path /
|
||||
f"{freqai.dk.model_filename}_model{model_file_extension}").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists()
|
||||
@@ -207,10 +227,12 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
("LightGBMRegressor", 2, "freqai_test_strat"),
|
||||
("XGBoostRegressor", 2, "freqai_test_strat"),
|
||||
("CatboostRegressor", 2, "freqai_test_strat"),
|
||||
("PyTorchMLPRegressor", 2, "freqai_test_strat"),
|
||||
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
|
||||
("XGBoostClassifier", 2, "freqai_test_classifier"),
|
||||
("LightGBMClassifier", 2, "freqai_test_classifier"),
|
||||
("CatboostClassifier", 2, "freqai_test_classifier")
|
||||
("CatboostClassifier", 2, "freqai_test_classifier"),
|
||||
("PyTorchMLPClassifier", 2, "freqai_test_classifier")
|
||||
],
|
||||
)
|
||||
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
|
||||
@@ -231,6 +253,10 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
|
||||
if 'test_4ac' in model:
|
||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||
|
||||
if 'PyTorchMLP' in model:
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
|
||||
{"indicator_periods_candles": [2]})
|
||||
|
||||
|
||||
69
tests/persistence/test_key_value_store.py
Normal file
69
tests/persistence/test_key_value_store.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
|
||||
from tests.conftest import create_mock_trades_usdt
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_key_value_store(time_machine):
|
||||
start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
|
||||
time_machine.move_to(start, tick=False)
|
||||
|
||||
KeyValueStore.store_value("test", "testStringValue")
|
||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
||||
KeyValueStore.store_value("test_float", 22.51)
|
||||
KeyValueStore.store_value("test_int", 15)
|
||||
|
||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_string_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_string_value("test_dt") is None
|
||||
assert KeyValueStore.get_float_value("test_dt") is None
|
||||
assert KeyValueStore.get_int_value("test_dt") is None
|
||||
assert KeyValueStore.get_value("test_float") == 22.51
|
||||
assert KeyValueStore.get_float_value("test_float") == 22.51
|
||||
assert KeyValueStore.get_value("test_int") == 15
|
||||
assert KeyValueStore.get_int_value("test_int") == 15
|
||||
assert KeyValueStore.get_datetime_value("test_int") is None
|
||||
|
||||
time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
|
||||
assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_value("test_dt") == start
|
||||
# Test update works
|
||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
||||
|
||||
KeyValueStore.store_value("test_float", 23.51)
|
||||
assert KeyValueStore.get_value("test_float") == 23.51
|
||||
# test deleting
|
||||
KeyValueStore.delete_value("test_float")
|
||||
assert KeyValueStore.get_value("test_float") is None
|
||||
# Delete same value again (should not fail)
|
||||
KeyValueStore.delete_value("test_float")
|
||||
|
||||
with pytest.raises(ValueError, match=r"Unknown value type"):
|
||||
KeyValueStore.store_value("test_float", {'some': 'dict'})
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_set_startup_time(fee, time_machine):
|
||||
create_mock_trades_usdt(fee)
|
||||
start = datetime.now(timezone.utc)
|
||||
time_machine.move_to(start, tick=False)
|
||||
set_startup_time()
|
||||
|
||||
assert KeyValueStore.get_value("startup_time") == start
|
||||
initial_time = KeyValueStore.get_value("bot_start_time")
|
||||
assert initial_time <= start
|
||||
|
||||
# Simulate bot restart
|
||||
new_start = start + timedelta(days=5)
|
||||
time_machine.move_to(new_start, tick=False)
|
||||
set_startup_time()
|
||||
|
||||
assert KeyValueStore.get_value("startup_time") == new_start
|
||||
assert KeyValueStore.get_value("bot_start_time") == initial_time
|
||||
@@ -6,7 +6,7 @@ import arrow
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
@@ -2037,6 +2037,7 @@ def test_Trade_object_idem():
|
||||
'get_mix_tag_performance',
|
||||
'get_trading_volume',
|
||||
'from_json',
|
||||
'validate_string_len',
|
||||
)
|
||||
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
|
||||
'total_profit')
|
||||
@@ -2055,6 +2056,31 @@ def test_Trade_object_idem():
|
||||
assert item in trade
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_trade_truncates_string_fields():
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=20.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
open_date=datetime.utcnow() - timedelta(minutes=20),
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
exchange='binance',
|
||||
leverage=1.0,
|
||||
trading_mode='futures',
|
||||
enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2,
|
||||
exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2,
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
trade1 = Trade.session.scalars(select(Trade)).first()
|
||||
|
||||
assert trade1.enter_tag == 'a' * CUSTOM_TAG_MAX_LENGTH
|
||||
assert trade1.exit_reason == 'b' * CUSTOM_TAG_MAX_LENGTH
|
||||
|
||||
|
||||
def test_recalc_trade_from_orders(fee):
|
||||
|
||||
o1_amount = 100
|
||||
|
||||
@@ -883,6 +883,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
|
||||
'max_drawdown': ANY,
|
||||
'max_drawdown_abs': ANY,
|
||||
'trading_volume': expected['trading_volume'],
|
||||
'bot_start_timestamp': 0,
|
||||
'bot_start_date': '',
|
||||
}
|
||||
|
||||
|
||||
@@ -1403,10 +1405,10 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
||||
])
|
||||
|
||||
|
||||
def test_api_pair_history(botclient, ohlcv_history):
|
||||
def test_api_pair_history(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
timeframe = '5m'
|
||||
|
||||
lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
|
||||
# No pair
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
||||
@@ -1440,6 +1442,7 @@ def test_api_pair_history(botclient, ohlcv_history):
|
||||
assert len(rc.json()['data']) == rc.json()['length']
|
||||
assert 'columns' in rc.json()
|
||||
assert 'data' in rc.json()
|
||||
assert lfm.call_count == 1
|
||||
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
||||
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
|
||||
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||
|
||||
@@ -2241,8 +2241,9 @@ def test_send_msg_buy_notification_no_fiat(
|
||||
('Short', 'short_signal_01', 2.0),
|
||||
])
|
||||
def test_send_msg_sell_notification_no_fiat(
|
||||
default_conf, mocker, direction, enter_signal, leverage) -> None:
|
||||
default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
|
||||
del default_conf['fiat_display_currency']
|
||||
time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
|
||||
@@ -82,7 +82,7 @@ class freqai_test_classifier(IStrategy):
|
||||
return dataframe
|
||||
|
||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
|
||||
|
||||
self.freqai.class_names = ["down", "up"]
|
||||
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
|
||||
dataframe["close"], 'up', 'down')
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
|
||||
@@ -529,13 +530,13 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
|
||||
assert res[0].exit_reason == 'hello world'
|
||||
|
||||
caplog.clear()
|
||||
strategy.custom_exit = MagicMock(return_value='h' * 100)
|
||||
strategy.custom_exit = MagicMock(return_value='h' * CUSTOM_TAG_MAX_LENGTH * 2)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
assert res[0].exit_type == ExitType.CUSTOM_EXIT
|
||||
assert res[0].exit_flag is True
|
||||
assert res[0].exit_reason == 'h' * 64
|
||||
assert res[0].exit_reason == 'h' * (CUSTOM_TAG_MAX_LENGTH)
|
||||
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ from freqtrade.exceptions import OperationalException
|
||||
|
||||
def test_parse_timerange_incorrect():
|
||||
|
||||
timerange = TimeRange.parse_timerange('')
|
||||
assert timerange == TimeRange(None, None, 0, 0)
|
||||
timerange = TimeRange.parse_timerange('20100522-')
|
||||
assert TimeRange('date', None, 1274486400, 0) == timerange
|
||||
assert timerange.timerange_str == '20100522-'
|
||||
|
||||
Reference in New Issue
Block a user