Merge branch 'develop' into backtest_live_models

This commit is contained in:
Wagner Costa Santos
2022-10-20 11:59:37 -03:00
43 changed files with 650 additions and 236 deletions

View File

@@ -18,6 +18,7 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star
from freqtrade.commands.db_commands import start_convert_db
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
get_ui_download_url, read_ui_version)
from freqtrade.commands.list_commands import start_list_freqAI_models
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
@@ -944,6 +945,34 @@ def test_start_list_strategies(capsys):
assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out
def test_start_list_freqAI_models(capsys):
args = [
"list-freqaimodels",
"-1"
]
pargs = get_args(args)
pargs['config'] = None
start_list_freqAI_models(pargs)
captured = capsys.readouterr()
assert "LightGBMClassifier" in captured.out
assert "LightGBMRegressor" in captured.out
assert "XGBoostRegressor" in captured.out
assert "<builtin>/LightGBMRegressor.py" not in captured.out
args = [
"list-freqaimodels",
]
pargs = get_args(args)
pargs['config'] = None
start_list_freqAI_models(pargs)
captured = capsys.readouterr()
assert "LightGBMClassifier" in captured.out
assert "LightGBMRegressor" in captured.out
assert "XGBoostRegressor" in captured.out
assert "<builtin>/LightGBMRegressor.py" in captured.out
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
patch_exchange(mocker, mock_markets=True)
mocker.patch.multiple('freqtrade.exchange.Exchange',

View File

@@ -2196,6 +2196,9 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
time_machine.move_to(start + timedelta(hours=99, minutes=30))
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100)
assert exchange._startup_candle_count == 0
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair1 = ('IOTA/ETH', '1h', candle_type)
pair2 = ('XRP/ETH', '1h', candle_type)
@@ -2236,30 +2239,36 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
assert len(res) == 2
assert len(res[pair1]) == 99
assert len(res[pair2]) == 99
assert res[pair2].at[0, 'open']
assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000
refresh_pior = exchange._pairs_last_refresh_time[pair1]
# New candle on exchange - only return 50 candles (but one candle further)
new_startdate = (start + timedelta(hours=51)).strftime('%Y-%m-%d %H:%M')
ohlcv = generate_test_data_raw('1h', 50, new_startdate)
# New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles
# in one go
new_startdate = (start + timedelta(hours=2)).strftime('%Y-%m-%d %H:%M')
# mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100)
ohlcv = generate_test_data_raw('1h', 100, new_startdate)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
res = exchange.refresh_latest_ohlcv(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert len(res) == 2
assert len(res[pair1]) == 100
assert len(res[pair2]) == 100
# Verify index starts at 0
assert res[pair2].at[0, 'open']
assert refresh_pior != exchange._pairs_last_refresh_time[pair1]
assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000
assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-1][0] // 1000
exchange._api_async.fetch_ohlcv.reset_mock()
# Retry same call - no action.
# Retry same call - from cache
res = exchange.refresh_latest_ohlcv(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 0
assert len(res) == 2
assert len(res[pair1]) == 100
assert len(res[pair2]) == 100
assert res[pair2].at[0, 'open']
# Move to distant future (so a 1 call would cause a hole in the data)
time_machine.move_to(start + timedelta(hours=2000))
@@ -2272,6 +2281,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
# Cache eviction - new data.
assert len(res[pair1]) == 99
assert len(res[pair2]) == 99
assert res[pair2].at[0, 'open']
@pytest.mark.asyncio
@@ -4341,9 +4351,10 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
('XLTCUSDT', 1, 'spot'),
('LTC/USD', 1, 'futures'),
('XLTCUSDT', 0.01, 'futures'),
('ETH/USDT:USDT', 10, 'futures')
('ETH/USDT:USDT', 10, 'futures'),
('TORN/USDT:USDT', None, 'futures'), # Don't fail for unavailable pairs.
])
def est__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
api_mock = MagicMock()
default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = 'isolated'

View File

@@ -30,6 +30,7 @@ def is_mac() -> bool:
@pytest.mark.parametrize('model', [
'LightGBMRegressor',
'XGBoostRegressor',
'XGBoostRFRegressor',
'CatboostRegressor',
])
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model):
@@ -55,10 +56,17 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model):
data_load_timerange = TimeRange.parse_timerange("20180125-20180130")
new_timerange = TimeRange.parse_timerange("20180127-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.train_timer("start", "ADA/BTC")
freqai.extract_data_and_train_model(
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
freqai.train_timer("stop", "ADA/BTC")
freqai.dd.save_metric_tracker_to_disk()
freqai.dd.save_drawer_to_disk()
assert Path(freqai.dk.full_path / "metric_tracker.json").is_file()
assert Path(freqai.dk.full_path / "pair_dictionary.json").is_file()
assert Path(freqai.dk.data_path /
f"{freqai.dk.model_filename}_model.{model_save_ext}").is_file()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").is_file()
@@ -93,6 +101,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model):
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.extract_data_and_train_model(
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
@@ -111,6 +120,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model):
'LightGBMClassifier',
'CatboostClassifier',
'XGBoostClassifier',
'XGBoostRFClassifier',
])
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
if is_arm() and model == 'CatboostClassifier':
@@ -134,6 +144,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
strategy, freqai.dk, data_load_timerange)

View File

@@ -97,7 +97,6 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
'start_date': min_date,
'end_date': max_date,
'max_open_trades': 10,
'position_stacking': False,
}
@@ -735,7 +734,6 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
results = result['results']
assert not results.empty
@@ -799,6 +797,34 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) -> None:
# This strategy intentionally places unfillable orders.
default_conf['strategy'] = 'StrategyTestV3CustomEntryPrice'
default_conf['startup_candle_count'] = 0
# Cancel unfilled order after 4 minutes on 5m timeframe.
default_conf["unfilledtimeout"] = {"entry": 4}
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
# Testing dataframe contains 11 candles. Expecting 10 timed out orders.
timerange = TimeRange('date', 'date', 1517227800, 1517231100)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
min_date, max_date = get_timerange(data)
result = backtesting.backtest(
processed=deepcopy(data),
start_date=min_date,
end_date=max_date,
max_open_trades=1,
)
assert result['timedout_entry_orders'] == 10
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
@@ -819,7 +845,6 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
)
assert not results['results'].empty
assert len(results['results']) == 1
@@ -851,7 +876,6 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
@@ -906,7 +930,6 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
assert count == 5
@@ -950,8 +973,6 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
enable_protections=default_conf.get('enable_protections', False),
)
assert len(results['results']) == numres
@@ -994,8 +1015,6 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
enable_protections=default_conf.get('enable_protections', False),
)
assert len(results['results']) == expected
@@ -1107,7 +1126,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
'start_date': min_date,
'end_date': max_date,
'max_open_trades': 3,
'position_stacking': False,
}
results = backtesting.backtest(**backtest_conf)
@@ -1130,7 +1148,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
'start_date': min_date,
'end_date': max_date,
'max_open_trades': 1,
'position_stacking': False,
}
results = backtesting.backtest(**backtest_conf)
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0

View File

@@ -42,7 +42,6 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
results = result['results']
assert not results.empty

View File

@@ -336,7 +336,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
assert hasattr(hyperopt.backtesting, "_position_stacking")
def test_hyperopt_format_results(hyperopt):
@@ -704,7 +704,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
assert hasattr(hyperopt.backtesting, "_position_stacking")
def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
@@ -778,7 +778,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
assert hasattr(hyperopt.backtesting, "_position_stacking")
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
@@ -821,7 +821,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
assert hasattr(hyperopt.backtesting, "_position_stacking")
@pytest.mark.parametrize("space", [

View File

@@ -1443,8 +1443,9 @@ def test_api_plot_config(botclient):
assert isinstance(rc.json()['subplots'], dict)
def test_api_strategies(botclient):
def test_api_strategies(botclient, tmpdir):
ftbot, client = botclient
ftbot.config['user_data_dir'] = Path(tmpdir)
rc = client_get(client, f"{BASE_URI}/strategies")
@@ -1456,6 +1457,7 @@ def test_api_strategies(botclient):
'InformativeDecoratorTest',
'StrategyTestV2',
'StrategyTestV3',
'StrategyTestV3CustomEntryPrice',
'StrategyTestV3Futures',
'freqai_test_classifier',
'freqai_test_multimodel_strat',

View File

@@ -0,0 +1,37 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
from typing import Optional
from pandas import DataFrame
from strategy_test_v3 import StrategyTestV3
class StrategyTestV3CustomEntryPrice(StrategyTestV3):
"""
Strategy used by tests freqtrade bot.
Please do not modify this strategy, it's intended for internal use only.
Please look at the SampleStrategy in the user_data/strategy directory
or strategy repository https://github.com/freqtrade/freqtrade-strategies
for samples and inspiration.
"""
new_entry_price: float = 0.001
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
dataframe['volume'] > 0,
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
return self.new_entry_price

View File

@@ -32,24 +32,25 @@ def test_search_strategy():
def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
strategies = StrategyResolver._search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list)
assert len(strategies) == 9
assert len(strategies) == 10
assert isinstance(strategies[0], dict)
def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
strategies = StrategyResolver._search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list)
assert len(strategies) == 10
assert len(strategies) == 11
# with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load
assert len([x for x in strategies if x['class'] is not None]) == 9
assert len([x for x in strategies if x['class'] is not None]) == 10
assert len([x for x in strategies if x['class'] is None]) == 1
directory = Path(__file__).parent / "strats_nonexistingdir"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
strategies = StrategyResolver._search_all_objects(directory, enum_failed=True)
assert len(strategies) == 0
@@ -77,10 +78,9 @@ def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
def test_load_strategy_invalid_directory(caplog, default_conf):
default_conf['strategy'] = 'StrategyTestV3'
extra_dir = Path.cwd() / 'some/path'
with pytest.raises(OperationalException):
StrategyResolver._load_strategy(CURRENT_TEST_STRATEGY, config=default_conf,
with pytest.raises(OperationalException, match=r"Impossible to load Strategy.*"):
StrategyResolver._load_strategy('StrategyTestV333', config=default_conf,
extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
@@ -102,8 +102,8 @@ def test_load_strategy_noname(default_conf):
StrategyResolver.load_strategy(default_conf)
@pytest.mark.filterwarnings("ignore:deprecated")
@pytest.mark.parametrize('strategy_name', ['StrategyTestV2'])
@ pytest.mark.filterwarnings("ignore:deprecated")
@ pytest.mark.parametrize('strategy_name', ['StrategyTestV2'])
def test_strategy_pre_v3(dataframe_1m, default_conf, strategy_name):
default_conf.update({'strategy': strategy_name})
@@ -349,7 +349,7 @@ def test_strategy_override_use_exit_profit_only(caplog, default_conf):
assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog)
@pytest.mark.filterwarnings("ignore:deprecated")
@ pytest.mark.filterwarnings("ignore:deprecated")
def test_missing_implements(default_conf, caplog):
default_location = Path(__file__).parent / "strats"

View File

@@ -2406,6 +2406,8 @@ def test_Trade_object_idem():
'get_trading_volume',
)
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
'total_profit')
# Parent (LocalTrade) should have the same attributes
for item in trade:
@@ -2416,7 +2418,7 @@ def test_Trade_object_idem():
# Fails if only a column is added without corresponding parent field
for item in localtrade:
if (not item.startswith('__')
and item not in ('trades', 'trades_open', 'total_profit')
and item not in EXCLUDES2
and type(getattr(LocalTrade, item)) not in (property, FunctionType)):
assert item in trade