From 4f5bf632fc5891faab3c4b616e11f07960ec7357 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 May 2024 16:15:27 +0200 Subject: [PATCH] ruff format: remaining tests --- tests/optimize/test_backtesting.py | 2242 ++++++++++++++----------- tests/persistence/test_persistence.py | 2138 +++++++++++------------ 2 files changed, 2317 insertions(+), 2063 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 1f0cb50ff..ce9f3e51a 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -38,18 +38,9 @@ from tests.conftest import ( ORDER_TYPES = [ - { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': False - }, - { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': True - }] + {"entry": "limit", "exit": "limit", "stoploss": "limit", "stoploss_on_exchange": False}, + {"entry": "limit", "exit": "limit", "stoploss": "limit", "stoploss_on_exchange": True}, +] def trim_dictlist(dict_list, num): @@ -60,39 +51,46 @@ def trim_dictlist(dict_list, num): def load_data_test(what, testdatadir): - timerange = TimeRange.parse_timerange('1510694220-1510700340') - data = history.load_pair_history(pair='UNITTEST/BTC', datadir=testdatadir, - timeframe='1m', timerange=timerange, - drop_incomplete=False, - fill_up_missing=False) + timerange = TimeRange.parse_timerange("1510694220-1510700340") + data = history.load_pair_history( + pair="UNITTEST/BTC", + datadir=testdatadir, + timeframe="1m", + timerange=timerange, + drop_incomplete=False, + fill_up_missing=False, + ) base = 0.001 - if what == 'raise': - data.loc[:, 'open'] = data.index * base - data.loc[:, 'high'] = data.index * base + 0.0001 - data.loc[:, 'low'] = data.index * base - 0.0001 - data.loc[:, 'close'] = data.index * base + if what == "raise": + data.loc[:, "open"] = data.index * base + data.loc[:, "high"] = data.index * base + 0.0001 + data.loc[:, "low"] = data.index * base - 0.0001 + data.loc[:, "close"] = data.index * base - if what == 'lower': - data.loc[:, 'open'] = 1 - data.index * base - data.loc[:, 'high'] = 1 - data.index * base + 0.0001 - data.loc[:, 'low'] = 1 - data.index * base - 0.0001 - data.loc[:, 'close'] = 1 - data.index * base + if what == "lower": + data.loc[:, "open"] = 1 - data.index * base + data.loc[:, "high"] = 1 - data.index * base + 0.0001 + data.loc[:, "low"] = 1 - data.index * base - 0.0001 + data.loc[:, "close"] = 1 - data.index * base - if what == 'sine': + if what == "sine": hz = 0.1 # frequency - data.loc[:, 'open'] = np.sin(data.index * hz) / 1000 + base - data.loc[:, 'high'] = np.sin(data.index * hz) / 1000 + base + 0.0001 - data.loc[:, 'low'] = np.sin(data.index * hz) / 1000 + base - 0.0001 - data.loc[:, 'close'] = np.sin(data.index * hz) / 1000 + base + data.loc[:, "open"] = np.sin(data.index * hz) / 1000 + base + data.loc[:, "high"] = np.sin(data.index * hz) / 1000 + base + 0.0001 + data.loc[:, "low"] = np.sin(data.index * hz) / 1000 + base - 0.0001 + data.loc[:, "close"] = np.sin(data.index * hz) / 1000 + base - return {'UNITTEST/BTC': clean_ohlcv_dataframe(data, timeframe='1m', pair='UNITTEST/BTC', - fill_missing=True, drop_incomplete=True)} + return { + "UNITTEST/BTC": clean_ohlcv_dataframe( + data, timeframe="1m", pair="UNITTEST/BTC", fill_missing=True, drop_incomplete=True + ) + } # FIX: fixturize this? -def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'): - data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair]) +def _make_backtest_conf(mocker, datadir, conf=None, pair="UNITTEST/BTC"): + data = history.load_data(datadir=datadir, timeframe="1m", pairs=[pair]) data = trim_dictlist(data, -201) patch_exchange(mocker) backtesting = Backtesting(conf) @@ -100,30 +98,30 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'): processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) return { - 'processed': processed, - 'start_date': min_date, - 'end_date': max_date, + "processed": processed, + "start_date": min_date, + "end_date": max_date, } def _trend(signals, buy_value, sell_value): - n = len(signals['low']) + n = len(signals["low"]) buy = np.zeros(n) sell = np.zeros(n) - for i in range(0, len(signals['date'])): + for i in range(0, len(signals["date"])): if random.random() > 0.5: # Both buy and sell signals at same timeframe buy[i] = buy_value sell[i] = sell_value - signals['enter_long'] = buy - signals['exit_long'] = sell - signals['enter_short'] = 0 - signals['exit_short'] = 0 + signals["enter_long"] = buy + signals["exit_long"] = sell + signals["enter_short"] = 0 + signals["exit_short"] = 0 return signals def _trend_alternate(dataframe=None, metadata=None): signals = dataframe - low = signals['low'] + low = signals["low"] n = len(low) buy = np.zeros(n) sell = np.zeros(n) @@ -132,10 +130,10 @@ def _trend_alternate(dataframe=None, metadata=None): buy[i] = 1 else: sell[i] = 1 - signals['enter_long'] = buy - signals['exit_long'] = sell - signals['enter_short'] = 0 - signals['exit_short'] = 0 + signals["enter_long"] = buy + signals["exit_long"] = sell + signals["enter_short"] = 0 + signals["exit_short"] = 0 return dataframe @@ -144,107 +142,120 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, - '--export', 'none' + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, + "--export", + "none", ] config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST) - assert 'max_open_trades' in config - assert 'stake_currency' in config - assert 'stake_amount' in config - assert 'exchange' in config - assert 'pair_whitelist' in config['exchange'] - assert 'datadir' in config - assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) + assert "max_open_trades" in config + assert "stake_currency" in config + assert "stake_amount" in config + assert "exchange" in config + assert "pair_whitelist" in config["exchange"] + assert "datadir" in config + assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog) + assert "timeframe" in config + assert not log_has_re("Parameter -i/--ticker-interval detected .*", caplog) - assert 'position_stacking' not in config - assert not log_has('Parameter --enable-position-stacking detected ...', caplog) + assert "position_stacking" not in config + assert not log_has("Parameter --enable-position-stacking detected ...", caplog) - assert 'timerange' not in config - assert 'export' in config - assert config['export'] == 'none' - assert 'runmode' in config - assert config['runmode'] == RunMode.BACKTEST + assert "timerange" not in config + assert "export" in config + assert config["export"] == "none" + assert "runmode" in config + assert config["runmode"] == RunMode.BACKTEST def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) - mocker.patch( - 'freqtrade.configuration.configuration.create_datadir', - lambda c, x: x - ) + mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, - '--datadir', '/foo/bar', - '--timeframe', '1m', - '--enable-position-stacking', - '--disable-max-market-positions', - '--timerange', ':100', - '--export-filename', 'foo_bar.json', - '--fee', '0', + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, + "--datadir", + "/foo/bar", + "--timeframe", + "1m", + "--enable-position-stacking", + "--disable-max-market-positions", + "--timerange", + ":100", + "--export-filename", + "foo_bar.json", + "--fee", + "0", ] config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST) - assert 'max_open_trades' in config - assert 'stake_currency' in config - assert 'stake_amount' in config - assert 'exchange' in config - assert 'pair_whitelist' in config['exchange'] - assert 'datadir' in config - assert config['runmode'] == RunMode.BACKTEST + assert "max_open_trades" in config + assert "stake_currency" in config + assert "stake_amount" in config + assert "exchange" in config + assert "pair_whitelist" in config["exchange"] + assert "datadir" in config + assert config["runmode"] == RunMode.BACKTEST - assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'timeframe' in config - assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', - caplog) + assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog) + assert "timeframe" in config + assert log_has("Parameter -i/--timeframe detected ... Using timeframe: 1m ...", caplog) - assert 'position_stacking' in config - assert log_has('Parameter --enable-position-stacking detected ...', caplog) + assert "position_stacking" in config + assert log_has("Parameter --enable-position-stacking detected ...", caplog) - assert 'use_max_market_positions' in config - assert log_has('Parameter --disable-max-market-positions detected ...', caplog) - assert log_has('max_open_trades set to unlimited ...', caplog) + assert "use_max_market_positions" in config + assert log_has("Parameter --disable-max-market-positions detected ...", caplog) + assert log_has("max_open_trades set to unlimited ...", caplog) - assert 'timerange' in config - assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) + assert "timerange" in config + assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog) - assert 'export' in config - assert 'exportfilename' in config - assert isinstance(config['exportfilename'], Path) - assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog) + assert "export" in config + assert "exportfilename" in config + assert isinstance(config["exportfilename"], Path) + assert log_has("Storing backtest results to {} ...".format(config["exportfilename"]), caplog) - assert 'fee' in config - assert log_has('Parameter --fee detected, setting fee to: {} ...'.format(config['fee']), caplog) + assert "fee" in config + assert log_has("Parameter --fee detected, setting fee to: {} ...".format(config["fee"]), caplog) def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) -> None: - patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, - '--stake-amount', '1', - '--starting-balance', '2' + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, + "--stake-amount", + "1", + "--starting-balance", + "2", ] conf = setup_optimize_configuration(get_args(args), RunMode.BACKTEST) assert isinstance(conf, dict) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, - '--stake-amount', '1', - '--starting-balance', '0.5' + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, + "--stake-amount", + "1", + "--starting-balance", + "0.5", ] with pytest.raises(OperationalException, match=r"Starting balance .* smaller .*"): setup_optimize_configuration(get_args(args), RunMode.BACKTEST) @@ -252,19 +263,21 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) def test_start(mocker, fee, default_conf, caplog) -> None: start_mock = MagicMock() - mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_fee", fee) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.start", start_mock) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, ] pargs = get_args(args) start_backtesting(pargs) - assert log_has('Starting freqtrade in Backtesting mode', caplog) + assert log_has("Starting freqtrade in Backtesting mode", caplog) assert start_mock.call_count == 1 @@ -276,11 +289,11 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: """ default_conf["order_types"] = order_types patch_exchange(mocker) - get_fee = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) + get_fee = mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.config == default_conf - assert backtesting.timeframe == '5m' + assert backtesting.timeframe == "5m" assert callable(backtesting.strategy.advise_all_indicators) assert callable(backtesting.strategy.advise_entry) assert callable(backtesting.strategy.advise_exit) @@ -293,27 +306,27 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - del default_conf['timeframe'] - default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, - 'HyperoptableStrategy'] + del default_conf["timeframe"] + default_conf["strategy_list"] = [CURRENT_TEST_STRATEGY, "HyperoptableStrategy"] - mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) - with pytest.raises(OperationalException, - match=r"Timeframe needs to be set in either configuration"): + mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.5)) + with pytest.raises( + OperationalException, match=r"Timeframe needs to be set in either configuration" + ): Backtesting(default_conf) def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) - default_conf['fee'] = 0.01234 + default_conf["fee"] = 0.01234 - fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) + fee_mock = mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.fee == 0.01234 assert fee_mock.call_count == 0 - default_conf['fee'] = 0.0 + default_conf["fee"] = 0.0 backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.fee == 0.0 @@ -322,19 +335,20 @@ def test_data_with_fee(default_conf, mocker) -> None: def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) - timerange = TimeRange.parse_timerange('1510694220-1510700340') - data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) + timerange = TimeRange.parse_timerange("1510694220-1510700340") + data = history.load_data( + testdatadir, "1m", ["UNITTEST/BTC"], timerange=timerange, fill_up_missing=True + ) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) processed = backtesting.strategy.advise_all_indicators(data) - assert len(processed['UNITTEST/BTC']) == 103 + assert len(processed["UNITTEST/BTC"]) == 103 # Load strategy to compare the result between Backtesting function and strategy are the same strategy = StrategyResolver.load_strategy(default_conf) processed2 = strategy.advise_all_indicators(data) - assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC']) + assert processed["UNITTEST/BTC"].equals(processed2["UNITTEST/BTC"]) def test_backtest_abort(default_conf, mocker, testdatadir) -> None: @@ -355,21 +369,23 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None: def get_timerange(input1): return dt_utc(2017, 11, 14, 21, 17), dt_utc(2017, 11, 14, 22, 59) - mocker.patch('freqtrade.data.history.get_timerange', get_timerange) + mocker.patch("freqtrade.data.history.get_timerange", get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') - mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') - mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') - sbs = mocker.patch('freqtrade.optimize.backtesting.store_backtest_stats') - sbc = mocker.patch('freqtrade.optimize.backtesting.store_backtest_analysis_results') - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") + mocker.patch("freqtrade.optimize.backtesting.generate_backtest_stats") + mocker.patch("freqtrade.optimize.backtesting.show_backtest_results") + sbs = mocker.patch("freqtrade.optimize.backtesting.store_backtest_stats") + sbc = mocker.patch("freqtrade.optimize.backtesting.store_backtest_analysis_results") + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) - default_conf['timeframe'] = '1m' - default_conf['export'] = 'signals' - default_conf['exportfilename'] = 'export.txt' - default_conf['timerange'] = '-1510694220' - default_conf['runmode'] = RunMode.BACKTEST + default_conf["timeframe"] = "1m" + default_conf["export"] = "signals" + default_conf["exportfilename"] = "export.txt" + default_conf["timerange"] = "-1510694220" + default_conf["runmode"] = RunMode.BACKTEST backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -378,8 +394,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None: backtesting.start() # check the logs, that will contain the backtest result exists = [ - 'Backtesting with data from 2017-11-14 21:17:00 ' - 'up to 2017-11-14 22:59:00 (0 days).' + "Backtesting with data from 2017-11-14 21:17:00 " "up to 2017-11-14 22:59:00 (0 days)." ] for line in exists: assert log_has(line, caplog) @@ -394,104 +409,126 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> def get_timerange(input1): return dt_utc(2017, 11, 14, 21, 17), dt_utc(2017, 11, 14, 22, 59) - mocker.patch('freqtrade.data.history.history_utils.load_pair_history', - MagicMock(return_value=pd.DataFrame())) - mocker.patch('freqtrade.data.history.get_timerange', get_timerange) + mocker.patch( + "freqtrade.data.history.history_utils.load_pair_history", + MagicMock(return_value=pd.DataFrame()), + ) + mocker.patch("freqtrade.data.history.get_timerange", get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) - default_conf['timeframe'] = "1m" - default_conf['export'] = 'none' - default_conf['timerange'] = '20180101-20180102' + default_conf["timeframe"] = "1m" + default_conf["export"] = "none" + default_conf["timerange"] = "20180101-20180102" backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - with pytest.raises(OperationalException, match='No data found. Terminating.'): + with pytest.raises(OperationalException, match="No data found. Terminating."): backtesting.start() def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None: - mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) - mocker.patch('freqtrade.data.history.history_utils.load_pair_history', - MagicMock(return_value=pd.DataFrame())) - mocker.patch('freqtrade.data.history.get_timerange', get_timerange) + mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) + mocker.patch( + "freqtrade.data.history.history_utils.load_pair_history", + MagicMock(return_value=pd.DataFrame()), + ) + mocker.patch("freqtrade.data.history.get_timerange", get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=[])) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", PropertyMock(return_value=[]) + ) - default_conf['timeframe'] = "1m" - default_conf['export'] = 'none' - default_conf['timerange'] = '20180101-20180102' + default_conf["timeframe"] = "1m" + default_conf["export"] = "none" + default_conf["timerange"] = "20180101-20180102" - with pytest.raises(OperationalException, match='No pair in whitelist.'): + with pytest.raises(OperationalException, match="No pair in whitelist."): Backtesting(default_conf) - default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}] - with pytest.raises(OperationalException, - match=r'VolumePairList not allowed for backtesting\..*StaticPairList.*'): + default_conf["pairlists"] = [{"method": "VolumePairList", "number_assets": 5}] + with pytest.raises( + OperationalException, + match=r"VolumePairList not allowed for backtesting\..*StaticPairList.*", + ): Backtesting(default_conf) - default_conf.update({ - 'pairlists': [{"method": "StaticPairList"}], - 'timeframe_detail': '1d', - }) + default_conf.update( + { + "pairlists": [{"method": "StaticPairList"}], + "timeframe_detail": "1d", + } + ) - with pytest.raises(OperationalException, - match='Detail timeframe must be smaller than strategy timeframe.'): + with pytest.raises( + OperationalException, match="Detail timeframe must be smaller than strategy timeframe." + ): Backtesting(default_conf) def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: - mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) - mocker.patch(f'{EXMS}.get_tickers', tickers) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.data.history.get_timerange', get_timerange) + mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) + mocker.patch(f"{EXMS}.get_tickers", tickers) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) + mocker.patch("freqtrade.data.history.get_timerange", get_timerange) patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['XRP/BTC'])) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.refresh_pairlist') + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["XRP/BTC"]), + ) + mocker.patch("freqtrade.plugins.pairlistmanager.PairListManager.refresh_pairlist") - default_conf['ticker_interval'] = "1m" - default_conf['export'] = 'none' + default_conf["ticker_interval"] = "1m" + default_conf["export"] = "none" # Use stoploss from strategy - del default_conf['stoploss'] - default_conf['timerange'] = '20180101-20180102' + del default_conf["stoploss"] + default_conf["timerange"] = "20180101-20180102" - default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}] - with pytest.raises(OperationalException, - match=r'VolumePairList not allowed for backtesting\..*StaticPairList.*'): + default_conf["pairlists"] = [{"method": "VolumePairList", "number_assets": 5}] + with pytest.raises( + OperationalException, + match=r"VolumePairList not allowed for backtesting\..*StaticPairList.*", + ): Backtesting(default_conf) - default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] - with pytest.raises(OperationalException, - match='PerformanceFilter not allowed for backtesting.'): + default_conf["pairlists"] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] + with pytest.raises( + OperationalException, match="PerformanceFilter not allowed for backtesting." + ): Backtesting(default_conf) - default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}, ] + default_conf["pairlists"] = [ + {"method": "StaticPairList"}, + {"method": "PrecisionFilter"}, + ] Backtesting(default_conf) # Multiple strategies - default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'StrategyTestV2'] - with pytest.raises(OperationalException, - match='PrecisionFilter not allowed for backtesting multiple strategies.'): + default_conf["strategy_list"] = [CURRENT_TEST_STRATEGY, "StrategyTestV2"] + with pytest.raises( + OperationalException, + match="PrecisionFilter not allowed for backtesting multiple strategies.", + ): Backtesting(default_conf) def test_backtest__enter_trade(default_conf, fee, mocker) -> None: - default_conf['use_exit_signal'] = False - mocker.patch(f'{EXMS}.get_fee', fee) - mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001) - mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf')) + default_conf["use_exit_signal"] = False + mocker.patch(f"{EXMS}.get_fee", fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) patch_exchange(mocker) - default_conf['stake_amount'] = 'unlimited' - default_conf['max_open_trades'] = 2 + default_conf["stake_amount"] = "unlimited" + default_conf["max_open_trades"] = 2 backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - pair = 'UNITTEST/BTC' + pair = "UNITTEST/BTC" row = [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), 1, # Buy @@ -500,68 +537,68 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: 0, # Sell 0.00099, # Low 0.0012, # High - '', # Buy Signal Name + "", # Buy Signal Name ] - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert isinstance(trade, LocalTrade) assert trade.stake_amount == 495 # Fake 2 trades, so there's not enough amount for the next trade left. LocalTrade.trades_open.append(trade) backtesting.wallets.update() - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is None LocalTrade.trades_open.pop() - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is not None LocalTrade.trades_open.pop() backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5 backtesting.wallets.update() - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") LocalTrade.trades_open.pop() assert trade assert trade.stake_amount == 123.5 # In case of error - use proposed stake backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") LocalTrade.trades_open.pop() assert trade assert trade.stake_amount == 495 assert trade.is_short is False - trade = backtesting._enter_trade(pair, row=row, direction='short') + trade = backtesting._enter_trade(pair, row=row, direction="short") LocalTrade.trades_open.pop() assert trade assert trade.stake_amount == 495 assert trade.is_short is True mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0) - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") LocalTrade.trades_open.pop() assert trade assert trade.stake_amount == 300.0 def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: - default_conf_usdt['use_exit_signal'] = False - mocker.patch(f'{EXMS}.get_fee', fee) + default_conf_usdt["use_exit_signal"] = False + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) mocker.patch(f"{EXMS}.get_max_leverage", return_value=100) mocker.patch("freqtrade.optimize.backtesting.price_to_precision", lambda p, *args: p) patch_exchange(mocker) - default_conf_usdt['stake_amount'] = 300 - default_conf_usdt['max_open_trades'] = 2 - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' - default_conf_usdt['stake_currency'] = 'USDT' - default_conf_usdt['exchange']['pair_whitelist'] = ['.*'] + default_conf_usdt["stake_amount"] = 300 + default_conf_usdt["max_open_trades"] = 2 + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" + default_conf_usdt["stake_currency"] = "USDT" + default_conf_usdt["exchange"]["pair_whitelist"] = [".*"] backtesting = Backtesting(default_conf_usdt) backtesting._set_strategy(backtesting.strategylist[0]) - mocker.patch('freqtrade.optimize.backtesting.Backtesting._run_funding_fees') - pair = 'ETH/USDT:USDT' + mocker.patch("freqtrade.optimize.backtesting.Backtesting._run_funding_fees") + pair = "ETH/USDT:USDT" row = [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), 0.1, # Open @@ -572,14 +609,13 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: 0, # exit_long 1, # enter_short 0, # exit_hsort - '', # Long Signal Name - '', # Short Signal Name - '', # Exit Signal Name + "", # Long Signal Name + "", # Short Signal Name + "", # Exit Signal Name ] backtesting.strategy.leverage = MagicMock(return_value=5.0) - mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", - return_value=(0.01, 0.01)) + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) # leverage = 5 # ep1(trade.open_rate) = 0.1 @@ -599,7 +635,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: # = 0.08080740740740741 + ((0.1 - 0.08080740740740741) * 0.05 * 1) # = 0.08176703703703704 - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert pytest.approx(trade.liquidation_price) == 0.081767037 # Binance, Short @@ -611,36 +647,38 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: # = 0.11881254125412541 + (abs(0.1 - 0.11881254125412541) * 0.05 * -1) # = 0.11787191419141915 - trade = backtesting._enter_trade(pair, row=row, direction='short') + trade = backtesting._enter_trade(pair, row=row, direction="short") assert pytest.approx(trade.liquidation_price) == 0.11787191 assert pytest.approx(trade.orders[0].cost) == ( - trade.stake_amount * trade.leverage + trade.fee_open) + trade.stake_amount * trade.leverage + trade.fee_open + ) assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount # Stake-amount too high! mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0) - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is None # Stake-amount throwing error - mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount", - side_effect=DependencyException) + mocker.patch( + "freqtrade.wallets.Wallets.get_trade_stake_amount", side_effect=DependencyException + ) - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is None def test_backtest__check_trade_exit(default_conf, mocker) -> None: - default_conf['use_exit_signal'] = False + default_conf["use_exit_signal"] = False patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - default_conf['timeframe_detail'] = '1m' - default_conf['max_open_trades'] = 2 + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + default_conf["timeframe_detail"] = "1m" + default_conf["max_open_trades"] = 2 backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - pair = 'UNITTEST/BTC' + pair = "UNITTEST/BTC" row = [ pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc), 200, # Open @@ -651,12 +689,12 @@ def test_backtest__check_trade_exit(default_conf, mocker) -> None: 0, # exit_long 0, # enter_short 0, # exit_hsort - '', # Long Signal Name - '', # Short Signal Name - '', # Exit Signal Name + "", # Long Signal Name + "", # Short Signal Name + "", # Exit Signal Name ] - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert isinstance(trade, LocalTrade) row_sell = [ @@ -669,10 +707,9 @@ def test_backtest__check_trade_exit(default_conf, mocker) -> None: 0, # exit_long 0, # enter_short 0, # exit_short - '', # long Signal Name - '', # Short Signal Name - '', # Exit Signal Name - + "", # long Signal Name + "", # Short Signal Name + "", # Exit Signal Name ] # No data available. @@ -682,30 +719,45 @@ def test_backtest__check_trade_exit(default_conf, mocker) -> None: assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) # Enter new trade - trade = backtesting._enter_trade(pair, row=row, direction='long') + trade = backtesting._enter_trade(pair, row=row, direction="long") assert isinstance(trade, LocalTrade) # Assign empty ... no result. backtesting.detail_data[pair] = pd.DataFrame( - [], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', - 'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']) + [], + columns=[ + "date", + "open", + "high", + "low", + "close", + "enter_long", + "exit_long", + "enter_short", + "exit_short", + "long_tag", + "short_tag", + "exit_tag", + ], + ) res = backtesting._check_trade_exit(trade, row, row[0].to_pydatetime()) assert res is None def test_backtest_one(default_conf, mocker, testdatadir) -> None: - default_conf['use_exit_signal'] = False - default_conf['max_open_trades'] = 10 + default_conf["use_exit_signal"] = False + default_conf["max_open_trades"] = 10 patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - pair = 'UNITTEST/BTC' - timerange = TimeRange('date', None, 1517227800, 0) - data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], - timerange=timerange) + pair = "UNITTEST/BTC" + timerange = TimeRange("date", None, 1517227800, 0) + data = history.load_data( + datadir=testdatadir, timeframe="5m", pairs=["UNITTEST/BTC"], timerange=timerange + ) processed = backtesting.strategy.advise_all_indicators(data) backtesting.strategy.order_filled = MagicMock() min_date, max_date = get_timerange(processed) @@ -715,107 +767,131 @@ def test_backtest_one(default_conf, mocker, testdatadir) -> None: start_date=min_date, end_date=max_date, ) - results = result['results'] + results = result["results"] assert not results.empty assert len(results) == 2 expected = pd.DataFrame( - {'pair': [pair, pair], - 'stake_amount': [0.001, 0.001], - 'max_stake_amount': [0.001, 0.001], - 'amount': [0.00957442, 0.0097064], - 'open_date': pd.to_datetime([dt_utc(2018, 1, 29, 18, 40, 0), - dt_utc(2018, 1, 30, 3, 30, 0)], utc=True - ), - 'close_date': pd.to_datetime([dt_utc(2018, 1, 29, 22, 35, 0), - dt_utc(2018, 1, 30, 4, 10, 0)], utc=True), - 'open_rate': [0.104445, 0.10302485], - 'close_rate': [0.104969, 0.103541], - 'fee_open': [0.0025, 0.0025], - 'fee_close': [0.0025, 0.0025], - 'trade_duration': [235, 40], - 'profit_ratio': [0.0, 0.0], - 'profit_abs': [0.0, 0.0], - 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value], - 'initial_stop_loss_abs': [0.0940005, 0.09272236], - 'initial_stop_loss_ratio': [-0.1, -0.1], - 'stop_loss_abs': [0.0940005, 0.09272236], - 'stop_loss_ratio': [-0.1, -0.1], - 'min_rate': [0.10370188, 0.10300000000000001], - 'max_rate': [0.10501, 0.1038888], - 'is_open': [False, False], - 'enter_tag': ['', ''], - "leverage": [1.0, 1.0], - "is_short": [False, False], - 'open_timestamp': [1517251200000, 1517283000000], - 'close_timestamp': [1517265300000, 1517285400000], - 'orders': [ - [ - {'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy', - 'order_filled_timestamp': 1517251200000, 'ft_is_entry': True, - 'ft_order_tag': ''}, - {'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell', - 'order_filled_timestamp': 1517265300000, 'ft_is_entry': False, - 'ft_order_tag': 'roi'} - ], [ - {'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy', - 'order_filled_timestamp': 1517283000000, 'ft_is_entry': True, - 'ft_order_tag': ''}, - {'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell', - 'order_filled_timestamp': 1517285400000, 'ft_is_entry': False, - 'ft_order_tag': 'roi'} - ] - ] - }) + { + "pair": [pair, pair], + "stake_amount": [0.001, 0.001], + "max_stake_amount": [0.001, 0.001], + "amount": [0.00957442, 0.0097064], + "open_date": pd.to_datetime( + [dt_utc(2018, 1, 29, 18, 40, 0), dt_utc(2018, 1, 30, 3, 30, 0)], utc=True + ), + "close_date": pd.to_datetime( + [dt_utc(2018, 1, 29, 22, 35, 0), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True + ), + "open_rate": [0.104445, 0.10302485], + "close_rate": [0.104969, 0.103541], + "fee_open": [0.0025, 0.0025], + "fee_close": [0.0025, 0.0025], + "trade_duration": [235, 40], + "profit_ratio": [0.0, 0.0], + "profit_abs": [0.0, 0.0], + "exit_reason": [ExitType.ROI.value, ExitType.ROI.value], + "initial_stop_loss_abs": [0.0940005, 0.09272236], + "initial_stop_loss_ratio": [-0.1, -0.1], + "stop_loss_abs": [0.0940005, 0.09272236], + "stop_loss_ratio": [-0.1, -0.1], + "min_rate": [0.10370188, 0.10300000000000001], + "max_rate": [0.10501, 0.1038888], + "is_open": [False, False], + "enter_tag": ["", ""], + "leverage": [1.0, 1.0], + "is_short": [False, False], + "open_timestamp": [1517251200000, 1517283000000], + "close_timestamp": [1517265300000, 1517285400000], + "orders": [ + [ + { + "amount": 0.00957442, + "safe_price": 0.104445, + "ft_order_side": "buy", + "order_filled_timestamp": 1517251200000, + "ft_is_entry": True, + "ft_order_tag": "", + }, + { + "amount": 0.00957442, + "safe_price": 0.10496853383458644, + "ft_order_side": "sell", + "order_filled_timestamp": 1517265300000, + "ft_is_entry": False, + "ft_order_tag": "roi", + }, + ], + [ + { + "amount": 0.0097064, + "safe_price": 0.10302485, + "ft_order_side": "buy", + "order_filled_timestamp": 1517283000000, + "ft_is_entry": True, + "ft_order_tag": "", + }, + { + "amount": 0.0097064, + "safe_price": 0.10354126528822055, + "ft_order_side": "sell", + "order_filled_timestamp": 1517285400000, + "ft_is_entry": False, + "ft_order_tag": "roi", + }, + ], + ], + } + ) pd.testing.assert_frame_equal(results, expected) - assert 'orders' in results.columns + assert "orders" in results.columns data_pair = processed[pair] # Called once per order assert backtesting.strategy.order_filled.call_count == 4 for _, t in results.iterrows(): - assert len(t['orders']) == 2 + assert len(t["orders"]) == 2 ln = data_pair.loc[data_pair["date"] == t["open_date"]] # Check open trade rate aligns to open rate assert not ln.empty assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6) # check close trade rate aligns to close rate or is between high and low ln1 = data_pair.loc[data_pair["date"] == t["close_date"]] - assert (round(ln1.iloc[0]["open"], 6) == round(t["close_rate"], 6) or - round(ln1.iloc[0]["low"], 6) < round( - t["close_rate"], 6) < round(ln1.iloc[0]["high"], 6)) + assert round(ln1.iloc[0]["open"], 6) == round(t["close_rate"], 6) or round( + ln1.iloc[0]["low"], 6 + ) < round(t["close_rate"], 6) < round(ln1.iloc[0]["high"], 6) -@pytest.mark.parametrize('use_detail', [True, False]) +@pytest.mark.parametrize("use_detail", [True, False]) def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) -> None: - default_conf_usdt['use_exit_signal'] = False + default_conf_usdt["use_exit_signal"] = False patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) if use_detail: - default_conf_usdt['timeframe_detail'] = '1m' + default_conf_usdt["timeframe_detail"] = "1m" def advise_entry(df, *args, **kwargs): # Mock function to force several entries - df.loc[(df['rsi'] < 40), 'enter_long'] = 1 + df.loc[(df["rsi"] < 40), "enter_long"] = 1 return df def custom_entry_price(proposed_rate, **kwargs): return proposed_rate * 0.997 - default_conf_usdt['max_open_trades'] = 10 + default_conf_usdt["max_open_trades"] = 10 backtesting = Backtesting(default_conf_usdt) backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.populate_entry_trend = advise_entry backtesting.strategy.custom_entry_price = custom_entry_price - pair = 'XRP/ETH' + pair = "XRP/ETH" # Pick a timerange adapted to the pair we use to test - timerange = TimeRange.parse_timerange('20191010-20191013') - data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=[pair], - timerange=timerange) + timerange = TimeRange.parse_timerange("20191010-20191013") + data = history.load_data(datadir=testdatadir, timeframe="5m", pairs=[pair], timerange=timerange) if use_detail: - data_1m = history.load_data(datadir=testdatadir, timeframe='1m', pairs=[pair], - timerange=timerange) + data_1m = history.load_data( + datadir=testdatadir, timeframe="1m", pairs=[pair], timerange=timerange + ) backtesting.detail_data = data_1m processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -825,33 +901,37 @@ def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) start_date=min_date, end_date=max_date, ) - results = result['results'] + results = result["results"] assert not results.empty # Timeout settings from default_conf = entry: 10, exit: 30 assert len(results) == (2 if use_detail else 3) - assert 'orders' in results.columns + assert "orders" in results.columns data_pair = processed[pair] data_1m_pair = data_1m[pair] if use_detail else pd.DataFrame() late_entry = 0 for _, t in results.iterrows(): - assert len(t['orders']) == 2 + assert len(t["orders"]) == 2 - entryo = t['orders'][0] - entry_ts = datetime.fromtimestamp(entryo['order_filled_timestamp'] // 1000, tz=timezone.utc) - if entry_ts > t['open_date']: + entryo = t["orders"][0] + entry_ts = datetime.fromtimestamp(entryo["order_filled_timestamp"] // 1000, tz=timezone.utc) + if entry_ts > t["open_date"]: late_entry += 1 # Get "entry fill" candle - ln = (data_1m_pair.loc[data_1m_pair["date"] == entry_ts] - if use_detail else data_pair.loc[data_pair["date"] == entry_ts]) + ln = ( + data_1m_pair.loc[data_1m_pair["date"] == entry_ts] + if use_detail + else data_pair.loc[data_pair["date"] == entry_ts] + ) # Check open trade rate aligns to open rate assert not ln.empty # assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6) - assert round(ln.iloc[0]["low"], 6) <= round( - t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6) + assert ( + round(ln.iloc[0]["low"], 6) <= round(t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6) + ) # check close trade rate aligns to close rate or is between high and low ln1 = data_pair.loc[data_pair["date"] == t["close_date"]] if use_detail: @@ -861,56 +941,68 @@ def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) assert not ln1.empty ln2 = ln1_1m if ln1.empty else ln1 - assert (round(ln2.iloc[0]["low"], 6) <= round( - t["close_rate"], 6) <= round(ln2.iloc[0]["high"], 6)) + assert ( + round(ln2.iloc[0]["low"], 6) + <= round(t["close_rate"], 6) + <= round(ln2.iloc[0]["high"], 6) + ) assert late_entry > 0 -@pytest.mark.parametrize('use_detail,exp_funding_fee, exp_ff_updates', [ - (True, -0.018054162, 11), - (False, -0.01780296, 5), - ]) +@pytest.mark.parametrize( + "use_detail,exp_funding_fee, exp_ff_updates", + [ + (True, -0.018054162, 11), + (False, -0.01780296, 5), + ], +) def test_backtest_one_detail_futures( - default_conf_usdt, mocker, testdatadir, use_detail, exp_funding_fee, - exp_ff_updates) -> None: - default_conf_usdt['use_exit_signal'] = False - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' - default_conf_usdt['candle_type_def'] = CandleType.FUTURES + default_conf_usdt, mocker, testdatadir, use_detail, exp_funding_fee, exp_ff_updates +) -> None: + default_conf_usdt["use_exit_signal"] = False + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" + default_conf_usdt["candle_type_def"] = CandleType.FUTURES patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", - return_value=(0.01, 0.01)) - default_conf_usdt['timeframe'] = '1h' + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["XRP/USDT:USDT"]), + ) + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) + default_conf_usdt["timeframe"] = "1h" if use_detail: - default_conf_usdt['timeframe_detail'] = '5m' + default_conf_usdt["timeframe_detail"] = "5m" def advise_entry(df, *args, **kwargs): # Mock function to force several entries - df.loc[(df['rsi'] < 40), 'enter_long'] = 1 + df.loc[(df["rsi"] < 40), "enter_long"] = 1 return df def custom_entry_price(proposed_rate, **kwargs): return proposed_rate * 0.997 - default_conf_usdt['max_open_trades'] = 10 + default_conf_usdt["max_open_trades"] = 10 backtesting = Backtesting(default_conf_usdt) - ff_spy = mocker.spy(backtesting.exchange, 'calculate_funding_fees') + ff_spy = mocker.spy(backtesting.exchange, "calculate_funding_fees") backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.populate_entry_trend = advise_entry backtesting.strategy.custom_entry_price = custom_entry_price - pair = 'XRP/USDT:USDT' + pair = "XRP/USDT:USDT" # Pick a timerange adapted to the pair we use to test - timerange = TimeRange.parse_timerange('20211117-20211119') - data = history.load_data(datadir=Path(testdatadir), timeframe='1h', pairs=[pair], - timerange=timerange, candle_type=CandleType.FUTURES) + timerange = TimeRange.parse_timerange("20211117-20211119") + data = history.load_data( + datadir=Path(testdatadir), + timeframe="1h", + pairs=[pair], + timerange=timerange, + candle_type=CandleType.FUTURES, + ) backtesting.load_bt_data_detail() processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -920,32 +1012,36 @@ def test_backtest_one_detail_futures( start_date=min_date, end_date=max_date, ) - results = result['results'] + results = result["results"] assert not results.empty # Timeout settings from default_conf = entry: 10, exit: 30 assert len(results) == (5 if use_detail else 2) - assert 'orders' in results.columns + assert "orders" in results.columns data_pair = processed[pair] data_1m_pair = backtesting.detail_data[pair] if use_detail else pd.DataFrame() late_entry = 0 for _, t in results.iterrows(): - assert len(t['orders']) == 2 + assert len(t["orders"]) == 2 - entryo = t['orders'][0] - entry_ts = datetime.fromtimestamp(entryo['order_filled_timestamp'] // 1000, tz=timezone.utc) - if entry_ts > t['open_date']: + entryo = t["orders"][0] + entry_ts = datetime.fromtimestamp(entryo["order_filled_timestamp"] // 1000, tz=timezone.utc) + if entry_ts > t["open_date"]: late_entry += 1 # Get "entry fill" candle - ln = (data_1m_pair.loc[data_1m_pair["date"] == entry_ts] - if use_detail else data_pair.loc[data_pair["date"] == entry_ts]) + ln = ( + data_1m_pair.loc[data_1m_pair["date"] == entry_ts] + if use_detail + else data_pair.loc[data_pair["date"] == entry_ts] + ) # Check open trade rate aligns to open rate assert not ln.empty - assert round(ln.iloc[0]["low"], 6) <= round( - t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6) + assert ( + round(ln.iloc[0]["low"], 6) <= round(t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6) + ) # check close trade rate aligns to close rate or is between high and low ln1 = data_pair.loc[data_pair["date"] == t["close_date"]] if use_detail: @@ -955,67 +1051,86 @@ def test_backtest_one_detail_futures( assert not ln1.empty ln2 = ln1_1m if ln1.empty else ln1 - assert (round(ln2.iloc[0]["low"], 6) <= round( - t["close_rate"], 6) <= round(ln2.iloc[0]["high"], 6)) + assert ( + round(ln2.iloc[0]["low"], 6) + <= round(t["close_rate"], 6) + <= round(ln2.iloc[0]["high"], 6) + ) assert pytest.approx(Trade.trades[1].funding_fees) == exp_funding_fee assert ff_spy.call_count == exp_ff_updates # assert late_entry > 0 -@pytest.mark.parametrize('use_detail,entries,max_stake,ff_updates,expected_ff', [ - (True, 50, 3000, 54, -1.18038144), - (False, 6, 360, 10, -0.14679994), -]) +@pytest.mark.parametrize( + "use_detail,entries,max_stake,ff_updates,expected_ff", + [ + (True, 50, 3000, 54, -1.18038144), + (False, 6, 360, 10, -0.14679994), + ], +) def test_backtest_one_detail_futures_funding_fees( - default_conf_usdt, fee, mocker, testdatadir, use_detail, entries, max_stake, - ff_updates, expected_ff, + default_conf_usdt, + fee, + mocker, + testdatadir, + use_detail, + entries, + max_stake, + ff_updates, + expected_ff, ) -> None: """ Funding fees are expected to differ, as the maximum position size differs. """ - default_conf_usdt['use_exit_signal'] = False - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' - default_conf_usdt['candle_type_def'] = CandleType.FUTURES - default_conf_usdt['minimal_roi'] = {'0': 1} - default_conf_usdt['dry_run_wallet'] = 100000 + default_conf_usdt["use_exit_signal"] = False + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" + default_conf_usdt["candle_type_def"] = CandleType.FUTURES + default_conf_usdt["minimal_roi"] = {"0": 1} + default_conf_usdt["dry_run_wallet"] = 100000 - mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", - return_value=(0.01, 0.01)) - default_conf_usdt['timeframe'] = '1h' + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["XRP/USDT:USDT"]), + ) + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) + default_conf_usdt["timeframe"] = "1h" if use_detail: - default_conf_usdt['timeframe_detail'] = '5m' + default_conf_usdt["timeframe_detail"] = "5m" patch_exchange(mocker) def advise_entry(df, *args, **kwargs): # Mock function to force several entries - df.loc[:, 'enter_long'] = 1 + df.loc[:, "enter_long"] = 1 return df def adjust_trade_position(trade, current_time, **kwargs): if current_time > datetime(2021, 11, 18, 2, 0, 0, tzinfo=timezone.utc): return None - return default_conf_usdt['stake_amount'] + return default_conf_usdt["stake_amount"] - default_conf_usdt['max_open_trades'] = 1 + default_conf_usdt["max_open_trades"] = 1 backtesting = Backtesting(default_conf_usdt) - ff_spy = mocker.spy(backtesting.exchange, 'calculate_funding_fees') + ff_spy = mocker.spy(backtesting.exchange, "calculate_funding_fees") backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.populate_entry_trend = advise_entry backtesting.strategy.adjust_trade_position = adjust_trade_position backtesting.strategy.leverage = lambda **kwargs: 1 backtesting.strategy.position_adjustment_enable = True - pair = 'XRP/USDT:USDT' + pair = "XRP/USDT:USDT" # Pick a timerange adapted to the pair we use to test - timerange = TimeRange.parse_timerange('20211117-20211119') - data = history.load_data(datadir=Path(testdatadir), timeframe='1h', pairs=[pair], - timerange=timerange, candle_type=CandleType.FUTURES) + timerange = TimeRange.parse_timerange("20211117-20211119") + data = history.load_data( + datadir=Path(testdatadir), + timeframe="1h", + pairs=[pair], + timerange=timerange, + candle_type=CandleType.FUTURES, + ) backtesting.load_bt_data_detail() processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -1025,12 +1140,12 @@ def test_backtest_one_detail_futures_funding_fees( start_date=min_date, end_date=max_date, ) - results = result['results'] + results = result["results"] assert not results.empty # Only one result - as we're not selling. assert len(results) == 1 - assert 'orders' in results.columns + assert "orders" in results.columns # funding_fees have been calculated for each funding-fee candle # the trade is open for 26 hours - hence we expect the 8h fee to apply 4 times. # Additional counts will happen due each successful entry, which needs to call this, too. @@ -1047,21 +1162,22 @@ def test_backtest_one_detail_futures_funding_fees( 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 + 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(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) patch_exchange(mocker) - default_conf['max_open_trades'] = 1 + default_conf["max_open_trades"] = 1 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) + 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( @@ -1070,23 +1186,24 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) end_date=max_date, ) - assert result['timedout_entry_orders'] == 10 + assert result["timedout_entry_orders"] == 10 def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: - default_conf['use_exit_signal'] = False - default_conf['max_open_trades'] = 1 - mocker.patch(f'{EXMS}.get_fee', fee) + default_conf["use_exit_signal"] = False + default_conf["max_open_trades"] = 1 + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) # Run a backtesting for an exiting 1min timeframe - timerange = TimeRange.parse_timerange('1510688220-1510700340') - data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'], - timerange=timerange) + timerange = TimeRange.parse_timerange("1510688220-1510700340") + data = history.load_data( + datadir=testdatadir, timeframe="1m", pairs=["UNITTEST/BTC"], timerange=timerange + ) processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) results = backtesting.backtest( @@ -1094,30 +1211,31 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None start_date=min_date, end_date=max_date, ) - assert not results['results'].empty - assert len(results['results']) == 1 + assert not results["results"].empty + assert len(results["results"]) == 1 def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None: - default_conf['use_exit_signal'] = False - default_conf['max_open_trades'] = 10 + default_conf["use_exit_signal"] = False + default_conf["max_open_trades"] = 10 - mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - timerange = TimeRange('date', None, 1517227800, 0) + timerange = TimeRange("date", None, 1517227800, 0) backtesting.required_startup = 100 backtesting.timerange = timerange - data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], - timerange=timerange) - df = data['UNITTEST/BTC'] - df['date'] = df.loc[:, 'date'] - timedelta(days=1) + data = history.load_data( + datadir=testdatadir, timeframe="5m", pairs=["UNITTEST/BTC"], timerange=timerange + ) + df = data["UNITTEST/BTC"] + df["date"] = df.loc[:, "date"] - timedelta(days=1) # Trimming 100 candles, so after 2nd trimming, no candle is left. df = df.iloc[:100] - data['XRP/USDT'] = df + data["XRP/USDT"] = df processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -1133,29 +1251,29 @@ def test_processed(default_conf, mocker, testdatadir) -> None: backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - dict_of_tickerrows = load_data_test('raise', testdatadir) + dict_of_tickerrows = load_data_test("raise", testdatadir) dataframes = backtesting.strategy.advise_all_indicators(dict_of_tickerrows) - dataframe = dataframes['UNITTEST/BTC'] + dataframe = dataframes["UNITTEST/BTC"] cols = dataframe.columns # assert the dataframe got some of the indicator columns - for col in ['close', 'high', 'low', 'open', 'date', - 'ema10', 'rsi', 'fastd', 'plus_di']: + for col in ["close", "high", "low", "open", "date", "ema10", "rsi", "fastd", "plus_di"]: assert col in cols def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: - default_conf['use_exit_signal'] = False - default_conf['max_open_trades'] = 10 - default_conf['runmode'] = 'backtest' - mocker.patch(f'{EXMS}.get_fee', fee) + default_conf["use_exit_signal"] = False + default_conf["max_open_trades"] = 10 + default_conf["runmode"] = "backtest" + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - timerange = TimeRange('date', None, 1517227800, 0) - data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], - timerange=timerange) + timerange = TimeRange("date", None, 1517227800, 0) + data = history.load_data( + datadir=testdatadir, timeframe="5m", pairs=["UNITTEST/BTC"], timerange=timerange + ) processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -1166,17 +1284,18 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi dp = backtesting.strategy.dp df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe) current_candle = df.iloc[-1].squeeze() - assert current_candle['enter_long'] == 1 + assert current_candle["enter_long"] == 1 - candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date']) + candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle["date"]) assert candle_date == current_time # These asserts don't properly raise as they are nested, # therefore we increment count and assert for that. df = dp.get_pair_dataframe(pair, backtesting.strategy.timeframe) - prior_time = timeframe_to_prev_date(backtesting.strategy.timeframe, - candle_date - timedelta(seconds=1)) - assert prior_time == df.iloc[-1].squeeze()['date'] - assert df.iloc[-1].squeeze()['date'] < current_time + prior_time = timeframe_to_prev_date( + backtesting.strategy.timeframe, candle_date - timedelta(seconds=1) + ) + assert prior_time == df.iloc[-1].squeeze()["date"] + assert df.iloc[-1].squeeze()["date"] < current_time count += 1 @@ -1193,24 +1312,25 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad # While this test IS a copy of test_backtest_pricecontours, it's needed to ensure # results do not carry-over to the next run, which is not given by using parametrize. patch_exchange(mocker) - default_conf['protections'] = [ + default_conf["protections"] = [ { "method": "CooldownPeriod", "stop_duration": 3, - }] + } + ] - default_conf['enable_protections'] = True - default_conf['timeframe'] = '1m' - default_conf['max_open_trades'] = 1 - mocker.patch(f'{EXMS}.get_fee', fee) + default_conf["enable_protections"] = True + default_conf["timeframe"] = "1m" + default_conf["max_open_trades"] = 1 + mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) tests = [ - ['sine', 9], - ['raise', 10], - ['lower', 0], - ['sine', 9], - ['raise', 10], + ["sine", 9], + ["raise", 10], + ["lower", 0], + ["sine", 9], + ["raise", 10], ] backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1229,34 +1349,38 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad start_date=min_date, end_date=max_date, ) - assert len(results['results']) == numres + assert len(results["results"]) == numres -@pytest.mark.parametrize('protections,contour,expected', [ - (None, 'sine', 35), - (None, 'raise', 19), - (None, 'lower', 0), - (None, 'sine', 35), - (None, 'raise', 19), - ([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9), - ([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10), - ([{"method": "CooldownPeriod", "stop_duration": 3}], 'lower', 0), - ([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9), - ([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10), -]) -def test_backtest_pricecontours(default_conf, mocker, testdatadir, - protections, contour, expected) -> None: +@pytest.mark.parametrize( + "protections,contour,expected", + [ + (None, "sine", 35), + (None, "raise", 19), + (None, "lower", 0), + (None, "sine", 35), + (None, "raise", 19), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 9), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 10), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "lower", 0), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 9), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 10), + ], +) +def test_backtest_pricecontours( + default_conf, mocker, testdatadir, protections, contour, expected +) -> None: if protections: - default_conf['protections'] = protections - default_conf['enable_protections'] = True + default_conf["protections"] = protections + default_conf["enable_protections"] = True patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) # While entry-signals are unrealistic, running backtesting # over and over again should not cause different results - default_conf['timeframe'] = '1m' + default_conf["timeframe"] = "1m" backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1265,13 +1389,13 @@ def test_backtest_pricecontours(default_conf, mocker, testdatadir, min_date, max_date = get_timerange(processed) assert isinstance(processed, dict) backtesting.strategy.max_open_trades = 1 - backtesting.config.update({'max_open_trades': 1}) + backtesting.config.update({"max_open_trades": 1}) results = backtesting.backtest( processed=processed, start_date=min_date, end_date=max_date, ) - assert len(results['results']) == expected + assert len(results["results"]) == expected def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): @@ -1280,14 +1404,15 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): buy_value = 1 sell_value = 1 return _trend(dataframe, buy_value, sell_value) - default_conf['max_open_trades'] = 10 + + default_conf["max_open_trades"] = 10 backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.advise_entry = fun # Override backtesting.strategy.advise_exit = fun # Override result = backtesting.backtest(**backtest_conf) - assert result['results'].empty + assert result["results"].empty def test_backtest_only_sell(mocker, default_conf, testdatadir): @@ -1297,25 +1422,26 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): sell_value = 1 return _trend(dataframe, buy_value, sell_value) - default_conf['max_open_trades'] = 10 + default_conf["max_open_trades"] = 10 backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.advise_entry = fun # Override backtesting.strategy.advise_exit = fun # Override result = backtesting.backtest(**backtest_conf) - assert result['results'].empty + assert result["results"].empty def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch(f'{EXMS}.get_fee', fee) - default_conf['max_open_trades'] = 10 - default_conf['runmode'] = 'backtest' - backtest_conf = _make_backtest_conf(mocker, conf=default_conf, - pair='UNITTEST/BTC', datadir=testdatadir) - default_conf['timeframe'] = '1m' + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_fee", fee) + default_conf["max_open_trades"] = 10 + default_conf["runmode"] = "backtest" + backtest_conf = _make_backtest_conf( + mocker, conf=default_conf, pair="UNITTEST/BTC", datadir=testdatadir + ) + default_conf["timeframe"] = "1m" backtesting = Backtesting(default_conf) backtesting.required_startup = 0 backtesting._set_strategy(backtesting.strategylist[0]) @@ -1325,54 +1451,53 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): # 200 candles in backtest data # won't buy on first (shifted by 1) # 100 buys signals - results = result['results'] + results = result["results"] assert len(results) == 100 # Cached data should be 200 - analyzed_df = backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0] + analyzed_df = backtesting.dataprovider.get_analyzed_dataframe("UNITTEST/BTC", "1m")[0] assert len(analyzed_df) == 200 # Expect last candle to be 1 below end date (as the last candle is assumed as "incomplete" # during backtesting) - expected_last_candle_date = backtest_conf['end_date'] - timedelta(minutes=1) - assert analyzed_df.iloc[-1]['date'].to_pydatetime() == expected_last_candle_date + expected_last_candle_date = backtest_conf["end_date"] - timedelta(minutes=1) + assert analyzed_df.iloc[-1]["date"].to_pydatetime() == expected_last_candle_date # One trade was force-closed at the end - assert len(results.loc[results['is_open']]) == 0 + assert len(results.loc[results["is_open"]]) == 0 -@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC']) +@pytest.mark.parametrize("pair", ["ADA/BTC", "LTC/BTC"]) @pytest.mark.parametrize("tres", [0, 20, 30]) def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir): - def _trend_alternate_hold(dataframe=None, metadata=None): """ Buy every xth candle - sell every other xth -2 (hold on to pairs a bit) """ - if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'): + if metadata["pair"] in ("ETH/BTC", "LTC/BTC"): multi = 20 else: multi = 18 - dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0) - dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) - dataframe['enter_short'] = 0 - dataframe['exit_short'] = 0 + dataframe["enter_long"] = np.where(dataframe.index % multi == 0, 1, 0) + dataframe["exit_long"] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + dataframe["enter_short"] = 0 + dataframe["exit_short"] = 0 return dataframe - default_conf['runmode'] = 'backtest' + default_conf["runmode"] = "backtest" mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_fee", fee) patch_exchange(mocker) - pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] - data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs) + pairs = ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] + data = history.load_data(datadir=testdatadir, timeframe="5m", pairs=pairs) # Only use 500 lines to increase performance data = trim_dictlist(data, -500) # Remove data for one pair from the beginning of the data if tres > 0: data[pair] = data[pair][tres:].reset_index() - default_conf['timeframe'] = '5m' - default_conf['max_open_trades'] = 3 + default_conf["timeframe"] = "5m" + default_conf["max_open_trades"] = 3 backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1383,70 +1508,75 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) min_date, max_date = get_timerange(processed) backtest_conf = { - 'processed': deepcopy(processed), - 'start_date': min_date, - 'end_date': max_date, + "processed": deepcopy(processed), + "start_date": min_date, + "end_date": max_date, } results = backtesting.backtest(**backtest_conf) # Make sure we have parallel trades - assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0 + assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 # make sure we don't have trades with more than configured max_open_trades - assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0 + assert len(evaluate_result_multi(results["results"], "5m", 3)) == 0 # Cached data correctly removed amounts offset = 1 if tres == 0 else 0 removed_candles = len(data[pair]) - offset - assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles - assert len( - backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0] - ) == len(data['NXT/BTC']) - 1 + assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "5m")[0]) == removed_candles + assert ( + len(backtesting.dataprovider.get_analyzed_dataframe("NXT/BTC", "5m")[0]) + == len(data["NXT/BTC"]) - 1 + ) backtesting.strategy.max_open_trades = 1 - backtesting.config.update({'max_open_trades': 1}) + backtesting.config.update({"max_open_trades": 1}) backtest_conf = { - 'processed': deepcopy(processed), - 'start_date': min_date, - 'end_date': max_date, + "processed": deepcopy(processed), + "start_date": min_date, + "end_date": max_date, } results = backtesting.backtest(**backtest_conf) - assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0 + assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): - patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') - mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') - mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") + mocker.patch("freqtrade.optimize.backtesting.generate_backtest_stats") + mocker.patch("freqtrade.optimize.backtesting.show_backtest_results") + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--strategy', CURRENT_TEST_STRATEGY, - '--datadir', str(testdatadir), - '--timeframe', '1m', - '--timerange', '1510694220-1510700340', - '--enable-position-stacking', - '--disable-max-market-positions' + "backtesting", + "--config", + "config.json", + "--strategy", + CURRENT_TEST_STRATEGY, + "--datadir", + str(testdatadir), + "--timeframe", + "1m", + "--timerange", + "1510694220-1510700340", + "--enable-position-stacking", + "--disable-max-market-positions", ] args = get_args(args) start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', - 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', - 'Parameter --timerange detected: 1510694220-1510700340 ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2017-11-14 20:57:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Backtesting with data from 2017-11-14 21:17:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Parameter --enable-position-stacking detected ...' + "Parameter -i/--timeframe detected ... Using timeframe: 1m ...", + "Ignoring max_open_trades (--disable-max-market-positions was used) ...", + "Parameter --timerange detected: 1510694220-1510700340 ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2017-11-14 20:57:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Backtesting with data from 2017-11-14 21:17:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Parameter --enable-position-stacking detected ...", ] for line in exists: @@ -1455,58 +1585,70 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): @pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): - - default_conf.update({ - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - }) + default_conf.update( + { + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + } + ) patch_exchange(mocker) - backtestmock = MagicMock(return_value={ - 'results': pd.DataFrame(columns=BT_DATA_COLUMNS), - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, - }) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + backtestmock = MagicMock( + return_value={ + "results": pd.DataFrame(columns=BT_DATA_COLUMNS), + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + } + ) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock) text_table_mock = MagicMock() tag_metrics_mock = MagicMock() strattable_mock = MagicMock() strat_summary = MagicMock() - mocker.patch.multiple('freqtrade.optimize.optimize_reports.bt_output', - text_table_bt_results=text_table_mock, - text_table_strategy=strattable_mock, - ) - mocker.patch.multiple('freqtrade.optimize.optimize_reports.optimize_reports', - generate_pair_metrics=MagicMock(), - generate_tag_metrics=tag_metrics_mock, - generate_strategy_comparison=strat_summary, - generate_daily_stats=MagicMock(), - ) + mocker.patch.multiple( + "freqtrade.optimize.optimize_reports.bt_output", + text_table_bt_results=text_table_mock, + text_table_strategy=strattable_mock, + ) + mocker.patch.multiple( + "freqtrade.optimize.optimize_reports.optimize_reports", + generate_pair_metrics=MagicMock(), + generate_tag_metrics=tag_metrics_mock, + generate_strategy_comparison=strat_summary, + generate_daily_stats=MagicMock(), + ) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '1m', - '--timerange', '1510694220-1510700340', - '--enable-position-stacking', - '--disable-max-market-positions', - '--strategy-list', + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "1m", + "--timerange", + "1510694220-1510700340", + "--enable-position-stacking", + "--disable-max-market-positions", + "--strategy-list", CURRENT_TEST_STRATEGY, - 'StrategyTestV2', + "StrategyTestV2", ] args = get_args(args) start_backtesting(args) @@ -1519,17 +1661,15 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', - 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', - 'Parameter --timerange detected: 1510694220-1510700340 ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2017-11-14 20:57:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Backtesting with data from 2017-11-14 21:17:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Parameter --enable-position-stacking detected ...', - f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', - 'Running backtesting for Strategy StrategyTestV2', + "Parameter -i/--timeframe detected ... Using timeframe: 1m ...", + "Ignoring max_open_trades (--disable-max-market-positions was used) ...", + "Parameter --timerange detected: 1510694220-1510700340 ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2017-11-14 20:57:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Backtesting with data from 2017-11-14 21:17:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Parameter --enable-position-stacking detected ...", + f"Running backtesting for Strategy {CURRENT_TEST_STRATEGY}", + "Running backtesting for Strategy StrategyTestV2", ] for line in exists: @@ -1537,151 +1677,181 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys): - default_conf.update({ - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - }) - patch_exchange(mocker) - result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'], - 'profit_ratio': [0.0, 0.0], - 'profit_abs': [0.0, 0.0], - 'open_date': pd.to_datetime(['2018-01-29 18:40:00', - '2018-01-30 03:30:00', ], utc=True - ), - 'close_date': pd.to_datetime(['2018-01-29 20:45:00', - '2018-01-30 05:35:00', ], utc=True), - 'trade_duration': [235, 40], - 'is_open': [False, False], - 'stake_amount': [0.01, 0.01], - 'open_rate': [0.104445, 0.10302485], - 'close_rate': [0.104969, 0.103541], - "is_short": [False, False], - - 'exit_reason': [ExitType.ROI, ExitType.ROI] - }) - result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], - 'profit_ratio': [0.03, 0.01, 0.1], - 'profit_abs': [0.01, 0.02, 0.2], - 'open_date': pd.to_datetime(['2018-01-29 18:40:00', - '2018-01-30 03:30:00', - '2018-01-30 05:30:00'], utc=True - ), - 'close_date': pd.to_datetime(['2018-01-29 20:45:00', - '2018-01-30 05:35:00', - '2018-01-30 08:30:00'], utc=True), - 'trade_duration': [47, 40, 20], - 'is_open': [False, False, False], - 'stake_amount': [0.01, 0.01, 0.01], - 'open_rate': [0.104445, 0.10302485, 0.122541], - 'close_rate': [0.104969, 0.103541, 0.123541], - "is_short": [False, False, False], - 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] - }) - backtestmock = MagicMock(side_effect=[ + default_conf.update( { - 'results': result1, - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, - }, - { - 'results': result2, - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, } - ]) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + ) + patch_exchange(mocker) + result1 = pd.DataFrame( + { + "pair": ["XRP/BTC", "LTC/BTC"], + "profit_ratio": [0.0, 0.0], + "profit_abs": [0.0, 0.0], + "open_date": pd.to_datetime( + [ + "2018-01-29 18:40:00", + "2018-01-30 03:30:00", + ], + utc=True, + ), + "close_date": pd.to_datetime( + [ + "2018-01-29 20:45:00", + "2018-01-30 05:35:00", + ], + utc=True, + ), + "trade_duration": [235, 40], + "is_open": [False, False], + "stake_amount": [0.01, 0.01], + "open_rate": [0.104445, 0.10302485], + "close_rate": [0.104969, 0.103541], + "is_short": [False, False], + "exit_reason": [ExitType.ROI, ExitType.ROI], + } + ) + result2 = pd.DataFrame( + { + "pair": ["XRP/BTC", "LTC/BTC", "ETH/BTC"], + "profit_ratio": [0.03, 0.01, 0.1], + "profit_abs": [0.01, 0.02, 0.2], + "open_date": pd.to_datetime( + ["2018-01-29 18:40:00", "2018-01-30 03:30:00", "2018-01-30 05:30:00"], utc=True + ), + "close_date": pd.to_datetime( + ["2018-01-29 20:45:00", "2018-01-30 05:35:00", "2018-01-30 08:30:00"], utc=True + ), + "trade_duration": [47, 40, 20], + "is_open": [False, False, False], + "stake_amount": [0.01, 0.01, 0.01], + "open_rate": [0.104445, 0.10302485, 0.122541], + "close_rate": [0.104969, 0.103541, 0.123541], + "is_short": [False, False, False], + "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], + } + ) + backtestmock = MagicMock( + side_effect=[ + { + "results": result1, + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + { + "results": result2, + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + ] + ) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '1m', - '--timerange', '1510694220-1510700340', - '--enable-position-stacking', - '--disable-max-market-positions', - '--breakdown', 'day', - '--strategy-list', + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "1m", + "--timerange", + "1510694220-1510700340", + "--enable-position-stacking", + "--disable-max-market-positions", + "--breakdown", + "day", + "--strategy-list", CURRENT_TEST_STRATEGY, - 'StrategyTestV2', + "StrategyTestV2", ] args = get_args(args) start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', - 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', - 'Parameter --timerange detected: 1510694220-1510700340 ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2017-11-14 20:57:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Backtesting with data from 2017-11-14 21:17:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Parameter --enable-position-stacking detected ...', - f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', - 'Running backtesting for Strategy StrategyTestV2', + "Parameter -i/--timeframe detected ... Using timeframe: 1m ...", + "Ignoring max_open_trades (--disable-max-market-positions was used) ...", + "Parameter --timerange detected: 1510694220-1510700340 ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2017-11-14 20:57:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Backtesting with data from 2017-11-14 21:17:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Parameter --enable-position-stacking detected ...", + f"Running backtesting for Strategy {CURRENT_TEST_STRATEGY}", + "Running backtesting for Strategy StrategyTestV2", ] for line in exists: assert log_has(line, caplog) captured = capsys.readouterr() - assert 'BACKTESTING REPORT' in captured.out - assert 'EXIT REASON STATS' in captured.out - assert 'DAY BREAKDOWN' in captured.out - assert 'LEFT OPEN TRADES REPORT' in captured.out - assert '2017-11-14 21:17:00 -> 2017-11-14 22:59:00 | Max open trades : 1' in captured.out - assert 'STRATEGY SUMMARY' in captured.out + assert "BACKTESTING REPORT" in captured.out + assert "EXIT REASON STATS" in captured.out + assert "DAY BREAKDOWN" in captured.out + assert "LEFT OPEN TRADES REPORT" in captured.out + assert "2017-11-14 21:17:00 -> 2017-11-14 22:59:00 | Max open trades : 1" in captured.out + assert "STRATEGY SUMMARY" in captured.out @pytest.mark.filterwarnings("ignore:deprecated") -def test_backtest_start_futures_noliq(default_conf_usdt, mocker, - caplog, testdatadir, capsys): +def test_backtest_start_futures_noliq(default_conf_usdt, mocker, caplog, testdatadir, capsys): # Tests detail-data loading - default_conf_usdt.update({ - "trading_mode": "futures", - "margin_mode": "isolated", - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - "strategy": CURRENT_TEST_STRATEGY, - }) + default_conf_usdt.update( + { + "trading_mode": "futures", + "margin_mode": "isolated", + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + "strategy": CURRENT_TEST_STRATEGY, + } + ) patch_exchange(mocker) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT:USDT'])) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["HULUMULU/USDT", "XRP/USDT:USDT"]), + ) # mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) patched_configuration_load_config_file(mocker, default_conf_usdt) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '1h', + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "1h", ] args = get_args(args) with pytest.raises(OperationalException, match=r"Pairs .* got no leverage tiers available\."): @@ -1689,343 +1859,410 @@ def test_backtest_start_futures_noliq(default_conf_usdt, mocker, @pytest.mark.filterwarnings("ignore:deprecated") -def test_backtest_start_nomock_futures(default_conf_usdt, mocker, - caplog, testdatadir, capsys): +def test_backtest_start_nomock_futures(default_conf_usdt, mocker, caplog, testdatadir, capsys): # Tests detail-data loading - default_conf_usdt.update({ - "trading_mode": "futures", - "margin_mode": "isolated", - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - "strategy": CURRENT_TEST_STRATEGY, - }) - patch_exchange(mocker) - result1 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT'], - 'profit_ratio': [0.0, 0.0], - 'profit_abs': [0.0, 0.0], - 'open_date': pd.to_datetime(['2021-11-18 18:00:00', - '2021-11-18 03:00:00', ], utc=True - ), - 'close_date': pd.to_datetime(['2021-11-18 20:00:00', - '2021-11-18 05:00:00', ], utc=True), - 'trade_duration': [235, 40], - 'is_open': [False, False], - 'is_short': [False, False], - 'stake_amount': [0.01, 0.01], - 'open_rate': [0.104445, 0.10302485], - 'close_rate': [0.104969, 0.103541], - 'exit_reason': [ExitType.ROI, ExitType.ROI] - }) - result2 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT', 'XRP/USDT:USDT'], - 'profit_ratio': [0.03, 0.01, 0.1], - 'profit_abs': [0.01, 0.02, 0.2], - 'open_date': pd.to_datetime(['2021-11-19 18:00:00', - '2021-11-19 03:00:00', - '2021-11-19 05:00:00'], utc=True - ), - 'close_date': pd.to_datetime(['2021-11-19 20:00:00', - '2021-11-19 05:00:00', - '2021-11-19 08:00:00'], utc=True), - 'trade_duration': [47, 40, 20], - 'is_open': [False, False, False], - 'is_short': [False, False, False], - 'stake_amount': [0.01, 0.01, 0.01], - 'open_rate': [0.104445, 0.10302485, 0.122541], - 'close_rate': [0.104969, 0.103541, 0.123541], - 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] - }) - backtestmock = MagicMock(side_effect=[ + default_conf_usdt.update( { - 'results': result1, - 'config': default_conf_usdt, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, - }, - { - 'results': result2, - 'config': default_conf_usdt, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, + "trading_mode": "futures", + "margin_mode": "isolated", + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + "strategy": CURRENT_TEST_STRATEGY, } - ]) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + ) + patch_exchange(mocker) + result1 = pd.DataFrame( + { + "pair": ["XRP/USDT:USDT", "XRP/USDT:USDT"], + "profit_ratio": [0.0, 0.0], + "profit_abs": [0.0, 0.0], + "open_date": pd.to_datetime( + [ + "2021-11-18 18:00:00", + "2021-11-18 03:00:00", + ], + utc=True, + ), + "close_date": pd.to_datetime( + [ + "2021-11-18 20:00:00", + "2021-11-18 05:00:00", + ], + utc=True, + ), + "trade_duration": [235, 40], + "is_open": [False, False], + "is_short": [False, False], + "stake_amount": [0.01, 0.01], + "open_rate": [0.104445, 0.10302485], + "close_rate": [0.104969, 0.103541], + "exit_reason": [ExitType.ROI, ExitType.ROI], + } + ) + result2 = pd.DataFrame( + { + "pair": ["XRP/USDT:USDT", "XRP/USDT:USDT", "XRP/USDT:USDT"], + "profit_ratio": [0.03, 0.01, 0.1], + "profit_abs": [0.01, 0.02, 0.2], + "open_date": pd.to_datetime( + ["2021-11-19 18:00:00", "2021-11-19 03:00:00", "2021-11-19 05:00:00"], utc=True + ), + "close_date": pd.to_datetime( + ["2021-11-19 20:00:00", "2021-11-19 05:00:00", "2021-11-19 08:00:00"], utc=True + ), + "trade_duration": [47, 40, 20], + "is_open": [False, False, False], + "is_short": [False, False, False], + "stake_amount": [0.01, 0.01, 0.01], + "open_rate": [0.104445, 0.10302485, 0.122541], + "close_rate": [0.104969, 0.103541, 0.123541], + "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], + } + ) + backtestmock = MagicMock( + side_effect=[ + { + "results": result1, + "config": default_conf_usdt, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + { + "results": result2, + "config": default_conf_usdt, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + ] + ) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["XRP/USDT:USDT"]), + ) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock) patched_configuration_load_config_file(mocker, default_conf_usdt) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '1h', + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "1h", ] args = get_args(args) start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 1h ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2021-11-17 01:00:00 ' - 'up to 2021-11-21 04:00:00 (4 days).', - 'Backtesting with data from 2021-11-17 21:00:00 ' - 'up to 2021-11-21 04:00:00 (3 days).', - 'XRP/USDT:USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00', - 'XRP/USDT:USDT, mark, 8h, data starts at 2021-11-18 00:00:00', - f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', + "Parameter -i/--timeframe detected ... Using timeframe: 1h ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2021-11-17 01:00:00 " "up to 2021-11-21 04:00:00 (4 days).", + "Backtesting with data from 2021-11-17 21:00:00 " "up to 2021-11-21 04:00:00 (3 days).", + "XRP/USDT:USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00", + "XRP/USDT:USDT, mark, 8h, data starts at 2021-11-18 00:00:00", + f"Running backtesting for Strategy {CURRENT_TEST_STRATEGY}", ] for line in exists: assert log_has(line, caplog) captured = capsys.readouterr() - assert 'BACKTESTING REPORT' in captured.out - assert 'EXIT REASON STATS' in captured.out - assert 'LEFT OPEN TRADES REPORT' in captured.out + assert "BACKTESTING REPORT" in captured.out + assert "EXIT REASON STATS" in captured.out + assert "LEFT OPEN TRADES REPORT" in captured.out @pytest.mark.filterwarnings("ignore:deprecated") -def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, - caplog, testdatadir, capsys): +def test_backtest_start_multi_strat_nomock_detail( + default_conf, mocker, caplog, testdatadir, capsys +): # Tests detail-data loading - default_conf.update({ - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - }) - patch_exchange(mocker) - result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'], - 'profit_ratio': [0.0, 0.0], - 'profit_abs': [0.0, 0.0], - 'open_date': pd.to_datetime(['2018-01-29 18:40:00', - '2018-01-30 03:30:00', ], utc=True - ), - 'close_date': pd.to_datetime(['2018-01-29 20:45:00', - '2018-01-30 05:35:00', ], utc=True), - 'trade_duration': [235, 40], - 'is_open': [False, False], - 'is_short': [False, False], - 'stake_amount': [0.01, 0.01], - 'open_rate': [0.104445, 0.10302485], - 'close_rate': [0.104969, 0.103541], - 'exit_reason': [ExitType.ROI, ExitType.ROI] - }) - result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], - 'profit_ratio': [0.03, 0.01, 0.1], - 'profit_abs': [0.01, 0.02, 0.2], - 'open_date': pd.to_datetime(['2018-01-29 18:40:00', - '2018-01-30 03:30:00', - '2018-01-30 05:30:00'], utc=True - ), - 'close_date': pd.to_datetime(['2018-01-29 20:45:00', - '2018-01-30 05:35:00', - '2018-01-30 08:30:00'], utc=True), - 'trade_duration': [47, 40, 20], - 'is_open': [False, False, False], - 'is_short': [False, False, False], - 'stake_amount': [0.01, 0.01, 0.01], - 'open_rate': [0.104445, 0.10302485, 0.122541], - 'close_rate': [0.104969, 0.103541, 0.123541], - 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] - }) - backtestmock = MagicMock(side_effect=[ + default_conf.update( { - 'results': result1, - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, - }, - { - 'results': result2, - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, } - ]) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['XRP/ETH'])) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + ) + patch_exchange(mocker) + result1 = pd.DataFrame( + { + "pair": ["XRP/BTC", "LTC/BTC"], + "profit_ratio": [0.0, 0.0], + "profit_abs": [0.0, 0.0], + "open_date": pd.to_datetime( + [ + "2018-01-29 18:40:00", + "2018-01-30 03:30:00", + ], + utc=True, + ), + "close_date": pd.to_datetime( + [ + "2018-01-29 20:45:00", + "2018-01-30 05:35:00", + ], + utc=True, + ), + "trade_duration": [235, 40], + "is_open": [False, False], + "is_short": [False, False], + "stake_amount": [0.01, 0.01], + "open_rate": [0.104445, 0.10302485], + "close_rate": [0.104969, 0.103541], + "exit_reason": [ExitType.ROI, ExitType.ROI], + } + ) + result2 = pd.DataFrame( + { + "pair": ["XRP/BTC", "LTC/BTC", "ETH/BTC"], + "profit_ratio": [0.03, 0.01, 0.1], + "profit_abs": [0.01, 0.02, 0.2], + "open_date": pd.to_datetime( + ["2018-01-29 18:40:00", "2018-01-30 03:30:00", "2018-01-30 05:30:00"], utc=True + ), + "close_date": pd.to_datetime( + ["2018-01-29 20:45:00", "2018-01-30 05:35:00", "2018-01-30 08:30:00"], utc=True + ), + "trade_duration": [47, 40, 20], + "is_open": [False, False, False], + "is_short": [False, False, False], + "stake_amount": [0.01, 0.01, 0.01], + "open_rate": [0.104445, 0.10302485, 0.122541], + "close_rate": [0.104969, 0.103541, 0.123541], + "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], + } + ) + backtestmock = MagicMock( + side_effect=[ + { + "results": result1, + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + { + "results": result2, + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + }, + ] + ) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["XRP/ETH"]), + ) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '5m', - '--timeframe-detail', '1m', - '--strategy-list', - CURRENT_TEST_STRATEGY + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "5m", + "--timeframe-detail", + "1m", + "--strategy-list", + CURRENT_TEST_STRATEGY, ] args = get_args(args) start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 5m ...', - 'Parameter --timeframe-detail detected, using 1m for intra-candle backtesting ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2019-10-11 00:00:00 ' - 'up to 2019-10-13 11:15:00 (2 days).', - 'Backtesting with data from 2019-10-11 01:40:00 ' - 'up to 2019-10-13 11:15:00 (2 days).', - f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', + "Parameter -i/--timeframe detected ... Using timeframe: 5m ...", + "Parameter --timeframe-detail detected, using 1m for intra-candle backtesting ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2019-10-11 00:00:00 " "up to 2019-10-13 11:15:00 (2 days).", + "Backtesting with data from 2019-10-11 01:40:00 " "up to 2019-10-13 11:15:00 (2 days).", + f"Running backtesting for Strategy {CURRENT_TEST_STRATEGY}", ] for line in exists: assert log_has(line, caplog) captured = capsys.readouterr() - assert 'BACKTESTING REPORT' in captured.out - assert 'EXIT REASON STATS' in captured.out - assert 'LEFT OPEN TRADES REPORT' in captured.out + assert "BACKTESTING REPORT" in captured.out + assert "EXIT REASON STATS" in captured.out + assert "LEFT OPEN TRADES REPORT" in captured.out @pytest.mark.filterwarnings("ignore:deprecated") -@pytest.mark.parametrize('run_id', ['2', 'changed']) -@pytest.mark.parametrize('start_delta', [{'days': 0}, {'days': 1}, {'weeks': 1}, {'weeks': 4}]) -@pytest.mark.parametrize('cache', constants.BACKTEST_CACHE_AGE) -def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testdatadir, run_id, - start_delta, cache): - default_conf.update({ - "use_exit_signal": True, - "exit_profit_only": False, - "exit_profit_offset": 0.0, - "ignore_roi_if_entry_signal": False, - }) +@pytest.mark.parametrize("run_id", ["2", "changed"]) +@pytest.mark.parametrize("start_delta", [{"days": 0}, {"days": 1}, {"weeks": 1}, {"weeks": 4}]) +@pytest.mark.parametrize("cache", constants.BACKTEST_CACHE_AGE) +def test_backtest_start_multi_strat_caching( + default_conf, mocker, caplog, testdatadir, run_id, start_delta, cache +): + default_conf.update( + { + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + } + ) patch_exchange(mocker) - backtestmock = MagicMock(return_value={ - 'results': pd.DataFrame(columns=BT_DATA_COLUMNS), - 'config': default_conf, - 'locks': [], - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'final_balance': 1000, - }) - mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', - PropertyMock(return_value=['UNITTEST/BTC'])) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) - mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock()) + backtestmock = MagicMock( + return_value={ + "results": pd.DataFrame(columns=BT_DATA_COLUMNS), + "config": default_conf, + "locks": [], + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "final_balance": 1000, + } + ) + mocker.patch( + "freqtrade.plugins.pairlistmanager.PairListManager.whitelist", + PropertyMock(return_value=["UNITTEST/BTC"]), + ) + mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock) + mocker.patch("freqtrade.optimize.backtesting.show_backtest_results", MagicMock()) now = min_backtest_date = datetime.now(tz=timezone.utc) start_time = now - timedelta(**start_delta) + timedelta(hours=1) - if cache == 'none': + if cache == "none": min_backtest_date = now + timedelta(days=1) - elif cache == 'day': + elif cache == "day": min_backtest_date = now - timedelta(days=1) - elif cache == 'week': + elif cache == "week": min_backtest_date = now - timedelta(weeks=1) - elif cache == 'month': + elif cache == "month": min_backtest_date = now - timedelta(weeks=4) - load_backtest_metadata = MagicMock(return_value={ - 'StrategyTestV2': {'run_id': '1', 'backtest_start_time': now.timestamp()}, - 'StrategyTestV3': {'run_id': run_id, 'backtest_start_time': start_time.timestamp()} - }) - load_backtest_stats = MagicMock(side_effect=[ - { - 'metadata': {'StrategyTestV2': {'run_id': '1'}}, - 'strategy': {'StrategyTestV2': {}}, - 'strategy_comparison': [{'key': 'StrategyTestV2'}] - }, - { - 'metadata': {'StrategyTestV3': {'run_id': '2'}}, - 'strategy': {'StrategyTestV3': {}}, - 'strategy_comparison': [{'key': 'StrategyTestV3'}] + load_backtest_metadata = MagicMock( + return_value={ + "StrategyTestV2": {"run_id": "1", "backtest_start_time": now.timestamp()}, + "StrategyTestV3": {"run_id": run_id, "backtest_start_time": start_time.timestamp()}, } - ]) - mocker.patch('pathlib.Path.glob', return_value=[ - Path(datetime.strftime(datetime.now(), 'backtest-result-%Y-%m-%d_%H-%M-%S.json'))]) - mocker.patch.multiple('freqtrade.data.btanalysis', - load_backtest_metadata=load_backtest_metadata, - load_backtest_stats=load_backtest_stats) - mocker.patch('freqtrade.optimize.backtesting.get_strategy_run_id', side_effect=['1', '2', '2']) + ) + load_backtest_stats = MagicMock( + side_effect=[ + { + "metadata": {"StrategyTestV2": {"run_id": "1"}}, + "strategy": {"StrategyTestV2": {}}, + "strategy_comparison": [{"key": "StrategyTestV2"}], + }, + { + "metadata": {"StrategyTestV3": {"run_id": "2"}}, + "strategy": {"StrategyTestV3": {}}, + "strategy_comparison": [{"key": "StrategyTestV3"}], + }, + ] + ) + mocker.patch( + "pathlib.Path.glob", + return_value=[ + Path(datetime.strftime(datetime.now(), "backtest-result-%Y-%m-%d_%H-%M-%S.json")) + ], + ) + mocker.patch.multiple( + "freqtrade.data.btanalysis", + load_backtest_metadata=load_backtest_metadata, + load_backtest_stats=load_backtest_stats, + ) + mocker.patch("freqtrade.optimize.backtesting.get_strategy_run_id", side_effect=["1", "2", "2"]) patched_configuration_load_config_file(mocker, default_conf) args = [ - 'backtesting', - '--config', 'config.json', - '--datadir', str(testdatadir), - '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--timeframe', '1m', - '--timerange', '1510694220-1510700340', - '--enable-position-stacking', - '--disable-max-market-positions', - '--cache', cache, - '--strategy-list', - 'StrategyTestV2', - 'StrategyTestV3', + "backtesting", + "--config", + "config.json", + "--datadir", + str(testdatadir), + "--strategy-path", + str(Path(__file__).parents[1] / "strategy/strats"), + "--timeframe", + "1m", + "--timerange", + "1510694220-1510700340", + "--enable-position-stacking", + "--disable-max-market-positions", + "--cache", + cache, + "--strategy-list", + "StrategyTestV2", + "StrategyTestV3", ] args = get_args(args) start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', - 'Parameter --timerange detected: 1510694220-1510700340 ...', - f'Using data directory: {testdatadir} ...', - 'Loading data from 2017-11-14 20:57:00 ' - 'up to 2017-11-14 22:59:00 (0 days).', - 'Parameter --enable-position-stacking detected ...', + "Parameter -i/--timeframe detected ... Using timeframe: 1m ...", + "Parameter --timerange detected: 1510694220-1510700340 ...", + f"Using data directory: {testdatadir} ...", + "Loading data from 2017-11-14 20:57:00 " "up to 2017-11-14 22:59:00 (0 days).", + "Parameter --enable-position-stacking detected ...", ] for line in exists: assert log_has(line, caplog) - if cache == 'none': + if cache == "none": assert backtestmock.call_count == 2 exists = [ - 'Running backtesting for Strategy StrategyTestV2', - 'Running backtesting for Strategy StrategyTestV3', - 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', - 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).', + "Running backtesting for Strategy StrategyTestV2", + "Running backtesting for Strategy StrategyTestV3", + "Ignoring max_open_trades (--disable-max-market-positions was used) ...", + "Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).", ] - elif run_id == '2' and min_backtest_date < start_time: + elif run_id == "2" and min_backtest_date < start_time: assert backtestmock.call_count == 0 exists = [ - 'Reusing result of previous backtest for StrategyTestV2', - 'Reusing result of previous backtest for StrategyTestV3', + "Reusing result of previous backtest for StrategyTestV2", + "Reusing result of previous backtest for StrategyTestV3", ] else: exists = [ - 'Reusing result of previous backtest for StrategyTestV2', - 'Running backtesting for Strategy StrategyTestV3', - 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', - 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).', + "Reusing result of previous backtest for StrategyTestV2", + "Running backtesting for Strategy StrategyTestV3", + "Ignoring max_open_trades (--disable-max-market-positions was used) ...", + "Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).", ] assert backtestmock.call_count == 1 @@ -2034,10 +2271,7 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda def test_get_strategy_run_id(default_conf_usdt): - default_conf_usdt.update({ - 'strategy': 'StrategyTestV2', - 'max_open_trades': float('inf') - }) + default_conf_usdt.update({"strategy": "StrategyTestV2", "max_open_trades": float("inf")}) strategy = StrategyResolver.load_strategy(default_conf_usdt) x = get_strategy_run_id(strategy) assert isinstance(x, str) @@ -2045,36 +2279,36 @@ def test_get_strategy_run_id(default_conf_usdt): def test_get_backtest_metadata_filename(): # Test with a file path - filename = Path('backtest_results.json') - expected = Path('backtest_results.meta.json') + filename = Path("backtest_results.json") + expected = Path("backtest_results.meta.json") assert get_backtest_metadata_filename(filename) == expected # Test with a file path with multiple dots in the name - filename = Path('/path/to/backtest.results.json') - expected = Path('/path/to/backtest.results.meta.json') + filename = Path("/path/to/backtest.results.json") + expected = Path("/path/to/backtest.results.meta.json") assert get_backtest_metadata_filename(filename) == expected # Test with a file path with no parent directory - filename = Path('backtest_results.json') - expected = Path('backtest_results.meta.json') + filename = Path("backtest_results.json") + expected = Path("backtest_results.meta.json") assert get_backtest_metadata_filename(filename) == expected # Test with a string file path - filename = '/path/to/backtest_results.json' - expected = Path('/path/to/backtest_results.meta.json') + filename = "/path/to/backtest_results.json" + expected = Path("/path/to/backtest_results.meta.json") assert get_backtest_metadata_filename(filename) == expected # Test with a string file path with no extension - filename = '/path/to/backtest_results' - expected = Path('/path/to/backtest_results.meta') + filename = "/path/to/backtest_results" + expected = Path("/path/to/backtest_results.meta") assert get_backtest_metadata_filename(filename) == expected # Test with a string file path with multiple dots in the name - filename = '/path/to/backtest.results.json' - expected = Path('/path/to/backtest.results.meta.json') + filename = "/path/to/backtest.results.json" + expected = Path("/path/to/backtest.results.meta.json") assert get_backtest_metadata_filename(filename) == expected # Test with a string file path with no parent directory - filename = 'backtest_results.json' - expected = Path('backtest_results.meta.json') + filename = "backtest_results.json" + expected = Path("backtest_results.meta.json") assert get_backtest_metadata_filename(filename) == expected diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 504a743b0..a30761486 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -22,13 +22,13 @@ from tests.conftest import ( spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES -@pytest.mark.parametrize('is_short', [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_enter_exit_side(fee, is_short): entry_side, exit_side = ("sell", "buy") if is_short else ("buy", "sell") trade = Trade( id=2, - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=0.001, open_rate=0.01, amount=5, @@ -36,21 +36,21 @@ def test_enter_exit_side(fee, is_short): open_date=dt_now(), fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", is_short=is_short, leverage=2.0, - trading_mode=margin + trading_mode=margin, ) assert trade.entry_side == entry_side assert trade.exit_side == exit_side - assert trade.trade_direction == 'short' if is_short else 'long' + assert trade.trade_direction == "short" if is_short else "long" @pytest.mark.usefixtures("init_persistence") def test_set_stop_loss_liquidation(fee): trade = Trade( id=2, - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, @@ -58,10 +58,10 @@ def test_set_stop_loss_liquidation(fee): open_date=dt_now(), fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", is_short=False, leverage=2.0, - trading_mode=margin + trading_mode=margin, ) trade.set_liquidation_price(0.09) assert trade.liquidation_price == 0.09 @@ -173,95 +173,94 @@ def test_set_stop_loss_liquidation(fee): assert trade.stoploss_or_liquidation == 1.5 -@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [ - ("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8), margin), - ("binance", True, 3, 10, 0.0005, 0.000625, margin), - ("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8), margin), - ("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8), margin), - ("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8), margin), - ("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8), margin), - ("binance", False, 5, 295, 0.0005, 0.005, margin), - ("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8), margin), - ("binance", False, 1, 295, 0.0005, 0.0, spot), - ("binance", True, 1, 295, 0.0005, 0.003125, margin), - - ("binance", False, 3, 10, 0.0005, 0.0, futures), - ("binance", True, 3, 295, 0.0005, 0.0, futures), - ("binance", False, 5, 295, 0.0005, 0.0, futures), - ("binance", True, 5, 295, 0.0005, 0.0, futures), - ("binance", False, 1, 295, 0.0005, 0.0, futures), - ("binance", True, 1, 295, 0.0005, 0.0, futures), - - ("kraken", False, 3, 10, 0.0005, 0.040, margin), - ("kraken", True, 3, 10, 0.0005, 0.030, margin), - ("kraken", False, 3, 295, 0.0005, 0.06, margin), - ("kraken", True, 3, 295, 0.0005, 0.045, margin), - ("kraken", False, 3, 295, 0.00025, 0.03, margin), - ("kraken", True, 3, 295, 0.00025, 0.0225, margin), - ("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8), margin), - ("kraken", True, 5, 295, 0.0005, 0.045, margin), - ("kraken", False, 1, 295, 0.0005, 0.0, spot), - ("kraken", True, 1, 295, 0.0005, 0.045, margin), - -]) +@pytest.mark.parametrize( + "exchange,is_short,lev,minutes,rate,interest,trading_mode", + [ + ("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8), margin), + ("binance", True, 3, 10, 0.0005, 0.000625, margin), + ("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8), margin), + ("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8), margin), + ("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8), margin), + ("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8), margin), + ("binance", False, 5, 295, 0.0005, 0.005, margin), + ("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8), margin), + ("binance", False, 1, 295, 0.0005, 0.0, spot), + ("binance", True, 1, 295, 0.0005, 0.003125, margin), + ("binance", False, 3, 10, 0.0005, 0.0, futures), + ("binance", True, 3, 295, 0.0005, 0.0, futures), + ("binance", False, 5, 295, 0.0005, 0.0, futures), + ("binance", True, 5, 295, 0.0005, 0.0, futures), + ("binance", False, 1, 295, 0.0005, 0.0, futures), + ("binance", True, 1, 295, 0.0005, 0.0, futures), + ("kraken", False, 3, 10, 0.0005, 0.040, margin), + ("kraken", True, 3, 10, 0.0005, 0.030, margin), + ("kraken", False, 3, 295, 0.0005, 0.06, margin), + ("kraken", True, 3, 295, 0.0005, 0.045, margin), + ("kraken", False, 3, 295, 0.00025, 0.03, margin), + ("kraken", True, 3, 295, 0.00025, 0.0225, margin), + ("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8), margin), + ("kraken", True, 5, 295, 0.0005, 0.045, margin), + ("kraken", False, 1, 295, 0.0005, 0.0, spot), + ("kraken", True, 1, 295, 0.0005, 0.045, margin), + ], +) @pytest.mark.usefixtures("init_persistence") -def test_interest(fee, exchange, is_short, lev, minutes, rate, interest, - trading_mode): +def test_interest(fee, exchange, is_short, lev, minutes, rate, interest, trading_mode): """ - 10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage - fee: 0.25 % quote - interest_rate: 0.05 % per 4 hrs - open_rate: 2.00 quote - close_rate: 2.20 quote - amount: = 30.0 crypto - stake_amount - 3x, -3x: 20.0 quote - 5x, -5x: 12.0 quote - borrowed - 10min - 3x: 40 quote - -3x: 30 crypto - 5x: 48 quote - -5x: 30 crypto - 1x: 0 - -1x: 30 crypto - hours: 1/6 (10 minutes) - time-periods: - 10min - kraken: (1 + 1) 4hr_periods = 2 4hr_periods - binance: 1/24 24hr_periods - 4.95hr - kraken: ceil(1 + 4.95/4) 4hr_periods = 3 4hr_periods - binance: ceil(4.95)/24 24hr_periods = 5/24 24hr_periods - interest: borrowed * interest_rate * time-periods - 10min - binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote - kraken 3x: 40 * 0.0005 * 2 = 0.040 quote - binace -3x: 30 * 0.0005 * 1/24 = 0.000625 crypto - kraken -3x: 30 * 0.0005 * 2 = 0.030 crypto - 5hr - binance 3x: 40 * 0.0005 * 5/24 = 0.004166666666666667 quote - kraken 3x: 40 * 0.0005 * 3 = 0.06 quote - binace -3x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto - kraken -3x: 30 * 0.0005 * 3 = 0.045 crypto - 0.00025 interest - binance 3x: 40 * 0.00025 * 5/24 = 0.0020833333333333333 quote - kraken 3x: 40 * 0.00025 * 3 = 0.03 quote - binace -3x: 30 * 0.00025 * 5/24 = 0.0015624999999999999 crypto - kraken -3x: 30 * 0.00025 * 3 = 0.0225 crypto - 5x leverage, 0.0005 interest, 5hr - binance 5x: 48 * 0.0005 * 5/24 = 0.005 quote - kraken 5x: 48 * 0.0005 * 3 = 0.07200000000000001 quote - binace -5x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto - kraken -5x: 30 * 0.0005 * 3 = 0.045 crypto - 1x leverage, 0.0005 interest, 5hr - binance,kraken 1x: 0.0 quote - binace -1x: 30 * 0.0005 * 5/24 = 0.003125 crypto - kraken -1x: 30 * 0.0005 * 3 = 0.045 crypto + 10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage + fee: 0.25 % quote + interest_rate: 0.05 % per 4 hrs + open_rate: 2.00 quote + close_rate: 2.20 quote + amount: = 30.0 crypto + stake_amount + 3x, -3x: 20.0 quote + 5x, -5x: 12.0 quote + borrowed + 10min + 3x: 40 quote + -3x: 30 crypto + 5x: 48 quote + -5x: 30 crypto + 1x: 0 + -1x: 30 crypto + hours: 1/6 (10 minutes) + time-periods: + 10min + kraken: (1 + 1) 4hr_periods = 2 4hr_periods + binance: 1/24 24hr_periods + 4.95hr + kraken: ceil(1 + 4.95/4) 4hr_periods = 3 4hr_periods + binance: ceil(4.95)/24 24hr_periods = 5/24 24hr_periods + interest: borrowed * interest_rate * time-periods + 10min + binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote + kraken 3x: 40 * 0.0005 * 2 = 0.040 quote + binace -3x: 30 * 0.0005 * 1/24 = 0.000625 crypto + kraken -3x: 30 * 0.0005 * 2 = 0.030 crypto + 5hr + binance 3x: 40 * 0.0005 * 5/24 = 0.004166666666666667 quote + kraken 3x: 40 * 0.0005 * 3 = 0.06 quote + binace -3x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto + kraken -3x: 30 * 0.0005 * 3 = 0.045 crypto + 0.00025 interest + binance 3x: 40 * 0.00025 * 5/24 = 0.0020833333333333333 quote + kraken 3x: 40 * 0.00025 * 3 = 0.03 quote + binace -3x: 30 * 0.00025 * 5/24 = 0.0015624999999999999 crypto + kraken -3x: 30 * 0.00025 * 3 = 0.0225 crypto + 5x leverage, 0.0005 interest, 5hr + binance 5x: 48 * 0.0005 * 5/24 = 0.005 quote + kraken 5x: 48 * 0.0005 * 3 = 0.07200000000000001 quote + binace -5x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto + kraken -5x: 30 * 0.0005 * 3 = 0.045 crypto + 1x leverage, 0.0005 interest, 5hr + binance,kraken 1x: 0.0 quote + binace -1x: 30 * 0.0005 * 5/24 = 0.003125 crypto + kraken -1x: 30 * 0.0005 * 3 = 0.045 crypto """ trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=20.0, amount=30.0, open_rate=2.0, @@ -272,85 +271,88 @@ def test_interest(fee, exchange, is_short, lev, minutes, rate, interest, leverage=lev, interest_rate=rate, is_short=is_short, - trading_mode=trading_mode + trading_mode=trading_mode, ) assert round(float(trade.calculate_interest()), 8) == interest -@pytest.mark.parametrize('is_short,lev,borrowed,trading_mode', [ - (False, 1.0, 0.0, spot), - (True, 1.0, 30.0, margin), - (False, 3.0, 40.0, margin), - (True, 3.0, 30.0, margin), -]) +@pytest.mark.parametrize( + "is_short,lev,borrowed,trading_mode", + [ + (False, 1.0, 0.0, spot), + (True, 1.0, 30.0, margin), + (False, 3.0, 40.0, margin), + (True, 3.0, 30.0, margin), + ], +) @pytest.mark.usefixtures("init_persistence") def test_borrowed(fee, is_short, lev, borrowed, trading_mode): """ - 10 minute limit trade on Binance/Kraken at 1x, 3x leverage - fee: 0.25% quote - interest_rate: 0.05% per 4 hrs - open_rate: 2.00 quote - close_rate: 2.20 quote - amount: = 30.0 crypto - stake_amount - 1x,-1x: 60.0 quote - 3x,-3x: 20.0 quote - borrowed - 1x: 0 quote - 3x: 40 quote - -1x: 30 crypto - -3x: 30 crypto - hours: 1/6 (10 minutes) - time-periods: - kraken: (1 + 1) 4hr_periods = 2 4hr_periods - binance: 1/24 24hr_periods - interest: borrowed * interest_rate * time-periods - 1x : / - binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote - kraken 3x: 40 * 0.0005 * 2 = 0.040 quote - binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto - kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto - open_value: (amount * open_rate) ± (amount * open_rate * fee) - 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote - amount_closed: - 1x, 3x : amount - -1x, -3x : amount + interest - binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto - kraken -1x,-3x: 30 + 0.03 = 30.03 crypto - close_value: - 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest - -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) - binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 - binance 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 - kraken 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 - binance -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001 - kraken -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 - total_profit: - 1x, 3x : close_value - open_value - -1x,-3x: open_value - close_value - binance,kraken 1x: 65.835 - 60.15 = 5.685 - binance 3x: 65.83416667 - 60.15 = 5.684166670000003 - kraken 3x: 65.795 - 60.15 = 5.645 - binance -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013 - kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 - total_profit_ratio: - 1x, 3x : ((close_value/open_value) - 1) * leverage - -1x,-3x: (1 - (close_value/open_value)) * leverage - binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 - binance 3x: ((65.83416667 / 60.15) - 1) * 3 = 0.2834995845386534 - kraken 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 - kraken 3x: ((65.795 / 60.15) - 1) * 3 = 0.2815461346633419 - binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292 - binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876 - kraken -1x: (1-(66.2311650 / 59.85)) * 1 = -0.106619298245614 - kraken -3x: (1-(66.2311650 / 59.85)) * 3 = -0.319857894736842 + 10 minute limit trade on Binance/Kraken at 1x, 3x leverage + fee: 0.25% quote + interest_rate: 0.05% per 4 hrs + open_rate: 2.00 quote + close_rate: 2.20 quote + amount: = 30.0 crypto + stake_amount + 1x,-1x: 60.0 quote + 3x,-3x: 20.0 quote + borrowed + 1x: 0 quote + 3x: 40 quote + -1x: 30 crypto + -3x: 30 crypto + hours: 1/6 (10 minutes) + time-periods: + kraken: (1 + 1) 4hr_periods = 2 4hr_periods + binance: 1/24 24hr_periods + interest: borrowed * interest_rate * time-periods + 1x : / + binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote + kraken 3x: 40 * 0.0005 * 2 = 0.040 quote + binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto + kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto + open_value: (amount * open_rate) ± (amount * open_rate * fee) + 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote + amount_closed: + 1x, 3x : amount + -1x, -3x : amount + interest + binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto + kraken -1x,-3x: 30 + 0.03 = 30.03 crypto + close_value: + 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + binance 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 + kraken 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 + binance -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001 + kraken -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 + total_profit: + 1x, 3x : close_value - open_value + -1x,-3x: open_value - close_value + binance,kraken 1x: 65.835 - 60.15 = 5.685 + binance 3x: 65.83416667 - 60.15 = 5.684166670000003 + kraken 3x: 65.795 - 60.15 = 5.645 + binance -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013 + kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 + total_profit_ratio: + 1x, 3x : ((close_value/open_value) - 1) * leverage + -1x,-3x: (1 - (close_value/open_value)) * leverage + binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 + binance 3x: ((65.83416667 / 60.15) - 1) * 3 = 0.2834995845386534 + kraken 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 + kraken 3x: ((65.795 / 60.15) - 1) * 3 = 0.2815461346633419 + binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292 + binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876 + kraken -1x: (1-(66.2311650 / 59.85)) * 1 = -0.106619298245614 + kraken -3x: (1-(66.2311650 / 59.85)) * 3 = -0.319857894736842 """ trade = Trade( id=2, - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, @@ -358,87 +360,101 @@ def test_borrowed(fee, is_short, lev, borrowed, trading_mode): open_date=dt_now(), fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", is_short=is_short, leverage=lev, - trading_mode=trading_mode + trading_mode=trading_mode, ) assert trade.borrowed == borrowed -@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit,trading_mode', [ - (False, 2.0, 2.2, 1.0, 0.09451372, spot), - (True, 2.2, 2.0, 3.0, 0.25894253, margin), -]) +@pytest.mark.parametrize( + "is_short,open_rate,close_rate,lev,profit,trading_mode", + [ + (False, 2.0, 2.2, 1.0, 0.09451372, spot), + (True, 2.2, 2.0, 3.0, 0.25894253, margin), + ], +) @pytest.mark.usefixtures("init_persistence") -def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt, time_machine, - is_short, open_rate, close_rate, lev, profit, trading_mode): +def test_update_limit_order( + fee, + caplog, + limit_buy_order_usdt, + limit_sell_order_usdt, + time_machine, + is_short, + open_rate, + close_rate, + lev, + profit, + trading_mode, +): """ - 10 minute limit trade on Binance/Kraken at 1x, 3x leverage - fee: 0.25% quote - interest_rate: 0.05% per 4 hrs - open_rate: 2.00 quote - close_rate: 2.20 quote - amount: = 30.0 crypto - stake_amount - 1x,-1x: 60.0 quote - 3x,-3x: 20.0 quote - borrowed - 1x: 0 quote - 3x: 40 quote - -1x: 30 crypto - -3x: 30 crypto - hours: 1/6 (10 minutes) - time-periods: - kraken: (1 + 1) 4hr_periods = 2 4hr_periods - binance: 1/24 24hr_periods - interest: borrowed * interest_rate * time-periods - 1x : / - binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote - kraken 3x: 40 * 0.0005 * 2 = 0.040 quote - binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto - kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto - open_value: (amount * open_rate) ± (amount * open_rate * fee) - 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote - amount_closed: - 1x, 3x : amount - -1x, -3x : amount + interest - binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto - kraken -1x,-3x: 30 + 0.03 = 30.03 crypto - close_value: - 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest - -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) - binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 - binance 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 - kraken 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 - binance -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001 - kraken -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 - total_profit: - 1x, 3x : close_value - open_value - -1x,-3x: open_value - close_value - binance,kraken 1x: 65.835 - 60.15 = 5.685 - binance 3x: 65.83416667 - 60.15 = 5.684166670000003 - kraken 3x: 65.795 - 60.15 = 5.645 - binance -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013 - kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 - total_profit_ratio: - 1x, 3x : ((close_value/open_value) - 1) * leverage - -1x,-3x: (1 - (close_value/open_value)) * leverage - binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 - binance 3x: ((65.83416667 / 60.15) - 1) * 3 = 0.2834995845386534 - kraken 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 - kraken 3x: ((65.795 / 60.15) - 1) * 3 = 0.2815461346633419 - binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292 - binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876 - kraken -1x: (1-(66.2311650 / 59.85)) * 1 = -0.106619298245614 - kraken -3x: (1-(66.2311650 / 59.85)) * 3 = -0.319857894736842 - open_rate: 2.2, close_rate: 2.0, -3x, binance, short - open_value: 30 * 2.2 - 30 * 2.2 * 0.0025 = 65.835 quote - amount_closed: 30 + 0.000625 = 30.000625 crypto - close_value: (30.000625 * 2.0) + (30.000625 * 2.0 * 0.0025) = 60.151253125 - total_profit: 65.835 - 60.151253125 = 5.683746874999997 - total_profit_ratio: (1-(60.151253125/65.835)) * 3 = 0.2589996297562085 + 10 minute limit trade on Binance/Kraken at 1x, 3x leverage + fee: 0.25% quote + interest_rate: 0.05% per 4 hrs + open_rate: 2.00 quote + close_rate: 2.20 quote + amount: = 30.0 crypto + stake_amount + 1x,-1x: 60.0 quote + 3x,-3x: 20.0 quote + borrowed + 1x: 0 quote + 3x: 40 quote + -1x: 30 crypto + -3x: 30 crypto + hours: 1/6 (10 minutes) + time-periods: + kraken: (1 + 1) 4hr_periods = 2 4hr_periods + binance: 1/24 24hr_periods + interest: borrowed * interest_rate * time-periods + 1x : / + binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote + kraken 3x: 40 * 0.0005 * 2 = 0.040 quote + binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto + kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto + open_value: (amount * open_rate) ± (amount * open_rate * fee) + 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote + amount_closed: + 1x, 3x : amount + -1x, -3x : amount + interest + binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto + kraken -1x,-3x: 30 + 0.03 = 30.03 crypto + close_value: + 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + binance 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 + kraken 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 + binance -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001 + kraken -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 + total_profit: + 1x, 3x : close_value - open_value + -1x,-3x: open_value - close_value + binance,kraken 1x: 65.835 - 60.15 = 5.685 + binance 3x: 65.83416667 - 60.15 = 5.684166670000003 + kraken 3x: 65.795 - 60.15 = 5.645 + binance -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013 + kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 + total_profit_ratio: + 1x, 3x : ((close_value/open_value) - 1) * leverage + -1x,-3x: (1 - (close_value/open_value)) * leverage + binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 + binance 3x: ((65.83416667 / 60.15) - 1) * 3 = 0.2834995845386534 + kraken 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 + kraken 3x: ((65.795 / 60.15) - 1) * 3 = 0.2815461346633419 + binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292 + binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876 + kraken -1x: (1-(66.2311650 / 59.85)) * 1 = -0.106619298245614 + kraken -3x: (1-(66.2311650 / 59.85)) * 3 = -0.319857894736842 + open_rate: 2.2, close_rate: 2.0, -3x, binance, short + open_value: 30 * 2.2 - 30 * 2.2 * 0.0025 = 65.835 quote + amount_closed: 30 + 0.000625 = 30.000625 crypto + close_value: (30.000625 * 2.0) + (30.000625 * 2.0 * 0.0025) = 60.151253125 + total_profit: 65.835 - 60.151253125 = 5.683746874999997 + total_profit_ratio: (1-(60.151253125/65.835)) * 3 = 0.2589996297562085 """ time_machine.move_to("2022-03-31 20:45:00 +00:00") @@ -449,7 +465,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_ trade = Trade( id=2, - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=open_rate, amount=30.0, @@ -457,32 +473,34 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_ open_date=dt_now(), fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", is_short=is_short, interest_rate=0.0005, leverage=lev, - trading_mode=trading_mode + trading_mode=trading_mode, ) assert not trade.has_open_orders assert trade.close_profit is None assert trade.close_date is None - oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', entry_side) + oobj = Order.parse_from_ccxt_object(enter_order, "ADA/USDT", entry_side) trade.orders.append(oobj) trade.update_trade(oobj) assert not trade.has_open_orders assert trade.open_rate == open_rate assert trade.close_profit is None assert trade.close_date is None - assert log_has_re(f"LIMIT_{entry_side.upper()} has been fulfilled for " - r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, " - f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, " - r"open_since=.*\).", - caplog) + assert log_has_re( + f"LIMIT_{entry_side.upper()} has been fulfilled for " + r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, " + f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, " + r"open_since=.*\).", + caplog, + ) caplog.clear() time_machine.move_to("2022-03-31 21:45:05 +00:00") - oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side) + oobj = Order.parse_from_ccxt_object(exit_order, "ADA/USDT", exit_side) trade.orders.append(oobj) trade.update_trade(oobj) @@ -490,11 +508,13 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_ assert trade.close_rate == close_rate assert pytest.approx(trade.close_profit) == profit assert trade.close_date is not None - assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for " - r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, " - f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, " - r"open_since=.*\).", - caplog) + assert log_has_re( + f"LIMIT_{exit_side.upper()} has been fulfilled for " + r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, " + f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, " + r"open_since=.*\).", + caplog, + ) caplog.clear() @@ -502,7 +522,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, caplog): trade = Trade( id=1, - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, @@ -510,64 +530,77 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, fee_open=fee.return_value, fee_close=fee.return_value, open_date=dt_now(), - exchange='binance', + exchange="binance", trading_mode=margin, leverage=1.0, ) - oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, 'ADA/USDT', 'buy') + oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, "ADA/USDT", "buy") trade.orders.append(oobj) trade.update_trade(oobj) assert not trade.has_open_orders assert trade.open_rate == 2.0 assert trade.close_profit is None assert trade.close_date is None - assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, " - r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, " - r"open_rate=2.00000000, open_since=.*\).", - caplog) + assert log_has_re( + r"MARKET_BUY has been fulfilled for Trade\(id=1, " + r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, " + r"open_rate=2.00000000, open_since=.*\).", + caplog, + ) caplog.clear() trade.is_open = True - oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, 'ADA/USDT', 'sell') + oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, "ADA/USDT", "sell") trade.orders.append(oobj) trade.update_trade(oobj) assert not trade.has_open_orders assert trade.close_rate == 2.2 assert pytest.approx(trade.close_profit) == 0.094513715710723 assert trade.close_date is not None - assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " - r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, " - r"open_rate=2.00000000, open_since=.*\).", - caplog) + assert log_has_re( + r"MARKET_SELL has been fulfilled for Trade\(id=1, " + r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, " + r"open_rate=2.00000000, open_since=.*\).", + caplog, + ) @pytest.mark.parametrize( - 'exchange,is_short,lev,open_value,close_value,profit,profit_ratio,trading_mode,funding_fees', [ + "exchange,is_short,lev,open_value,close_value,profit,profit_ratio,trading_mode,funding_fees", + [ ("binance", False, 1, 60.15, 65.835, 5.685, 0.09451371, spot, 0.0), ("binance", True, 1, 65.835, 60.151253125, 5.68374687, 0.08633321, margin, 0.0), ("binance", False, 3, 60.15, 65.83416667, 5.68416667, 0.28349958, margin, 0.0), ("binance", True, 3, 65.835, 60.151253125, 5.68374687, 0.25899963, margin, 0.0), - ("kraken", False, 1, 60.15, 65.835, 5.685, 0.09451371, spot, 0.0), ("kraken", True, 1, 65.835, 60.21015, 5.62485, 0.0854386, margin, 0.0), ("kraken", False, 3, 60.15, 65.795, 5.645, 0.28154613, margin, 0.0), ("kraken", True, 3, 65.835, 60.21015, 5.62485, 0.25631579, margin, 0.0), - - ("binance", False, 1, 60.15, 65.835, 5.685, 0.09451371, futures, 0.0), - ("binance", False, 1, 60.15, 66.835, 6.685, 0.11113881, futures, 1.0), - ("binance", True, 1, 65.835, 60.15, 5.685, 0.08635224, futures, 0.0), - ("binance", True, 1, 65.835, 61.15, 4.685, 0.07116276, futures, -1.0), - ("binance", True, 3, 65.835, 59.15, 6.685, 0.3046252, futures, 1.0), - ("binance", False, 3, 60.15, 64.835, 4.685, 0.23366583, futures, -1.0), - ]) + ("binance", False, 1, 60.15, 65.835, 5.685, 0.09451371, futures, 0.0), + ("binance", False, 1, 60.15, 66.835, 6.685, 0.11113881, futures, 1.0), + ("binance", True, 1, 65.835, 60.15, 5.685, 0.08635224, futures, 0.0), + ("binance", True, 1, 65.835, 61.15, 4.685, 0.07116276, futures, -1.0), + ("binance", True, 3, 65.835, 59.15, 6.685, 0.3046252, futures, 1.0), + ("binance", False, 3, 60.15, 64.835, 4.685, 0.23366583, futures, -1.0), + ], +) @pytest.mark.usefixtures("init_persistence") def test_calc_open_close_trade_price( - limit_order, fee, exchange, is_short, lev, - open_value, close_value, profit, profit_ratio, trading_mode, funding_fees + limit_order, + fee, + exchange, + is_short, + lev, + open_value, + close_value, + profit, + profit_ratio, + trading_mode, + funding_fees, ): trade: Trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, @@ -583,14 +616,14 @@ def test_calc_open_close_trade_price( entry_order = limit_order[trade.entry_side] exit_order = limit_order[trade.exit_side] - oobj = Order.parse_from_ccxt_object(entry_order, 'ADA/USDT', trade.entry_side) + oobj = Order.parse_from_ccxt_object(entry_order, "ADA/USDT", trade.entry_side) oobj._trade_live = trade oobj.update_from_ccxt_object(entry_order) trade.update_trade(oobj) trade.funding_fee_running = funding_fees - oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', trade.exit_side) + oobj = Order.parse_from_ccxt_object(exit_order, "ADA/USDT", trade.exit_side) oobj._trade_live = trade oobj.update_from_ccxt_object(exit_order) trade.update_trade(oobj) @@ -611,7 +644,7 @@ def test_trade_close(fee, time_machine): time_machine.move_to("2022-09-01 05:00:00 +00:00", tick=False) trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, @@ -620,40 +653,44 @@ def test_trade_close(fee, time_machine): fee_close=fee.return_value, open_date=dt_now() - timedelta(minutes=10), interest_rate=0.0005, - exchange='binance', + exchange="binance", trading_mode=margin, leverage=1.0, ) - trade.orders.append(Order( - ft_order_side=trade.entry_side, - order_id=f'{trade.pair}-{trade.entry_side}-{trade.open_date}', - ft_is_open=False, - ft_pair=trade.pair, - amount=trade.amount, - filled=trade.amount, - remaining=0, - price=trade.open_rate, - average=trade.open_rate, - status="closed", - order_type="limit", - side=trade.entry_side, - order_filled_date=trade.open_date, - )) - trade.orders.append(Order( - ft_order_side=trade.exit_side, - order_id=f'{trade.pair}-{trade.exit_side}-{trade.open_date}', - ft_is_open=False, - ft_pair=trade.pair, - amount=trade.amount, - filled=trade.amount, - remaining=0, - price=2.2, - average=2.2, - status="closed", - order_type="limit", - side=trade.exit_side, - order_filled_date=dt_now(), - )) + trade.orders.append( + Order( + ft_order_side=trade.entry_side, + order_id=f"{trade.pair}-{trade.entry_side}-{trade.open_date}", + ft_is_open=False, + ft_pair=trade.pair, + amount=trade.amount, + filled=trade.amount, + remaining=0, + price=trade.open_rate, + average=trade.open_rate, + status="closed", + order_type="limit", + side=trade.entry_side, + order_filled_date=trade.open_date, + ) + ) + trade.orders.append( + Order( + ft_order_side=trade.exit_side, + order_id=f"{trade.pair}-{trade.exit_side}-{trade.open_date}", + ft_is_open=False, + ft_pair=trade.pair, + amount=trade.amount, + filled=trade.amount, + remaining=0, + price=2.2, + average=2.2, + status="closed", + order_type="limit", + side=trade.exit_side, + order_filled_date=dt_now(), + ) + ) assert trade.close_profit is None assert trade.close_date is None assert trade.is_open is True @@ -675,18 +712,18 @@ def test_trade_close(fee, time_machine): @pytest.mark.usefixtures("init_persistence") def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", trading_mode=margin, leverage=1.0, ) - oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, "ADA/USDT", "buy") trade.update_trade(oobj) assert trade.calc_close_trade_value(trade.close_rate) == 0.0 @@ -694,22 +731,22 @@ def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee): @pytest.mark.usefixtures("init_persistence") def test_update_open_order(limit_buy_order_usdt): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, open_rate=2.0, amount=30.0, fee_open=0.1, fee_close=0.1, - exchange='binance', - trading_mode=margin + exchange="binance", + trading_mode=margin, ) assert not trade.has_open_orders assert trade.close_profit is None assert trade.close_date is None - limit_buy_order_usdt['status'] = 'open' - oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + limit_buy_order_usdt["status"] = "open" + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, "ADA/USDT", "buy") trade.update_trade(oobj) assert not trade.has_open_orders @@ -720,43 +757,40 @@ def test_update_open_order(limit_buy_order_usdt): @pytest.mark.usefixtures("init_persistence") def test_update_invalid_order(limit_buy_order_usdt): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, amount=30.0, open_rate=2.0, fee_open=0.1, fee_close=0.1, - exchange='binance', - trading_mode=margin + exchange="binance", + trading_mode=margin, ) - limit_buy_order_usdt['type'] = 'invalid' - oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'meep') - with pytest.raises(ValueError, match=r'Unknown order type'): + limit_buy_order_usdt["type"] = "invalid" + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, "ADA/USDT", "meep") + with pytest.raises(ValueError, match=r"Unknown order type"): trade.update_trade(oobj) -@pytest.mark.parametrize('exchange', ['binance', 'kraken']) -@pytest.mark.parametrize('trading_mode', [spot, margin, futures]) -@pytest.mark.parametrize('lev', [1, 3]) -@pytest.mark.parametrize('is_short,fee_rate,result', [ - (False, 0.003, 60.18), - (False, 0.0025, 60.15), - (False, 0.003, 60.18), - (False, 0.0025, 60.15), - (True, 0.003, 59.82), - (True, 0.0025, 59.85), - (True, 0.003, 59.82), - (True, 0.0025, 59.85) -]) +@pytest.mark.parametrize("exchange", ["binance", "kraken"]) +@pytest.mark.parametrize("trading_mode", [spot, margin, futures]) +@pytest.mark.parametrize("lev", [1, 3]) +@pytest.mark.parametrize( + "is_short,fee_rate,result", + [ + (False, 0.003, 60.18), + (False, 0.0025, 60.15), + (False, 0.003, 60.18), + (False, 0.0025, 60.15), + (True, 0.003, 59.82), + (True, 0.0025, 59.85), + (True, 0.003, 59.82), + (True, 0.0025, 59.85), + ], +) @pytest.mark.usefixtures("init_persistence") def test_calc_open_trade_value( - limit_buy_order_usdt, - exchange, - lev, - is_short, - fee_rate, - result, - trading_mode + limit_buy_order_usdt, exchange, lev, is_short, fee_rate, result, trading_mode ): # 10 minute limit trade on Binance/Kraken at 1x, 3x leverage # fee: 0.25 %, 0.3% quote @@ -773,7 +807,7 @@ def test_calc_open_trade_value( # 1x, 3x: 30 * 2 + 30 * 2 * 0.003 = 60.18 quote # -1x,-3x: 30 * 2 - 30 * 2 * 0.003 = 59.82 quote trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, amount=30.0, open_rate=2.0, @@ -783,10 +817,11 @@ def test_calc_open_trade_value( exchange=exchange, leverage=lev, is_short=is_short, - trading_mode=trading_mode + trading_mode=trading_mode, ) oobj = Order.parse_from_ccxt_object( - limit_buy_order_usdt, 'ADA/USDT', 'sell' if is_short else 'buy') + limit_buy_order_usdt, "ADA/USDT", "sell" if is_short else "buy" + ) trade.update_trade(oobj) # Buy @ 2.0 # Get the open rate price with the standard fee rate @@ -794,38 +829,36 @@ def test_calc_open_trade_value( @pytest.mark.parametrize( - 'exchange,is_short,lev,open_rate,close_rate,fee_rate,result,trading_mode,funding_fees', [ - ('binance', False, 1, 2.0, 2.5, 0.0025, 74.8125, spot, 0), - ('binance', False, 1, 2.0, 2.5, 0.003, 74.775, spot, 0), - ('binance', False, 1, 2.0, 2.2, 0.005, 65.67, margin, 0), - ('binance', False, 3, 2.0, 2.5, 0.0025, 74.81166667, margin, 0), - ('binance', False, 3, 2.0, 2.5, 0.003, 74.77416667, margin, 0), - ('binance', True, 3, 2.2, 2.5, 0.0025, 75.18906641, margin, 0), - ('binance', True, 3, 2.2, 2.5, 0.003, 75.22656719, margin, 0), - ('binance', True, 1, 2.2, 2.5, 0.0025, 75.18906641, margin, 0), - ('binance', True, 1, 2.2, 2.5, 0.003, 75.22656719, margin, 0), - + "exchange,is_short,lev,open_rate,close_rate,fee_rate,result,trading_mode,funding_fees", + [ + ("binance", False, 1, 2.0, 2.5, 0.0025, 74.8125, spot, 0), + ("binance", False, 1, 2.0, 2.5, 0.003, 74.775, spot, 0), + ("binance", False, 1, 2.0, 2.2, 0.005, 65.67, margin, 0), + ("binance", False, 3, 2.0, 2.5, 0.0025, 74.81166667, margin, 0), + ("binance", False, 3, 2.0, 2.5, 0.003, 74.77416667, margin, 0), + ("binance", True, 3, 2.2, 2.5, 0.0025, 75.18906641, margin, 0), + ("binance", True, 3, 2.2, 2.5, 0.003, 75.22656719, margin, 0), + ("binance", True, 1, 2.2, 2.5, 0.0025, 75.18906641, margin, 0), + ("binance", True, 1, 2.2, 2.5, 0.003, 75.22656719, margin, 0), # Kraken - ('kraken', False, 3, 2.0, 2.5, 0.0025, 74.7725, margin, 0), - ('kraken', False, 3, 2.0, 2.5, 0.003, 74.735, margin, 0), - ('kraken', True, 3, 2.2, 2.5, 0.0025, 75.2626875, margin, 0), - ('kraken', True, 3, 2.2, 2.5, 0.003, 75.300225, margin, 0), - ('kraken', True, 1, 2.2, 2.5, 0.0025, 75.2626875, margin, 0), - ('kraken', True, 1, 2.2, 2.5, 0.003, 75.300225, margin, 0), - - ('binance', False, 1, 2.0, 2.5, 0.0025, 75.8125, futures, 1), - ('binance', False, 3, 2.0, 2.5, 0.0025, 73.8125, futures, -1), - ('binance', True, 3, 2.0, 2.5, 0.0025, 74.1875, futures, 1), - ('binance', True, 1, 2.0, 2.5, 0.0025, 76.1875, futures, -1), - - ]) + ("kraken", False, 3, 2.0, 2.5, 0.0025, 74.7725, margin, 0), + ("kraken", False, 3, 2.0, 2.5, 0.003, 74.735, margin, 0), + ("kraken", True, 3, 2.2, 2.5, 0.0025, 75.2626875, margin, 0), + ("kraken", True, 3, 2.2, 2.5, 0.003, 75.300225, margin, 0), + ("kraken", True, 1, 2.2, 2.5, 0.0025, 75.2626875, margin, 0), + ("kraken", True, 1, 2.2, 2.5, 0.003, 75.300225, margin, 0), + ("binance", False, 1, 2.0, 2.5, 0.0025, 75.8125, futures, 1), + ("binance", False, 3, 2.0, 2.5, 0.0025, 73.8125, futures, -1), + ("binance", True, 3, 2.0, 2.5, 0.0025, 74.1875, futures, 1), + ("binance", True, 1, 2.0, 2.5, 0.0025, 76.1875, futures, -1), + ], +) @pytest.mark.usefixtures("init_persistence") def test_calc_close_trade_price( - open_rate, exchange, is_short, - lev, close_rate, fee_rate, result, trading_mode, funding_fees + open_rate, exchange, is_short, lev, close_rate, fee_rate, result, trading_mode, funding_fees ): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, amount=30.0, open_rate=open_rate, @@ -837,316 +870,296 @@ def test_calc_close_trade_price( is_short=is_short, leverage=lev, trading_mode=trading_mode, - funding_fees=funding_fees + funding_fees=funding_fees, ) assert round(trade.calc_close_trade_value(rate=close_rate), 8) == result @pytest.mark.parametrize( - 'exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio,trading_mode,funding_fees', [ - ('binance', False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0), - ('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.134247714, margin, 0), - ('binance', True, 1, 2.1, 0.0025, -3.3088157, -0.055285142, margin, 0), - ('binance', True, 3, 2.1, 0.0025, -3.3088157, -0.16585542, margin, 0), - - ('binance', False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0), - ('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.164256026, margin, 0), - ('binance', True, 1, 1.9, 0.0025, 2.70630953, 0.0452182043, margin, 0), - ('binance', True, 3, 1.9, 0.0025, 2.70630953, 0.135654613, margin, 0), - - ('binance', False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0), - ('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.28349958, margin, 0), - ('binance', True, 1, 2.2, 0.0025, -6.3163784, -0.10553681, margin, 0), - ('binance', True, 3, 2.2, 0.0025, -6.3163784, -0.31661044, margin, 0), - + "exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio,trading_mode,funding_fees", + [ + ("binance", False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0), + ("binance", False, 3, 2.1, 0.0025, 2.69166667, 0.134247714, margin, 0), + ("binance", True, 1, 2.1, 0.0025, -3.3088157, -0.055285142, margin, 0), + ("binance", True, 3, 2.1, 0.0025, -3.3088157, -0.16585542, margin, 0), + ("binance", False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0), + ("binance", False, 3, 1.9, 0.0025, -3.29333333, -0.164256026, margin, 0), + ("binance", True, 1, 1.9, 0.0025, 2.70630953, 0.0452182043, margin, 0), + ("binance", True, 3, 1.9, 0.0025, 2.70630953, 0.135654613, margin, 0), + ("binance", False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0), + ("binance", False, 3, 2.2, 0.0025, 5.68416667, 0.28349958, margin, 0), + ("binance", True, 1, 2.2, 0.0025, -6.3163784, -0.10553681, margin, 0), + ("binance", True, 3, 2.2, 0.0025, -6.3163784, -0.31661044, margin, 0), # Kraken - ('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0), - ('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.132294264, margin, 0), - ('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.056318421, margin, 0), - ('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.168955263, margin, 0), - - ('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0), - ('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.166209476, margin, 0), - ('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.044283333, margin, 0), - ('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.132850000, margin, 0), - - ('kraken', False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0), - ('kraken', False, 3, 2.2, 0.0025, 5.645, 0.28154613, margin, 0), - ('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.1066192, margin, 0), - ('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.3198578, margin, 0), - - ('binance', False, 1, 2.1, 0.003, 2.66100000, 0.044239401, spot, 0), - ('binance', False, 1, 1.9, 0.003, -3.3209999, -0.055211970, spot, 0), - ('binance', False, 1, 2.2, 0.003, 5.6520000, 0.093965087, spot, 0), - + ("kraken", False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0), + ("kraken", False, 3, 2.1, 0.0025, 2.6525, 0.132294264, margin, 0), + ("kraken", True, 1, 2.1, 0.0025, -3.3706575, -0.056318421, margin, 0), + ("kraken", True, 3, 2.1, 0.0025, -3.3706575, -0.168955263, margin, 0), + ("kraken", False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0), + ("kraken", False, 3, 1.9, 0.0025, -3.3325, -0.166209476, margin, 0), + ("kraken", True, 1, 1.9, 0.0025, 2.6503575, 0.044283333, margin, 0), + ("kraken", True, 3, 1.9, 0.0025, 2.6503575, 0.132850000, margin, 0), + ("kraken", False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0), + ("kraken", False, 3, 2.2, 0.0025, 5.645, 0.28154613, margin, 0), + ("kraken", True, 1, 2.2, 0.0025, -6.381165, -0.1066192, margin, 0), + ("kraken", True, 3, 2.2, 0.0025, -6.381165, -0.3198578, margin, 0), + ("binance", False, 1, 2.1, 0.003, 2.66100000, 0.044239401, spot, 0), + ("binance", False, 1, 1.9, 0.003, -3.3209999, -0.055211970, spot, 0), + ("binance", False, 1, 2.2, 0.003, 5.6520000, 0.093965087, spot, 0), # FUTURES, funding_fee=1 - ('binance', False, 1, 2.1, 0.0025, 3.6925, 0.06138819, futures, 1), - ('binance', False, 3, 2.1, 0.0025, 3.6925, 0.18416458, futures, 1), - ('binance', True, 1, 2.1, 0.0025, -2.3074999, -0.03855472, futures, 1), - ('binance', True, 3, 2.1, 0.0025, -2.3074999, -0.11566416, futures, 1), - - ('binance', False, 1, 1.9, 0.0025, -2.2925, -0.03811305, futures, 1), - ('binance', False, 3, 1.9, 0.0025, -2.2925, -0.11433915, futures, 1), - ('binance', True, 1, 1.9, 0.0025, 3.7075, 0.06194653, futures, 1), - ('binance', True, 3, 1.9, 0.0025, 3.7075, 0.18583959, futures, 1), - - ('binance', False, 1, 2.2, 0.0025, 6.685, 0.11113881, futures, 1), - ('binance', False, 3, 2.2, 0.0025, 6.685, 0.33341645, futures, 1), - ('binance', True, 1, 2.2, 0.0025, -5.315, -0.08880534, futures, 1), - ('binance', True, 3, 2.2, 0.0025, -5.315, -0.26641604, futures, 1), - + ("binance", False, 1, 2.1, 0.0025, 3.6925, 0.06138819, futures, 1), + ("binance", False, 3, 2.1, 0.0025, 3.6925, 0.18416458, futures, 1), + ("binance", True, 1, 2.1, 0.0025, -2.3074999, -0.03855472, futures, 1), + ("binance", True, 3, 2.1, 0.0025, -2.3074999, -0.11566416, futures, 1), + ("binance", False, 1, 1.9, 0.0025, -2.2925, -0.03811305, futures, 1), + ("binance", False, 3, 1.9, 0.0025, -2.2925, -0.11433915, futures, 1), + ("binance", True, 1, 1.9, 0.0025, 3.7075, 0.06194653, futures, 1), + ("binance", True, 3, 1.9, 0.0025, 3.7075, 0.18583959, futures, 1), + ("binance", False, 1, 2.2, 0.0025, 6.685, 0.11113881, futures, 1), + ("binance", False, 3, 2.2, 0.0025, 6.685, 0.33341645, futures, 1), + ("binance", True, 1, 2.2, 0.0025, -5.315, -0.08880534, futures, 1), + ("binance", True, 3, 2.2, 0.0025, -5.315, -0.26641604, futures, 1), # FUTURES, funding_fee=-1 - ('binance', False, 1, 2.1, 0.0025, 1.6925, 0.02813798, futures, -1), - ('binance', False, 3, 2.1, 0.0025, 1.6925, 0.08441396, futures, -1), - ('binance', True, 1, 2.1, 0.0025, -4.307499, -0.07197159, futures, -1), - ('binance', True, 3, 2.1, 0.0025, -4.307499, -0.21591478, futures, -1), - - ('binance', False, 1, 1.9, 0.0025, -4.292499, -0.07136325, futures, -1), - ('binance', False, 3, 1.9, 0.0025, -4.292499, -0.21408977, futures, -1), - ('binance', True, 1, 1.9, 0.0025, 1.7075, 0.02852965, futures, -1), - ('binance', True, 3, 1.9, 0.0025, 1.7075, 0.08558897, futures, -1), - - ('binance', False, 1, 2.2, 0.0025, 4.684999, 0.07788861, futures, -1), - ('binance', False, 3, 2.2, 0.0025, 4.684999, 0.23366583, futures, -1), - ('binance', True, 1, 2.2, 0.0025, -7.315, -0.12222222, futures, -1), - ('binance', True, 3, 2.2, 0.0025, -7.315, -0.36666666, futures, -1), - + ("binance", False, 1, 2.1, 0.0025, 1.6925, 0.02813798, futures, -1), + ("binance", False, 3, 2.1, 0.0025, 1.6925, 0.08441396, futures, -1), + ("binance", True, 1, 2.1, 0.0025, -4.307499, -0.07197159, futures, -1), + ("binance", True, 3, 2.1, 0.0025, -4.307499, -0.21591478, futures, -1), + ("binance", False, 1, 1.9, 0.0025, -4.292499, -0.07136325, futures, -1), + ("binance", False, 3, 1.9, 0.0025, -4.292499, -0.21408977, futures, -1), + ("binance", True, 1, 1.9, 0.0025, 1.7075, 0.02852965, futures, -1), + ("binance", True, 3, 1.9, 0.0025, 1.7075, 0.08558897, futures, -1), + ("binance", False, 1, 2.2, 0.0025, 4.684999, 0.07788861, futures, -1), + ("binance", False, 3, 2.2, 0.0025, 4.684999, 0.23366583, futures, -1), + ("binance", True, 1, 2.2, 0.0025, -7.315, -0.12222222, futures, -1), + ("binance", True, 3, 2.2, 0.0025, -7.315, -0.36666666, futures, -1), # FUTURES, funding_fee=0 - ('binance', False, 1, 2.1, 0.0025, 2.6925, 0.04476309, futures, 0), - ('binance', False, 3, 2.1, 0.0025, 2.6925, 0.13428928, futures, 0), - ('binance', True, 1, 2.1, 0.0025, -3.3074999, -0.05526316, futures, 0), - ('binance', True, 3, 2.1, 0.0025, -3.3074999, -0.16578947, futures, 0), - - ('binance', False, 1, 1.9, 0.0025, -3.2925, -0.05473815, futures, 0), - ('binance', False, 3, 1.9, 0.0025, -3.2925, -0.16421446, futures, 0), - ('binance', True, 1, 1.9, 0.0025, 2.7075, 0.0452381, futures, 0), - ('binance', True, 3, 1.9, 0.0025, 2.7075, 0.13571429, futures, 0), - ]) + ("binance", False, 1, 2.1, 0.0025, 2.6925, 0.04476309, futures, 0), + ("binance", False, 3, 2.1, 0.0025, 2.6925, 0.13428928, futures, 0), + ("binance", True, 1, 2.1, 0.0025, -3.3074999, -0.05526316, futures, 0), + ("binance", True, 3, 2.1, 0.0025, -3.3074999, -0.16578947, futures, 0), + ("binance", False, 1, 1.9, 0.0025, -3.2925, -0.05473815, futures, 0), + ("binance", False, 3, 1.9, 0.0025, -3.2925, -0.16421446, futures, 0), + ("binance", True, 1, 1.9, 0.0025, 2.7075, 0.0452381, futures, 0), + ("binance", True, 3, 1.9, 0.0025, 2.7075, 0.13571429, futures, 0), + ], +) @pytest.mark.usefixtures("init_persistence") def test_calc_profit( - exchange, - is_short, - lev, - close_rate, - fee_close, - profit, - profit_ratio, - trading_mode, - funding_fees + exchange, is_short, lev, close_rate, fee_close, profit, profit_ratio, trading_mode, funding_fees ): """ - 10 minute limit trade on Binance/Kraken at 1x, 3x leverage - arguments: - fee: - 0.25% quote - 0.30% quote - interest_rate: 0.05% per 4 hrs - open_rate: 2.0 quote - close_rate: - 1.9 quote - 2.1 quote - 2.2 quote - amount: = 30.0 crypto - stake_amount - 1x,-1x: 60.0 quote - 3x,-3x: 20.0 quote - hours: 1/6 (10 minutes) - funding_fees: 1 - borrowed - 1x: 0 quote - 3x: 40 quote - -1x: 30 crypto - -3x: 30 crypto - time-periods: - kraken: (1 + 1) 4hr_periods = 2 4hr_periods - binance: 1/24 24hr_periods - interest: borrowed * interest_rate * time-periods - 1x : / - binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote - kraken 3x: 40 * 0.0005 * 2 = 0.040 quote - binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto - kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto - open_value: (amount * open_rate) ± (amount * open_rate * fee) - 0.0025 fee - 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote - 0.003 fee: Is only applied to close rate in this test - amount_closed: - 1x, 3x = amount - -1x, -3x = amount + interest - binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto - kraken -1x,-3x: 30 + 0.03 = 30.03 crypto + 10 minute limit trade on Binance/Kraken at 1x, 3x leverage + arguments: + fee: + 0.25% quote + 0.30% quote + interest_rate: 0.05% per 4 hrs + open_rate: 2.0 quote + close_rate: + 1.9 quote + 2.1 quote + 2.2 quote + amount: = 30.0 crypto + stake_amount + 1x,-1x: 60.0 quote + 3x,-3x: 20.0 quote + hours: 1/6 (10 minutes) + funding_fees: 1 + borrowed + 1x: 0 quote + 3x: 40 quote + -1x: 30 crypto + -3x: 30 crypto + time-periods: + kraken: (1 + 1) 4hr_periods = 2 4hr_periods + binance: 1/24 24hr_periods + interest: borrowed * interest_rate * time-periods + 1x : / + binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote + kraken 3x: 40 * 0.0005 * 2 = 0.040 quote + binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto + kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto + open_value: (amount * open_rate) ± (amount * open_rate * fee) + 0.0025 fee + 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote + 0.003 fee: Is only applied to close rate in this test + amount_closed: + 1x, 3x = amount + -1x, -3x = amount + interest + binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto + kraken -1x,-3x: 30 + 0.03 = 30.03 crypto + close_value: + equations: + 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + 2.1 quote + bin,krak 1x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) = 62.8425 + bin 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.0008333333 = 62.8416666667 + krak 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.040 = 62.8025 + bin -1x,-3x: (30.000625 * 2.1) + (30.000625 * 2.1 * 0.0025) = 63.15881578125 + krak -1x,-3x: (30.03 * 2.1) + (30.03 * 2.1 * 0.0025) = 63.2206575 + 1.9 quote + bin,krak 1x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) = 56.8575 + bin 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.0008333333 = 56.85666667 + krak 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.040 = 56.8175 + bin -1x,-3x: (30.000625 * 1.9) + (30.000625 * 1.9 * 0.0025) = 57.14369046875 + krak -1x,-3x: (30.03 * 1.9) + (30.03 * 1.9 * 0.0025) = 57.1996425 + 2.2 quote + bin,krak 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + bin 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 + krak 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 + bin -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.1663784375 + krak -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 + total_profit: + equations: + 1x, 3x : close_value - open_value + -1x,-3x: open_value - close_value + 2.1 quote + binance,kraken 1x: 62.8425 - 60.15 = 2.6925 + binance 3x: 62.84166667 - 60.15 = 2.69166667 + kraken 3x: 62.8025 - 60.15 = 2.6525 + binance -1x,-3x: 59.850 - 63.15881578125 = -3.308815781249997 + kraken -1x,-3x: 59.850 - 63.2206575 = -3.3706575 + 1.9 quote + binance,kraken 1x: 56.8575 - 60.15 = -3.2925 + binance 3x: 56.85666667 - 60.15 = -3.29333333 + kraken 3x: 56.8175 - 60.15 = -3.3325 + binance -1x,-3x: 59.850 - 57.14369046875 = 2.7063095312499996 + kraken -1x,-3x: 59.850 - 57.1996425 = 2.6503575 + 2.2 quote + binance,kraken 1x: 65.835 - 60.15 = 5.685 + binance 3x: 65.83416667 - 60.15 = 5.68416667 + kraken 3x: 65.795 - 60.15 = 5.645 + binance -1x,-3x: 59.850 - 66.1663784375 = -6.316378437499999 + kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 + total_profit_ratio: + equations: + 1x, 3x : ((close_value/open_value) - 1) * leverage + -1x,-3x: (1 - (close_value/open_value)) * leverage + 2.1 quote + binance,kraken 1x: (62.8425 / 60.15) - 1 = 0.04476309226932673 + binance 3x: ((62.84166667 / 60.15) - 1)*3 = 0.13424771421446402 + kraken 3x: ((62.8025 / 60.15) - 1)*3 = 0.13229426433915248 + binance -1x: 1 - (63.15881578125 / 59.850) = -0.05528514254385963 + binance -3x: (1 - (63.15881578125 / 59.850))*3 = -0.1658554276315789 + kraken -1x: 1 - (63.2206575 / 59.850) = -0.05631842105263152 + kraken -3x: (1 - (63.2206575 / 59.850))*3 = -0.16895526315789455 + 1.9 quote + binance,kraken 1x: (56.8575 / 60.15) - 1 = -0.05473815461346632 + binance 3x: ((56.85666667 / 60.15) - 1)*3 = -0.16425602643391513 + kraken 3x: ((56.8175 / 60.15) - 1)*3 = -0.16620947630922667 + binance -1x: 1 - (57.14369046875 / 59.850) = 0.045218204365079395 + binance -3x: (1 - (57.14369046875 / 59.850))*3 = 0.13565461309523819 + kraken -1x: 1 - (57.1996425 / 59.850) = 0.04428333333333334 + kraken -3x: (1 - (57.1996425 / 59.850))*3 = 0.13285000000000002 + 2.2 quote + binance,kraken 1x: (65.835 / 60.15) - 1 = 0.0945137157107232 + binance 3x: ((65.83416667 / 60.15) - 1)*3 = 0.2834995845386534 + kraken 3x: ((65.795 / 60.15) - 1)*3 = 0.2815461346633419 + binance -1x: 1 - (66.1663784375 / 59.850) = -0.1055368159983292 + binance -3x: (1 - (66.1663784375 / 59.850))*3 = -0.3166104479949876 + kraken -1x: 1 - (66.231165 / 59.850) = -0.106619298245614 + kraken -3x: (1 - (66.231165 / 59.850))*3 = -0.319857894736842 + fee: 0.003, 1x close_value: - equations: - 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest - -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) - 2.1 quote - bin,krak 1x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) = 62.8425 - bin 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.0008333333 = 62.8416666667 - krak 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.040 = 62.8025 - bin -1x,-3x: (30.000625 * 2.1) + (30.000625 * 2.1 * 0.0025) = 63.15881578125 - krak -1x,-3x: (30.03 * 2.1) + (30.03 * 2.1 * 0.0025) = 63.2206575 - 1.9 quote - bin,krak 1x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) = 56.8575 - bin 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.0008333333 = 56.85666667 - krak 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.040 = 56.8175 - bin -1x,-3x: (30.000625 * 1.9) + (30.000625 * 1.9 * 0.0025) = 57.14369046875 - krak -1x,-3x: (30.03 * 1.9) + (30.03 * 1.9 * 0.0025) = 57.1996425 - 2.2 quote - bin,krak 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 - bin 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 - krak 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 - bin -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.1663784375 - krak -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 - total_profit: - equations: - 1x, 3x : close_value - open_value - -1x,-3x: open_value - close_value - 2.1 quote - binance,kraken 1x: 62.8425 - 60.15 = 2.6925 - binance 3x: 62.84166667 - 60.15 = 2.69166667 - kraken 3x: 62.8025 - 60.15 = 2.6525 - binance -1x,-3x: 59.850 - 63.15881578125 = -3.308815781249997 - kraken -1x,-3x: 59.850 - 63.2206575 = -3.3706575 - 1.9 quote - binance,kraken 1x: 56.8575 - 60.15 = -3.2925 - binance 3x: 56.85666667 - 60.15 = -3.29333333 - kraken 3x: 56.8175 - 60.15 = -3.3325 - binance -1x,-3x: 59.850 - 57.14369046875 = 2.7063095312499996 - kraken -1x,-3x: 59.850 - 57.1996425 = 2.6503575 - 2.2 quote - binance,kraken 1x: 65.835 - 60.15 = 5.685 - binance 3x: 65.83416667 - 60.15 = 5.68416667 - kraken 3x: 65.795 - 60.15 = 5.645 - binance -1x,-3x: 59.850 - 66.1663784375 = -6.316378437499999 - kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 - total_profit_ratio: - equations: - 1x, 3x : ((close_value/open_value) - 1) * leverage - -1x,-3x: (1 - (close_value/open_value)) * leverage - 2.1 quote - binance,kraken 1x: (62.8425 / 60.15) - 1 = 0.04476309226932673 - binance 3x: ((62.84166667 / 60.15) - 1)*3 = 0.13424771421446402 - kraken 3x: ((62.8025 / 60.15) - 1)*3 = 0.13229426433915248 - binance -1x: 1 - (63.15881578125 / 59.850) = -0.05528514254385963 - binance -3x: (1 - (63.15881578125 / 59.850))*3 = -0.1658554276315789 - kraken -1x: 1 - (63.2206575 / 59.850) = -0.05631842105263152 - kraken -3x: (1 - (63.2206575 / 59.850))*3 = -0.16895526315789455 - 1.9 quote - binance,kraken 1x: (56.8575 / 60.15) - 1 = -0.05473815461346632 - binance 3x: ((56.85666667 / 60.15) - 1)*3 = -0.16425602643391513 - kraken 3x: ((56.8175 / 60.15) - 1)*3 = -0.16620947630922667 - binance -1x: 1 - (57.14369046875 / 59.850) = 0.045218204365079395 - binance -3x: (1 - (57.14369046875 / 59.850))*3 = 0.13565461309523819 - kraken -1x: 1 - (57.1996425 / 59.850) = 0.04428333333333334 - kraken -3x: (1 - (57.1996425 / 59.850))*3 = 0.13285000000000002 - 2.2 quote - binance,kraken 1x: (65.835 / 60.15) - 1 = 0.0945137157107232 - binance 3x: ((65.83416667 / 60.15) - 1)*3 = 0.2834995845386534 - kraken 3x: ((65.795 / 60.15) - 1)*3 = 0.2815461346633419 - binance -1x: 1 - (66.1663784375 / 59.850) = -0.1055368159983292 - binance -3x: (1 - (66.1663784375 / 59.850))*3 = -0.3166104479949876 - kraken -1x: 1 - (66.231165 / 59.850) = -0.106619298245614 - kraken -3x: (1 - (66.231165 / 59.850))*3 = -0.319857894736842 - fee: 0.003, 1x + 2.1 quote: (30.00 * 2.1) - (30.00 * 2.1 * 0.003) = 62.811 + 1.9 quote: (30.00 * 1.9) - (30.00 * 1.9 * 0.003) = 56.829 + 2.2 quote: (30.00 * 2.2) - (30.00 * 2.2 * 0.003) = 65.802 + total_profit + fee: 0.003, 1x + 2.1 quote: 62.811 - 60.15 = 2.6610000000000014 + 1.9 quote: 56.829 - 60.15 = -3.320999999999998 + 2.2 quote: 65.802 - 60.15 = 5.652000000000008 + total_profit_ratio + fee: 0.003, 1x + 2.1 quote: (62.811 / 60.15) - 1 = 0.04423940149625927 + 1.9 quote: (56.829 / 60.15) - 1 = -0.05521197007481293 + 2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565 + futures (live): + funding_fee: 1 close_value: - 2.1 quote: (30.00 * 2.1) - (30.00 * 2.1 * 0.003) = 62.811 - 1.9 quote: (30.00 * 1.9) - (30.00 * 1.9 * 0.003) = 56.829 - 2.2 quote: (30.00 * 2.2) - (30.00 * 2.2 * 0.003) = 65.802 - total_profit - fee: 0.003, 1x - 2.1 quote: 62.811 - 60.15 = 2.6610000000000014 - 1.9 quote: 56.829 - 60.15 = -3.320999999999998 - 2.2 quote: 65.802 - 60.15 = 5.652000000000008 - total_profit_ratio - fee: 0.003, 1x - 2.1 quote: (62.811 / 60.15) - 1 = 0.04423940149625927 - 1.9 quote: (56.829 / 60.15) - 1 = -0.05521197007481293 - 2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565 - futures (live): - funding_fee: 1 - close_value: - equations: - 1x,3x: (amount * close_rate) - (amount * close_rate * fee) + funding_fees - -1x,-3x: (amount * close_rate) + (amount * close_rate * fee) - funding_fees - 2.1 quote - 1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + 1 = 63.8425 - -1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - 1 = 62.1575 - 1.9 quote - 1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + 1 = 57.8575 - -1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - 1 = 56.1425 - 2.2 quote: - 1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + 1 = 66.835 - -1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - 1 = 65.165 - total_profit: - 2.1 quote - 1x,3x: 63.8425 - 60.15 = 3.6925 - -1x,-3x: 59.850 - 62.1575 = -2.3074999999999974 - 1.9 quote - 1x,3x: 57.8575 - 60.15 = -2.2925 - -1x,-3x: 59.850 - 56.1425 = 3.707500000000003 - 2.2 quote: - 1x,3x: 66.835 - 60.15 = 6.685 - -1x,-3x: 59.850 - 65.165 = -5.315000000000005 - total_profit_ratio: - 2.1 quote - 1x: (63.8425 / 60.15) - 1 = 0.06138819617622615 - 3x: ((63.8425 / 60.15) - 1)*3 = 0.18416458852867845 - -1x: 1 - (62.1575 / 59.850) = -0.038554720133667564 - -3x: (1 - (62.1575 / 59.850))*3 = -0.11566416040100269 - 1.9 quote - 1x: (57.8575 / 60.15) - 1 = -0.0381130507065669 - 3x: ((57.8575 / 60.15) - 1)*3 = -0.1143391521197007 - -1x: 1 - (56.1425 / 59.850) = 0.06194653299916464 - -3x: (1 - (56.1425 / 59.850))*3 = 0.18583959899749392 - 2.2 quote - 1x: (66.835 / 60.15) - 1 = 0.11113881961762262 - 3x: ((66.835 / 60.15) - 1)*3 = 0.33341645885286786 - -1x: 1 - (65.165 / 59.850) = -0.08880534670008355 - -3x: (1 - (65.165 / 59.850))*3 = -0.26641604010025066 - funding_fee: -1 - close_value: - equations: - (amount * close_rate) - (amount * close_rate * fee) + funding_fees - (amount * close_rate) - (amount * close_rate * fee) - funding_fees - 2.1 quote - 1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + (-1) = 61.8425 - -1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - (-1) = 64.1575 - 1.9 quote - 1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + (-1) = 55.8575 - -1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - (-1) = 58.1425 - 2.2 quote: - 1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + (-1) = 64.835 - -1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - (-1) = 67.165 - total_profit: - 2.1 quote - 1x,3x: 61.8425 - 60.15 = 1.6925000000000026 - -1x,-3x: 59.850 - 64.1575 = -4.307499999999997 - 1.9 quote - 1x,3x: 55.8575 - 60.15 = -4.292499999999997 - -1x,-3x: 59.850 - 58.1425 = 1.7075000000000031 - 2.2 quote: - 1x,3x: 64.835 - 60.15 = 4.684999999999995 - -1x,-3x: 59.850 - 67.165 = -7.315000000000005 - total_profit_ratio: - 2.1 quote - 1x: (61.8425 / 60.15) - 1 = 0.028137988362427313 - 3x: ((61.8425 / 60.15) - 1)*3 = 0.08441396508728194 - -1x: 1 - (64.1575 / 59.850) = -0.07197159565580624 - -3x: (1 - (64.1575 / 59.850))*3 = -0.21591478696741873 - 1.9 quote - 1x: (55.8575 / 60.15) - 1 = -0.07136325852036574 - 3x: ((55.8575 / 60.15) - 1)*3 = -0.2140897755610972 - -1x: 1 - (58.1425 / 59.850) = 0.02852965747702596 - -3x: (1 - (58.1425 / 59.850))*3 = 0.08558897243107788 - 2.2 quote - 1x: (64.835 / 60.15) - 1 = 0.07788861180382378 - 3x: ((64.835 / 60.15) - 1)*3 = 0.23366583541147135 - -1x: 1 - (67.165 / 59.850) = -0.12222222222222223 - -3x: (1 - (67.165 / 59.850))*3 = -0.3666666666666667 + equations: + 1x,3x: (amount * close_rate) - (amount * close_rate * fee) + funding_fees + -1x,-3x: (amount * close_rate) + (amount * close_rate * fee) - funding_fees + 2.1 quote + 1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + 1 = 63.8425 + -1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - 1 = 62.1575 + 1.9 quote + 1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + 1 = 57.8575 + -1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - 1 = 56.1425 + 2.2 quote: + 1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + 1 = 66.835 + -1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - 1 = 65.165 + total_profit: + 2.1 quote + 1x,3x: 63.8425 - 60.15 = 3.6925 + -1x,-3x: 59.850 - 62.1575 = -2.3074999999999974 + 1.9 quote + 1x,3x: 57.8575 - 60.15 = -2.2925 + -1x,-3x: 59.850 - 56.1425 = 3.707500000000003 + 2.2 quote: + 1x,3x: 66.835 - 60.15 = 6.685 + -1x,-3x: 59.850 - 65.165 = -5.315000000000005 + total_profit_ratio: + 2.1 quote + 1x: (63.8425 / 60.15) - 1 = 0.06138819617622615 + 3x: ((63.8425 / 60.15) - 1)*3 = 0.18416458852867845 + -1x: 1 - (62.1575 / 59.850) = -0.038554720133667564 + -3x: (1 - (62.1575 / 59.850))*3 = -0.11566416040100269 + 1.9 quote + 1x: (57.8575 / 60.15) - 1 = -0.0381130507065669 + 3x: ((57.8575 / 60.15) - 1)*3 = -0.1143391521197007 + -1x: 1 - (56.1425 / 59.850) = 0.06194653299916464 + -3x: (1 - (56.1425 / 59.850))*3 = 0.18583959899749392 + 2.2 quote + 1x: (66.835 / 60.15) - 1 = 0.11113881961762262 + 3x: ((66.835 / 60.15) - 1)*3 = 0.33341645885286786 + -1x: 1 - (65.165 / 59.850) = -0.08880534670008355 + -3x: (1 - (65.165 / 59.850))*3 = -0.26641604010025066 + funding_fee: -1 + close_value: + equations: + (amount * close_rate) - (amount * close_rate * fee) + funding_fees + (amount * close_rate) - (amount * close_rate * fee) - funding_fees + 2.1 quote + 1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + (-1) = 61.8425 + -1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - (-1) = 64.1575 + 1.9 quote + 1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + (-1) = 55.8575 + -1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - (-1) = 58.1425 + 2.2 quote: + 1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + (-1) = 64.835 + -1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - (-1) = 67.165 + total_profit: + 2.1 quote + 1x,3x: 61.8425 - 60.15 = 1.6925000000000026 + -1x,-3x: 59.850 - 64.1575 = -4.307499999999997 + 1.9 quote + 1x,3x: 55.8575 - 60.15 = -4.292499999999997 + -1x,-3x: 59.850 - 58.1425 = 1.7075000000000031 + 2.2 quote: + 1x,3x: 64.835 - 60.15 = 4.684999999999995 + -1x,-3x: 59.850 - 67.165 = -7.315000000000005 + total_profit_ratio: + 2.1 quote + 1x: (61.8425 / 60.15) - 1 = 0.028137988362427313 + 3x: ((61.8425 / 60.15) - 1)*3 = 0.08441396508728194 + -1x: 1 - (64.1575 / 59.850) = -0.07197159565580624 + -3x: (1 - (64.1575 / 59.850))*3 = -0.21591478696741873 + 1.9 quote + 1x: (55.8575 / 60.15) - 1 = -0.07136325852036574 + 3x: ((55.8575 / 60.15) - 1)*3 = -0.2140897755610972 + -1x: 1 - (58.1425 / 59.850) = 0.02852965747702596 + -3x: (1 - (58.1425 / 59.850))*3 = 0.08558897243107788 + 2.2 quote + 1x: (64.835 / 60.15) - 1 = 0.07788861180382378 + 3x: ((64.835 / 60.15) - 1)*3 = 0.23366583541147135 + -1x: 1 - (67.165 / 59.850) = -0.12222222222222223 + -3x: (1 - (67.165 / 59.850))*3 = -0.3666666666666667 """ trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=60.0, amount=30.0, open_rate=2.0, @@ -1159,7 +1172,7 @@ def test_calc_profit( fee_close=fee_close, max_stake_amount=60.0, trading_mode=trading_mode, - funding_fees=funding_fees + funding_fees=funding_fees, ) profit_res = trade.calculate_profit(close_rate) @@ -1181,20 +1194,22 @@ def test_calc_profit( assert pytest.approx(profit_res2.total_profit) == round(profit, 8) # assert pytest.approx(profit_res2.total_profit_ratio) == round(profit_ratio, 8) - assert pytest.approx(trade.calc_profit(close_rate, trade.amount, - trade.open_rate)) == round(profit, 8) - assert pytest.approx(trade.calc_profit_ratio(close_rate, trade.amount, - trade.open_rate)) == round(profit_ratio, 8) + assert pytest.approx(trade.calc_profit(close_rate, trade.amount, trade.open_rate)) == round( + profit, 8 + ) + assert pytest.approx( + trade.calc_profit_ratio(close_rate, trade.amount, trade.open_rate) + ) == round(profit_ratio, 8) def test_adjust_stop_loss(fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, amount=30, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, ) @@ -1241,12 +1256,12 @@ def test_adjust_stop_loss(fee): def test_adjust_stop_loss_short(fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=0.001, amount=5, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, is_short=True, @@ -1295,12 +1310,12 @@ def test_adjust_stop_loss_short(fee): def test_adjust_min_max_rates(fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, amount=30.0, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, ) @@ -1330,8 +1345,8 @@ def test_adjust_min_max_rates(fee): @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('use_db', [True, False]) -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("use_db", [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_get_open(fee, is_short, use_db): Trade.use_db = use_db Trade.reset_trades() @@ -1344,7 +1359,7 @@ def test_get_open(fee, is_short, use_db): @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('use_db', [True, False]) +@pytest.mark.parametrize("use_db", [True, False]) def test_get_open_lev(fee, use_db): Trade.use_db = use_db Trade.reset_trades() @@ -1356,8 +1371,8 @@ def test_get_open_lev(fee, use_db): Trade.use_db = True -@pytest.mark.parametrize('is_short', [True, False]) -@pytest.mark.parametrize('use_db', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) +@pytest.mark.parametrize("use_db", [True, False]) @pytest.mark.usefixtures("init_persistence") def test_get_open_orders(fee, is_short, use_db): Trade.use_db = use_db @@ -1376,10 +1391,9 @@ def test_get_open_orders(fee, is_short, use_db): @pytest.mark.usefixtures("init_persistence") def test_to_json(fee): - # Simulate dry_run entries trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=0.001, amount=123.0, amount_requested=123.0, @@ -1387,7 +1401,7 @@ def test_to_json(fee): fee_close=fee.return_value, open_date=dt_now() - timedelta(hours=2), open_rate=0.123, - exchange='binance', + exchange="binance", enter_tag=None, precision_mode=1, amount_precision=8.0, @@ -1398,75 +1412,75 @@ def test_to_json(fee): assert isinstance(result, dict) assert result == { - 'trade_id': None, - 'pair': 'ADA/USDT', - 'base_currency': 'ADA', - 'quote_currency': 'USDT', - 'is_open': None, - 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), - 'open_timestamp': int(trade.open_date.timestamp() * 1000), - 'open_fill_date': None, - 'open_fill_timestamp': None, - 'close_date': None, - 'close_timestamp': None, - 'open_rate': 0.123, - 'open_rate_requested': None, - 'open_trade_value': 15.1668225, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'close_rate': None, - 'close_rate_requested': None, - 'amount': 123.0, - 'amount_requested': 123.0, - 'stake_amount': 0.001, - 'max_stake_amount': None, - 'trade_duration': None, - 'trade_duration_s': None, - 'realized_profit': 0.0, - 'realized_profit_ratio': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'profit_ratio': None, - 'profit_pct': None, - 'profit_abs': None, - 'exit_reason': None, - 'exit_order_status': None, - 'stop_loss_abs': None, - 'stop_loss_ratio': None, - 'stop_loss_pct': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss_abs': None, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'min_rate': None, - 'max_rate': None, - 'strategy': None, - 'enter_tag': None, - 'timeframe': None, - 'exchange': 'binance', - 'leverage': None, - 'interest_rate': None, - 'liquidation_price': None, - 'is_short': None, - 'trading_mode': None, - 'funding_fees': None, - 'amount_precision': 8.0, - 'price_precision': 7.0, - 'precision_mode': 1, - 'contract_size': 1, - 'orders': [], - 'has_open_orders': False, + "trade_id": None, + "pair": "ADA/USDT", + "base_currency": "ADA", + "quote_currency": "USDT", + "is_open": None, + "open_date": trade.open_date.strftime(DATETIME_PRINT_FORMAT), + "open_timestamp": int(trade.open_date.timestamp() * 1000), + "open_fill_date": None, + "open_fill_timestamp": None, + "close_date": None, + "close_timestamp": None, + "open_rate": 0.123, + "open_rate_requested": None, + "open_trade_value": 15.1668225, + "fee_close": 0.0025, + "fee_close_cost": None, + "fee_close_currency": None, + "fee_open": 0.0025, + "fee_open_cost": None, + "fee_open_currency": None, + "close_rate": None, + "close_rate_requested": None, + "amount": 123.0, + "amount_requested": 123.0, + "stake_amount": 0.001, + "max_stake_amount": None, + "trade_duration": None, + "trade_duration_s": None, + "realized_profit": 0.0, + "realized_profit_ratio": None, + "close_profit": None, + "close_profit_pct": None, + "close_profit_abs": None, + "profit_ratio": None, + "profit_pct": None, + "profit_abs": None, + "exit_reason": None, + "exit_order_status": None, + "stop_loss_abs": None, + "stop_loss_ratio": None, + "stop_loss_pct": None, + "stoploss_last_update": None, + "stoploss_last_update_timestamp": None, + "initial_stop_loss_abs": None, + "initial_stop_loss_pct": None, + "initial_stop_loss_ratio": None, + "min_rate": None, + "max_rate": None, + "strategy": None, + "enter_tag": None, + "timeframe": None, + "exchange": "binance", + "leverage": None, + "interest_rate": None, + "liquidation_price": None, + "is_short": None, + "trading_mode": None, + "funding_fees": None, + "amount_precision": 8.0, + "price_precision": 7.0, + "precision_mode": 1, + "contract_size": 1, + "orders": [], + "has_open_orders": False, } # Simulate dry_run entries trade = Trade( - pair='XRP/BTC', + pair="XRP/BTC", stake_amount=0.001, amount=100.0, amount_requested=101.0, @@ -1476,94 +1490,94 @@ def test_to_json(fee): close_date=dt_now() - timedelta(hours=1), open_rate=0.123, close_rate=0.125, - enter_tag='buys_signal_001', - exchange='binance', + enter_tag="buys_signal_001", + exchange="binance", precision_mode=2, amount_precision=7.0, price_precision=8.0, - contract_size=1 + contract_size=1, ) result = trade.to_json() assert isinstance(result, dict) assert result == { - 'trade_id': None, - 'pair': 'XRP/BTC', - 'base_currency': 'XRP', - 'quote_currency': 'BTC', - 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), - 'open_timestamp': int(trade.open_date.timestamp() * 1000), - 'open_fill_date': None, - 'open_fill_timestamp': None, - 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), - 'close_timestamp': int(trade.close_date.timestamp() * 1000), - 'open_rate': 0.123, - 'close_rate': 0.125, - 'amount': 100.0, - 'amount_requested': 101.0, - 'stake_amount': 0.001, - 'max_stake_amount': None, - 'trade_duration': 60, - 'trade_duration_s': 3600, - 'stop_loss_abs': None, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss_abs': None, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'realized_profit': 0.0, - 'realized_profit_ratio': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'profit_ratio': None, - 'profit_pct': None, - 'profit_abs': None, - 'close_rate_requested': None, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'is_open': None, - 'max_rate': None, - 'min_rate': None, - 'open_rate_requested': None, - 'open_trade_value': 12.33075, - 'exit_reason': None, - 'exit_order_status': None, - 'strategy': None, - 'enter_tag': 'buys_signal_001', - 'timeframe': None, - 'exchange': 'binance', - 'leverage': None, - 'interest_rate': None, - 'liquidation_price': None, - 'is_short': None, - 'trading_mode': None, - 'funding_fees': None, - 'amount_precision': 7.0, - 'price_precision': 8.0, - 'precision_mode': 2, - 'contract_size': 1, - 'orders': [], - 'has_open_orders': False, + "trade_id": None, + "pair": "XRP/BTC", + "base_currency": "XRP", + "quote_currency": "BTC", + "open_date": trade.open_date.strftime(DATETIME_PRINT_FORMAT), + "open_timestamp": int(trade.open_date.timestamp() * 1000), + "open_fill_date": None, + "open_fill_timestamp": None, + "close_date": trade.close_date.strftime(DATETIME_PRINT_FORMAT), + "close_timestamp": int(trade.close_date.timestamp() * 1000), + "open_rate": 0.123, + "close_rate": 0.125, + "amount": 100.0, + "amount_requested": 101.0, + "stake_amount": 0.001, + "max_stake_amount": None, + "trade_duration": 60, + "trade_duration_s": 3600, + "stop_loss_abs": None, + "stop_loss_pct": None, + "stop_loss_ratio": None, + "stoploss_last_update": None, + "stoploss_last_update_timestamp": None, + "initial_stop_loss_abs": None, + "initial_stop_loss_pct": None, + "initial_stop_loss_ratio": None, + "realized_profit": 0.0, + "realized_profit_ratio": None, + "close_profit": None, + "close_profit_pct": None, + "close_profit_abs": None, + "profit_ratio": None, + "profit_pct": None, + "profit_abs": None, + "close_rate_requested": None, + "fee_close": 0.0025, + "fee_close_cost": None, + "fee_close_currency": None, + "fee_open": 0.0025, + "fee_open_cost": None, + "fee_open_currency": None, + "is_open": None, + "max_rate": None, + "min_rate": None, + "open_rate_requested": None, + "open_trade_value": 12.33075, + "exit_reason": None, + "exit_order_status": None, + "strategy": None, + "enter_tag": "buys_signal_001", + "timeframe": None, + "exchange": "binance", + "leverage": None, + "interest_rate": None, + "liquidation_price": None, + "is_short": None, + "trading_mode": None, + "funding_fees": None, + "amount_precision": 7.0, + "price_precision": 8.0, + "precision_mode": 2, + "contract_size": 1, + "orders": [], + "has_open_orders": False, } def test_stoploss_reinitialization(default_conf, fee): - init_db(default_conf['db_url']) + init_db(default_conf["db_url"]) trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, fee_open=fee.return_value, open_date=dt_now() - timedelta(hours=2), amount=30.0, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, ) @@ -1616,15 +1630,15 @@ def test_stoploss_reinitialization(default_conf, fee): def test_stoploss_reinitialization_leverage(default_conf, fee): - init_db(default_conf['db_url']) + init_db(default_conf["db_url"]) trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, fee_open=fee.return_value, open_date=dt_now() - timedelta(hours=2), amount=30.0, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, leverage=5.0, @@ -1678,15 +1692,15 @@ def test_stoploss_reinitialization_leverage(default_conf, fee): def test_stoploss_reinitialization_short(default_conf, fee): - init_db(default_conf['db_url']) + init_db(default_conf["db_url"]) trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=0.001, fee_open=fee.return_value, open_date=dt_now() - timedelta(hours=2), amount=10, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, is_short=True, @@ -1739,26 +1753,26 @@ def test_stoploss_reinitialization_short(default_conf, fee): def test_update_fee(fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, fee_open=fee.return_value, open_date=dt_now() - timedelta(hours=2), amount=30.0, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, ) fee_cost = 0.15 - fee_currency = 'BTC' + fee_currency = "BTC" fee_rate = 0.0075 assert trade.fee_open_currency is None - assert not trade.fee_updated('buy') - assert not trade.fee_updated('sell') + assert not trade.fee_updated("buy") + assert not trade.fee_updated("sell") - trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy') - assert trade.fee_updated('buy') - assert not trade.fee_updated('sell') + trade.update_fee(fee_cost, fee_currency, fee_rate, "buy") + assert trade.fee_updated("buy") + assert not trade.fee_updated("sell") assert trade.fee_open_currency == fee_currency assert trade.fee_open_cost == fee_cost assert trade.fee_open == fee_rate @@ -1768,9 +1782,9 @@ def test_update_fee(fee): assert trade.fee_close_cost is None fee_rate = 0.0076 - trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell') - assert trade.fee_updated('buy') - assert trade.fee_updated('sell') + trade.update_fee(fee_cost, fee_currency, fee_rate, "sell") + assert trade.fee_updated("buy") + assert trade.fee_updated("sell") assert trade.fee_close == 0.0076 assert trade.fee_close_cost == fee_cost assert trade.fee_close == fee_rate @@ -1778,39 +1792,38 @@ def test_update_fee(fee): def test_fee_updated(fee): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=30.0, fee_open=fee.return_value, open_date=dt_now() - timedelta(hours=2), amount=30.0, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=1, max_rate=1, ) assert trade.fee_open_currency is None - assert not trade.fee_updated('buy') - assert not trade.fee_updated('sell') - assert not trade.fee_updated('asdf') + assert not trade.fee_updated("buy") + assert not trade.fee_updated("sell") + assert not trade.fee_updated("asdf") - trade.update_fee(0.15, 'BTC', 0.0075, 'buy') - assert trade.fee_updated('buy') - assert not trade.fee_updated('sell') + trade.update_fee(0.15, "BTC", 0.0075, "buy") + assert trade.fee_updated("buy") + assert not trade.fee_updated("sell") assert trade.fee_open_currency is not None assert trade.fee_close_currency is None - trade.update_fee(0.15, 'ABC', 0.0075, 'sell') - assert trade.fee_updated('buy') - assert trade.fee_updated('sell') - assert not trade.fee_updated('asfd') + trade.update_fee(0.15, "ABC", 0.0075, "sell") + assert trade.fee_updated("buy") + assert trade.fee_updated("sell") + assert not trade.fee_updated("asfd") @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short', [True, False]) -@pytest.mark.parametrize('use_db', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) +@pytest.mark.parametrize("use_db", [True, False]) def test_total_open_trades_stakes(fee, is_short, use_db): - Trade.use_db = use_db Trade.reset_trades() res = Trade.total_open_trades_stakes() @@ -1823,14 +1836,16 @@ def test_total_open_trades_stakes(fee, is_short, use_db): @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short,result', [ - (True, -0.006739127), - (False, 0.000739127), - (None, -0.005429127), -]) -@pytest.mark.parametrize('use_db', [True, False]) +@pytest.mark.parametrize( + "is_short,result", + [ + (True, -0.006739127), + (False, 0.000739127), + (None, -0.005429127), + ], +) +@pytest.mark.parametrize("use_db", [True, False]) def test_get_total_closed_profit(fee, use_db, is_short, result): - Trade.use_db = use_db Trade.reset_trades() res = Trade.get_total_closed_profit() @@ -1843,8 +1858,8 @@ def test_get_total_closed_profit(fee, use_db, is_short, result): @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short', [True, False]) -@pytest.mark.parametrize('use_db', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) +@pytest.mark.parametrize("use_db", [True, False]) def test_get_trades_proxy(fee, use_db, is_short): Trade.use_db = use_db Trade.reset_trades() @@ -1870,7 +1885,7 @@ def test_get_trades_proxy(fee, use_db, is_short): @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_get_trades__query(fee, is_short): query = Trade.get_trades_query([]) # without orders there should be no join issued. @@ -1898,24 +1913,25 @@ def test_get_trades_backtest(): @pytest.mark.usefixtures("init_persistence") # @pytest.mark.parametrize('is_short', [True, False]) def test_get_overall_performance(fee): - create_mock_trades(fee, False) res = Trade.get_overall_performance() assert len(res) == 2 - assert 'pair' in res[0] - assert 'profit' in res[0] - assert 'count' in res[0] + assert "pair" in res[0] + assert "profit" in res[0] + assert "count" in res[0] @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short,pair,profit', [ - (True, 'ETC/BTC', -0.005), - (False, 'XRP/BTC', 0.01), - (None, 'XRP/BTC', 0.01), -]) +@pytest.mark.parametrize( + "is_short,pair,profit", + [ + (True, "ETC/BTC", -0.005), + (False, "XRP/BTC", 0.01), + (None, "XRP/BTC", 0.01), + ], +) def test_get_best_pair(fee, is_short, pair, profit): - res = Trade.get_best_pair() assert res is None @@ -1928,27 +1944,25 @@ def test_get_best_pair(fee, is_short, pair, profit): @pytest.mark.usefixtures("init_persistence") def test_get_best_pair_lev(fee): - res = Trade.get_best_pair() assert res is None create_mock_trades_with_leverage(fee) res = Trade.get_best_pair() assert len(res) == 2 - assert res[0] == 'DOGE/BTC' + assert res[0] == "DOGE/BTC" assert res[1] == 0.1713156134055116 @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_get_canceled_exit_order_count(fee, is_short): - create_mock_trades(fee, is_short=is_short) - trade = Trade.get_trades([Trade.pair == 'ETC/BTC']).first() + trade = Trade.get_trades([Trade.pair == "ETC/BTC"]).first() # No canceled order. assert trade.get_canceled_exit_order_count() == 0 - trade.orders[-1].status = 'canceled' + trade.orders[-1].status = "canceled" assert trade.get_canceled_exit_order_count() == 1 @@ -1958,32 +1972,32 @@ def test_update_order_from_ccxt(caplog, time_machine): time_machine.move_to(start, tick=False) # Most basic order return (only has orderid) - o = Order.parse_from_ccxt_object({'id': '1234'}, 'ADA/USDT', 'buy', 20.01, 1234.6) + o = Order.parse_from_ccxt_object({"id": "1234"}, "ADA/USDT", "buy", 20.01, 1234.6) assert isinstance(o, Order) - assert o.ft_pair == 'ADA/USDT' - assert o.ft_order_side == 'buy' - assert o.order_id == '1234' + assert o.ft_pair == "ADA/USDT" + assert o.ft_order_side == "buy" + assert o.order_id == "1234" assert o.ft_price == 1234.6 assert o.ft_amount == 20.01 assert o.ft_is_open ccxt_order = { - 'id': '1234', - 'side': 'buy', - 'symbol': 'ADA/USDT', - 'type': 'limit', - 'price': 1234.5, - 'amount': 20.0, - 'filled': 9, - 'remaining': 11, - 'status': 'open', - 'timestamp': 1599394315123 + "id": "1234", + "side": "buy", + "symbol": "ADA/USDT", + "type": "limit", + "price": 1234.5, + "amount": 20.0, + "filled": 9, + "remaining": 11, + "status": "open", + "timestamp": 1599394315123, } - o = Order.parse_from_ccxt_object(ccxt_order, 'ADA/USDT', 'buy', 20.01, 1234.6) + o = Order.parse_from_ccxt_object(ccxt_order, "ADA/USDT", "buy", 20.01, 1234.6) assert isinstance(o, Order) - assert o.ft_pair == 'ADA/USDT' - assert o.ft_order_side == 'buy' - assert o.order_id == '1234' - assert o.order_type == 'limit' + assert o.ft_pair == "ADA/USDT" + assert o.ft_order_side == "buy" + assert o.order_id == "1234" + assert o.order_type == "limit" assert o.price == 1234.5 assert o.ft_price == 1234.6 assert o.ft_amount == 20.01 @@ -1995,11 +2009,11 @@ def test_update_order_from_ccxt(caplog, time_machine): # Order is unfilled, "filled" not set # https://github.com/freqtrade/freqtrade/issues/5404 - ccxt_order.update({'filled': None, 'remaining': 20.0, 'status': 'canceled'}) + ccxt_order.update({"filled": None, "remaining": 20.0, "status": "canceled"}) o.update_from_ccxt_object(ccxt_order) # Order has been closed - ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'}) + ccxt_order.update({"filled": 20.0, "remaining": 0.0, "status": "closed"}) o.update_from_ccxt_object(ccxt_order) assert o.filled == 20.0 @@ -2009,27 +2023,27 @@ def test_update_order_from_ccxt(caplog, time_machine): # Move time time_machine.move_to(start + timedelta(hours=1), tick=False) - ccxt_order.update({'id': 'somethingelse'}) + ccxt_order.update({"id": "somethingelse"}) with pytest.raises(DependencyException, match=r"Order-id's don't match"): o.update_from_ccxt_object(ccxt_order) message = "aaaa is not a valid response object." assert not log_has(message, caplog) - Order.update_orders([o], 'aaaa') + Order.update_orders([o], "aaaa") assert log_has(message, caplog) # Call regular update - shouldn't fail. - Order.update_orders([o], {'id': '1234'}) + Order.update_orders([o], {"id": "1234"}) assert o.order_filled_date == start # Fill order again - shouldn't update filled date - ccxt_order.update({'id': '1234'}) + ccxt_order.update({"id": "1234"}) Order.update_orders([o], ccxt_order) assert o.order_filled_date == start @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_select_order(fee, is_short): create_mock_trades(fee, is_short) @@ -2071,85 +2085,91 @@ def test_select_order(fee, is_short): order = trades[4].select_order(trades[4].exit_side, True) assert order is not None - trades[4].orders[1].ft_order_side = 'stoploss' - order = trades[4].select_order('stoploss', None) + trades[4].orders[1].ft_order_side = "stoploss" + order = trades[4].select_order("stoploss", None) assert order is not None - assert order.ft_order_side == 'stoploss' + assert order.ft_order_side == "stoploss" def test_Trade_object_idem(): - assert issubclass(Trade, LocalTrade) trade = vars(Trade) localtrade = vars(LocalTrade) excludes = ( - 'delete', - 'session', - 'commit', - 'rollback', - 'query', - 'open_date', - 'get_best_pair', - 'get_overall_performance', - 'get_total_closed_profit', - 'total_open_trades_stakes', - 'get_closed_trades_without_assigned_fees', - 'get_open_trades_without_assigned_fees', - 'get_trades', - 'get_trades_query', - 'get_exit_reason_performance', - 'get_enter_tag_performance', - 'get_mix_tag_performance', - 'get_trading_volume', - 'validate_string_len', - 'custom_data' + "delete", + "session", + "commit", + "rollback", + "query", + "open_date", + "get_best_pair", + "get_overall_performance", + "get_total_closed_profit", + "total_open_trades_stakes", + "get_closed_trades_without_assigned_fees", + "get_open_trades_without_assigned_fees", + "get_trades", + "get_trades_query", + "get_exit_reason_performance", + "get_enter_tag_performance", + "get_mix_tag_performance", + "get_trading_volume", + "validate_string_len", + "custom_data", + ) + EXCLUDES2 = ( + "trades", + "trades_open", + "bt_trades_open_pp", + "bt_open_open_trade_count", + "total_profit", + "from_json", ) - EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count', - 'total_profit', 'from_json',) # Parent (LocalTrade) should have the same attributes for item in trade: # Exclude private attributes and open_date (as it's not assigned a default) - if (not item.startswith('_') and item not in excludes): + if not item.startswith("_") and item not in excludes: assert item in localtrade # Fails if only a column is added without corresponding parent field for item in localtrade: - if (not item.startswith('__') - and item not in EXCLUDES2 - and type(getattr(LocalTrade, item)) not in (property, FunctionType)): + if ( + not item.startswith("__") + and item not in EXCLUDES2 + and type(getattr(LocalTrade, item)) not in (property, FunctionType) + ): assert item in trade @pytest.mark.usefixtures("init_persistence") def test_trade_truncates_string_fields(): trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=20.0, amount=30.0, open_rate=2.0, open_date=datetime.now(timezone.utc) - timedelta(minutes=20), fee_open=0.001, fee_close=0.001, - exchange='binance', + exchange="binance", leverage=1.0, - trading_mode='futures', - enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2, - exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2, + 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 + 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 o1_rate = 1 o1_cost = o1_amount * o1_rate @@ -2157,13 +2177,13 @@ def test_recalc_trade_from_orders(fee): o1_trade_val = o1_cost + o1_fee_cost trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=o1_cost, open_date=dt_now() - timedelta(hours=2), amount=o1_amount, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=o1_rate, max_rate=o1_rate, leverage=1, @@ -2183,13 +2203,13 @@ def test_recalc_trade_from_orders(fee): assert trade.open_rate == o1_rate assert trade.open_trade_value == o1_trade_val - trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, 'buy') + trade.update_fee(o1_fee_cost, "BNB", fee.return_value, "buy") assert len(trade.orders) == 0 # Check with 1 order order1 = Order( - ft_order_side='buy', + ft_order_side="buy", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2222,7 +2242,7 @@ def test_recalc_trade_from_orders(fee): o2_trade_val = o2_cost + o2_fee_cost order2 = Order( - ft_order_side='buy', + ft_order_side="buy", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2256,7 +2276,7 @@ def test_recalc_trade_from_orders(fee): o3_trade_val = o3_cost + o3_fee_cost order3 = Order( - ft_order_side='buy', + ft_order_side="buy", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2285,7 +2305,7 @@ def test_recalc_trade_from_orders(fee): # Just to make sure full sell orders are ignored, let's calculate one more time. sell1 = Order( - ft_order_side='sell', + ft_order_side="sell", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2327,20 +2347,20 @@ def test_recalc_trade_from_orders_kucoin(): o1_cost = o1_amount * o1_rate trade = Trade( - pair='FLOKI/USDT', + pair="FLOKI/USDT", stake_amount=o1_cost, open_date=dt_now() - timedelta(hours=2), amount=o1_amount, fee_open=0.001, fee_close=0.001, - exchange='binance', + exchange="binance", open_rate=o1_rate, max_rate=o1_rate, leverage=1, ) # Check with 1 order order1 = Order( - ft_order_side='buy', + ft_order_side="buy", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2357,7 +2377,7 @@ def test_recalc_trade_from_orders_kucoin(): ) trade.orders.append(order1) order2 = Order( - ft_order_side='buy', + ft_order_side="buy", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2380,7 +2400,7 @@ def test_recalc_trade_from_orders_kucoin(): assert profit.profit_ratio == pytest.approx(0.00566035) order3 = Order( - ft_order_side='sell', + ft_order_side="sell", ft_pair=trade.pair, ft_is_open=False, status="closed", @@ -2405,9 +2425,8 @@ def test_recalc_trade_from_orders_kucoin(): assert pytest.approx(trade.close_profit) == 0.00566035 -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short): - o1_amount = 100 o1_rate = 1 o1_cost = o1_amount * o1_rate @@ -2417,19 +2436,19 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short): exit_side = "buy" if is_short else "sell" trade = Trade( - pair='ADA/USDT', + pair="ADA/USDT", stake_amount=o1_cost, open_date=dt_now() - timedelta(hours=2), amount=o1_amount, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange="binance", open_rate=o1_rate, max_rate=o1_rate, is_short=is_short, leverage=1.0, ) - trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, entry_side) + trade.update_fee(o1_fee_cost, "BNB", fee.return_value, entry_side) # Check with 1 order order1 = Order( ft_order_side=entry_side, @@ -2603,169 +2622,170 @@ def test_select_filled_orders(fee): trades = Trade.get_trades().all() # Closed buy order, no sell order - orders = trades[0].select_filled_orders('buy') + orders = trades[0].select_filled_orders("buy") assert isinstance(orders, list) assert len(orders) == 0 - orders = trades[0].select_filled_orders('sell') + orders = trades[0].select_filled_orders("sell") assert orders is not None assert len(orders) == 0 # closed buy order, and closed sell order - orders = trades[1].select_filled_orders('buy') + orders = trades[1].select_filled_orders("buy") assert isinstance(orders, list) assert len(orders) == 1 order = orders[0] assert order.amount > 0 assert order.filled > 0 - assert order.side == 'buy' - assert order.ft_order_side == 'buy' - assert order.status == 'closed' + assert order.side == "buy" + assert order.ft_order_side == "buy" + assert order.status == "closed" - orders = trades[1].select_filled_orders('sell') + orders = trades[1].select_filled_orders("sell") assert isinstance(orders, list) assert len(orders) == 1 # Has open buy order - orders = trades[3].select_filled_orders('buy') + orders = trades[3].select_filled_orders("buy") assert isinstance(orders, list) assert len(orders) == 0 - orders = trades[3].select_filled_orders('sell') + orders = trades[3].select_filled_orders("sell") assert isinstance(orders, list) assert len(orders) == 0 # Open sell order - orders = trades[4].select_filled_orders('buy') + orders = trades[4].select_filled_orders("buy") assert isinstance(orders, list) assert len(orders) == 1 - orders = trades[4].select_filled_orders('sell') + orders = trades[4].select_filled_orders("sell") assert isinstance(orders, list) assert len(orders) == 0 @pytest.mark.usefixtures("init_persistence") def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open): - - order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy') + order = Order.parse_from_ccxt_object(limit_buy_order_open, "mocked", "buy") order.ft_trade_id = 1 order.session.add(order) Order.session.commit() - order_resp = Order.order_by_id(limit_buy_order_open['id']) + order_resp = Order.order_by_id(limit_buy_order_open["id"]) assert order_resp raw_order = order_resp.to_ccxt_object() - del raw_order['fee'] - del raw_order['datetime'] - del raw_order['info'] - assert raw_order.get('stopPrice') is None - raw_order.pop('stopPrice', None) - del limit_buy_order_open['datetime'] + del raw_order["fee"] + del raw_order["datetime"] + del raw_order["info"] + assert raw_order.get("stopPrice") is None + raw_order.pop("stopPrice", None) + del limit_buy_order_open["datetime"] assert raw_order == limit_buy_order_open - order1 = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'mocked', 'sell') - order1.ft_order_side = 'stoploss' + order1 = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, "mocked", "sell") + order1.ft_order_side = "stoploss" order1.stop_price = order1.price * 0.9 order1.ft_trade_id = 1 order1.session.add(order1) Order.session.commit() - order_resp1 = Order.order_by_id(limit_sell_order_usdt_open['id']) + order_resp1 = Order.order_by_id(limit_sell_order_usdt_open["id"]) raw_order1 = order_resp1.to_ccxt_object() - assert raw_order1.get('stopPrice') is not None + assert raw_order1.get("stopPrice") is not None @pytest.mark.usefixtures("init_persistence") -@pytest.mark.parametrize('data', [ - # tuple 1 - side, amount, price - # tuple 2 - amount, open_rate, stake_amount, cumulative_profit, realized_profit, rel_profit - { - 'orders': [ - (('buy', 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)), - (('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), - (('sell', 50, 12), (150.0, 12.5, 1875.0, -25.0, -25.0, -0.01)), - (('sell', 100, 20), (50.0, 12.5, 625.0, 725.0, 750.0, 0.29)), - (('sell', 50, 5), (50.0, 12.5, 625.0, 350.0, -375.0, 0.14)), - ], - 'end_profit': 350.0, - 'end_profit_ratio': 0.14, - 'fee': 0.0, - }, - { - 'orders': [ - (('buy', 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)), - (('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), - (('sell', 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.011197)), - (('sell', 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.2848129)), - (('sell', 50, 5), (50.0, 12.5, 625.0, 336.625, -377.1875, 0.1343142)), - ], - 'end_profit': 336.625, - 'end_profit_ratio': 0.1343142, - 'fee': 0.0025, - }, - { - 'orders': [ - (('buy', 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)), - (('buy', 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)), - (('sell', 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 0.5945137)), - (('buy', 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 0.5945137)), - (('sell', 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.4261653)), - (('sell', 150, 23), (150.0, 11.0, 1650.0, 3175.75, 1787.25, 0.9747170)), - ], - 'end_profit': 3175.75, - 'end_profit_ratio': 0.9747170, - 'fee': 0.0025, - }, - { - # Test above without fees - 'orders': [ - (('buy', 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)), - (('buy', 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)), - (('sell', 100, 11), (100.0, 5.0, 500.0, 600.0, 600.0, 0.6)), - (('buy', 150, 15), (250.0, 11.0, 2750.0, 600.0, 600.0, 0.6)), - (('sell', 100, 19), (150.0, 11.0, 1650.0, 1400.0, 800.0, 0.43076923)), - (('sell', 150, 23), (150.0, 11.0, 1650.0, 3200.0, 1800.0, 0.98461538)), - ], - 'end_profit': 3200.0, - 'end_profit_ratio': 0.98461538, - 'fee': 0.0, - }, - { - 'orders': [ - (('buy', 100, 8), (100.0, 8.0, 800.0, 0.0, None, None)), - (('buy', 100, 9), (200.0, 8.5, 1700.0, 0.0, None, None)), - (('sell', 100, 10), (100.0, 8.5, 850.0, 150.0, 150.0, 0.08823529)), - (('buy', 150, 11), (250.0, 10, 2500.0, 150.0, 150.0, 0.08823529)), - (('sell', 100, 12), (150.0, 10.0, 1500.0, 350.0, 200.0, 0.1044776)), - (('sell', 150, 14), (150.0, 10.0, 1500.0, 950.0, 600.0, 0.283582)), - ], - 'end_profit': 950.0, - 'end_profit_ratio': 0.283582, - 'fee': 0.0, - }, -]) +@pytest.mark.parametrize( + "data", + [ + # tuple 1 - side, amount, price + # tuple 2 - amount, open_rate, stake_amount, cumulative_profit, realized_profit, rel_profit + { + "orders": [ + (("buy", 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)), + (("buy", 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), + (("sell", 50, 12), (150.0, 12.5, 1875.0, -25.0, -25.0, -0.01)), + (("sell", 100, 20), (50.0, 12.5, 625.0, 725.0, 750.0, 0.29)), + (("sell", 50, 5), (50.0, 12.5, 625.0, 350.0, -375.0, 0.14)), + ], + "end_profit": 350.0, + "end_profit_ratio": 0.14, + "fee": 0.0, + }, + { + "orders": [ + (("buy", 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)), + (("buy", 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)), + (("sell", 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.011197)), + (("sell", 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.2848129)), + (("sell", 50, 5), (50.0, 12.5, 625.0, 336.625, -377.1875, 0.1343142)), + ], + "end_profit": 336.625, + "end_profit_ratio": 0.1343142, + "fee": 0.0025, + }, + { + "orders": [ + (("buy", 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)), + (("buy", 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)), + (("sell", 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 0.5945137)), + (("buy", 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 0.5945137)), + (("sell", 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.4261653)), + (("sell", 150, 23), (150.0, 11.0, 1650.0, 3175.75, 1787.25, 0.9747170)), + ], + "end_profit": 3175.75, + "end_profit_ratio": 0.9747170, + "fee": 0.0025, + }, + { + # Test above without fees + "orders": [ + (("buy", 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)), + (("buy", 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)), + (("sell", 100, 11), (100.0, 5.0, 500.0, 600.0, 600.0, 0.6)), + (("buy", 150, 15), (250.0, 11.0, 2750.0, 600.0, 600.0, 0.6)), + (("sell", 100, 19), (150.0, 11.0, 1650.0, 1400.0, 800.0, 0.43076923)), + (("sell", 150, 23), (150.0, 11.0, 1650.0, 3200.0, 1800.0, 0.98461538)), + ], + "end_profit": 3200.0, + "end_profit_ratio": 0.98461538, + "fee": 0.0, + }, + { + "orders": [ + (("buy", 100, 8), (100.0, 8.0, 800.0, 0.0, None, None)), + (("buy", 100, 9), (200.0, 8.5, 1700.0, 0.0, None, None)), + (("sell", 100, 10), (100.0, 8.5, 850.0, 150.0, 150.0, 0.08823529)), + (("buy", 150, 11), (250.0, 10, 2500.0, 150.0, 150.0, 0.08823529)), + (("sell", 100, 12), (150.0, 10.0, 1500.0, 350.0, 200.0, 0.1044776)), + (("sell", 150, 14), (150.0, 10.0, 1500.0, 950.0, 600.0, 0.283582)), + ], + "end_profit": 950.0, + "end_profit_ratio": 0.283582, + "fee": 0.0, + }, + ], +) def test_recalc_trade_from_orders_dca(data) -> None: - - pair = 'ETH/USDT' + pair = "ETH/USDT" trade = Trade( id=2, pair=pair, stake_amount=1000, - open_rate=data['orders'][0][0][2], - amount=data['orders'][0][0][1], + open_rate=data["orders"][0][0][2], + amount=data["orders"][0][0][1], is_open=True, open_date=dt_now(), - fee_open=data['fee'], - fee_close=data['fee'], - exchange='binance', + fee_open=data["fee"], + fee_close=data["fee"], + exchange="binance", is_short=False, leverage=1.0, - trading_mode=TradingMode.SPOT + trading_mode=TradingMode.SPOT, ) Trade.session.add(trade) - for idx, (order, result) in enumerate(data['orders']): + for idx, (order, result) in enumerate(data["orders"]): amount = order[1] price = order[2] @@ -2810,8 +2830,8 @@ def test_recalc_trade_from_orders_dca(data) -> None: assert pytest.approx(trade.close_profit) == result[5] trade.close(price) - assert pytest.approx(trade.close_profit_abs) == data['end_profit'] - assert pytest.approx(trade.close_profit) == data['end_profit_ratio'] + assert pytest.approx(trade.close_profit_abs) == data["end_profit"] + assert pytest.approx(trade.close_profit) == data["end_profit_ratio"] assert not trade.is_open trade = Trade.session.scalars(select(Trade)).first() assert trade