From 02075b15e3d2c085bdffa558a3c39790dd5e72b4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 May 2024 16:04:01 +0200 Subject: [PATCH] ruff format: update more tests --- tests/freqtradebot/test_integration.py | 215 ++++---- tests/optimize/test_hyperoptloss.py | 109 ++-- tests/optimize/test_optimize_reports.py | 568 ++++++++++---------- tests/persistence/test_trade_custom_data.py | 131 ++--- 4 files changed, 524 insertions(+), 499 deletions(-) diff --git a/tests/freqtradebot/test_integration.py b/tests/freqtradebot/test_integration.py index 3384ae49f..75cc81fa1 100644 --- a/tests/freqtradebot/test_integration.py +++ b/tests/freqtradebot/test_integration.py @@ -19,13 +19,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, * 2nd trade is kept * 3rd trade is sold via sell-signal """ - default_conf['max_open_trades'] = 3 - default_conf['exchange']['name'] = 'binance' + default_conf["max_open_trades"] = 3 + default_conf["exchange"]["name"] = "binance" - stoploss = { - 'id': 123, - 'info': {} - } + stoploss = {"id": 123, "info": {}} stoploss_order_open = { "id": "123", "timestamp": 1542707426845, @@ -42,21 +39,17 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, "remaining": 0.0, "status": "open", "fee": None, - "trades": None + "trades": None, } stoploss_order_closed = stoploss_order_open.copy() - stoploss_order_closed['status'] = 'closed' - stoploss_order_closed['filled'] = stoploss_order_closed['amount'] + stoploss_order_closed["status"] = "closed" + stoploss_order_closed["filled"] = stoploss_order_closed["amount"] # Sell first trade based on stoploss, keep 2nd and 3rd trade open stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()] - stoploss_order_mock = MagicMock( - side_effect=stop_orders) + stoploss_order_mock = MagicMock(side_effect=stop_orders) # Sell 3rd trade (not called for the first trade) - should_sell_mock = MagicMock(side_effect=[ - [], - [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] - ) + should_sell_mock = MagicMock(side_effect=[[], [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]) cancel_order_mock = MagicMock() mocker.patch.multiple( EXMS, @@ -70,7 +63,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, ) mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', + "freqtrade.freqtradebot.FreqtradeBot", create_stoploss_order=MagicMock(return_value=True), _notify_exit=MagicMock(), ) @@ -80,9 +73,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True) freqtrade = get_patched_freqtradebot(mocker, default_conf) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True + freqtrade.strategy.order_types["stoploss_on_exchange"] = True # Switch ordertype to market to close trade immediately - freqtrade.strategy.order_types['exit'] = 'market' + freqtrade.strategy.order_types["exit"] = "market" freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) patch_get_signal(freqtrade) @@ -98,8 +91,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, # Make sure stoploss-order is open and trade is bought for idx, trade in enumerate(trades): stop_order = stop_orders[idx] - stop_order['id'] = f"stop{idx}" - oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss') + stop_order["id"] = f"stop{idx}" + oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, "stoploss") oobj.ft_is_open = True trade.orders.append(oobj) @@ -132,10 +125,13 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, assert not trade.is_open -@pytest.mark.parametrize("balance_ratio,result1", [ - (1, 200), - (0.99, 198), -]) +@pytest.mark.parametrize( + "balance_ratio,result1", + [ + (1, 200), + (0.99, 198), + ], +) def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_ratio, result1) -> None: """ Tests workflow unlimited stake-amount @@ -143,14 +139,14 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati Sell one trade, calculated stake amount should now be lower than before since one trade was sold at a loss. """ - default_conf['max_open_trades'] = 5 - default_conf['force_entry_enable'] = True - default_conf['stake_amount'] = 'unlimited' - default_conf['tradable_balance_ratio'] = balance_ratio - default_conf['dry_run_wallet'] = 1000 - default_conf['exchange']['name'] = 'binance' - default_conf['telegram']['enabled'] = True - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + default_conf["max_open_trades"] = 5 + default_conf["force_entry_enable"] = True + default_conf["stake_amount"] = "unlimited" + default_conf["tradable_balance_ratio"] = balance_ratio + default_conf["dry_run_wallet"] = 1000 + default_conf["exchange"]["name"] = "binance" + default_conf["telegram"]["enabled"] = True + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -160,24 +156,20 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati ) mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', + "freqtrade.freqtradebot.FreqtradeBot", create_stoploss_order=MagicMock(return_value=True), _notify_exit=MagicMock(), ) - should_sell_mock = MagicMock(side_effect=[ - [], - [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)], - [], - [], - []] + should_sell_mock = MagicMock( + side_effect=[[], [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)], [], [], []] ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock) freqtrade = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtrade) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True + freqtrade.strategy.order_types["stoploss_on_exchange"] = True # Switch ordertype to market to close trade immediately - freqtrade.strategy.order_types['exit'] = 'market' + freqtrade.strategy.order_types["exit"] = "market" patch_get_signal(freqtrade) # Create 4 trades @@ -186,9 +178,9 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == 4 - assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) == result1 + assert freqtrade.wallets.get_trade_stake_amount("XRP/BTC", 5) == result1 - rpc._rpc_force_entry('TKN/BTC', None) + rpc._rpc_force_entry("TKN/BTC", None) trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == 5 @@ -206,18 +198,18 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati # One trade sold assert len(trades) == 4 # stake-amount should now be reduced, since one trade was sold at a loss. - assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) < result1 + assert freqtrade.wallets.get_trade_stake_amount("XRP/BTC", 5) < result1 # Validate that balance of sold trade is not in dry-run balances anymore. bals2 = freqtrade.wallets.get_all_balances() assert bals != bals2 assert len(bals) == 6 assert len(bals2) == 5 - assert 'LTC' in bals - assert 'LTC' not in bals2 + assert "LTC" in bals + assert "LTC" not in bals2 def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: - default_conf_usdt['position_adjustment_enable'] = True + default_conf_usdt["position_adjustment_enable"] = True freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( @@ -242,8 +234,8 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value - ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995 - mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) + ticker_usdt_modif["bid"] = ticker_usdt_modif["bid"] * 0.995 + mocker.patch(f"{EXMS}.fetch_ticker", return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -263,7 +255,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert len(trade.orders) == 2 assert pytest.approx(trade.stake_amount) == 120 assert trade.orders[0].amount == 30 - assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid'] + assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif["bid"] assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount assert trade.nr_of_successful_buys == 2 @@ -275,10 +267,10 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: trade = Trade.get_trades().first() assert trade.is_open is False assert trade.orders[0].amount == 30 - assert trade.orders[0].side == 'buy' - assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid'] + assert trade.orders[0].side == "buy" + assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif["bid"] # Sold everything - assert trade.orders[-1].side == 'sell' + assert trade.orders[-1].side == "sell" assert trade.orders[2].amount == trade.amount assert trade.nr_of_successful_buys == 2 @@ -286,7 +278,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: - default_conf_usdt['position_adjustment_enable'] = True + default_conf_usdt["position_adjustment_enable"] = True freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( @@ -314,8 +306,8 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value - ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004 - mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) + ticker_usdt_modif["ask"] = ticker_usdt_modif["ask"] * 1.004 + mocker.patch(f"{EXMS}.fetch_ticker", return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -334,7 +326,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: trade = Trade.get_trades().first() assert len(trade.orders) == 2 assert pytest.approx(trade.stake_amount) == 120 - assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4) + assert trade.orders[1].amount == round(60 / ticker_usdt_modif["ask"], 4) assert trade.amount == trade.orders[0].amount + trade.orders[1].amount assert trade.nr_of_successful_entries == 2 @@ -345,23 +337,21 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: trade = Trade.get_trades().first() assert trade.is_open is False # assert trade.orders[0].amount == 30 - assert trade.orders[0].side == 'sell' - assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4) + assert trade.orders[0].side == "sell" + assert trade.orders[1].amount == round(60 / ticker_usdt_modif["ask"], 4) # Sold everything - assert trade.orders[-1].side == 'buy' + assert trade.orders[-1].side == "buy" assert trade.orders[2].amount == trade.amount assert trade.nr_of_successful_entries == 2 assert trade.nr_of_successful_exits == 1 -@pytest.mark.parametrize('leverage', [ - 1, 2 -]) +@pytest.mark.parametrize("leverage", [1, 2]) def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) -> None: - default_conf_usdt['position_adjustment_enable'] = True - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' + default_conf_usdt["position_adjustment_enable"] = True + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( @@ -371,13 +361,13 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, ) - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False) mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) mocker.patch(f"{EXMS}.get_funding_fees", return_value=0) mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0, 0)) patch_get_signal(freqtrade) - freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96 + freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt["ask"] * 0.96 freqtrade.strategy.leverage = MagicMock(return_value=leverage) freqtrade.strategy.minimal_roi = {0: 0.2} @@ -417,7 +407,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert trade.initial_stop_loss_pct == -0.1 # Fill order - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 @@ -433,7 +423,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # 2nd order - not filling freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120) - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False) freqtrade.process() trade = Trade.get_trades().first() @@ -458,7 +448,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # Fill DCA order freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None) - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError) freqtrade.process() @@ -468,7 +458,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert pytest.approx(trade.open_rate) == 1.963153456 assert trade.orders[-1].price == 1.95 assert pytest.approx(trade.orders[-1].cost) == 120 * leverage - assert trade.orders[-1].status == 'closed' + assert trade.orders[-1].status == "closed" assert pytest.approx(trade.amount) == 91.689215 * leverage # Check the 2 filled orders equal the above amount @@ -476,14 +466,14 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert pytest.approx(trade.orders[-1].amount) == 61.538461232 * leverage # Full exit - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) - freqtrade.strategy.custom_exit = MagicMock(return_value='Exit now') + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False) + freqtrade.strategy.custom_exit = MagicMock(return_value="Exit now") freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 5 assert trade.orders[-1].side == trade.exit_side - assert trade.orders[-1].status == 'open' + assert trade.orders[-1].status == "open" assert trade.orders[-1].price == 2.02 assert pytest.approx(trade.amount) == 91.689215 * leverage assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage @@ -492,23 +482,23 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 5 - assert trade.orders[-1].status == 'open' + assert trade.orders[-1].status == "open" assert trade.orders[-1].price == 2.02 # Adjust entry price cannot be called - this is an exit order assert freqtrade.strategy.adjust_entry_price.call_count == 0 -@pytest.mark.parametrize('leverage', [1, 2]) +@pytest.mark.parametrize("leverage", [1, 2]) @pytest.mark.parametrize("is_short", [False, True]) def test_dca_order_adjust_entry_replace_fails( default_conf_usdt, ticker_usdt, fee, mocker, caplog, is_short, leverage ) -> None: spot = leverage == 1 if not spot: - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' - default_conf_usdt['position_adjustment_enable'] = True - default_conf_usdt['max_open_trades'] = 2 + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" + default_conf_usdt["position_adjustment_enable"] = True + default_conf_usdt["max_open_trades"] = 2 freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( EXMS, @@ -518,7 +508,7 @@ def test_dca_order_adjust_entry_replace_fails( ) # no order fills. - mocker.patch(f'{EXMS}._dry_is_price_crossed', side_effect=[False, True]) + mocker.patch(f"{EXMS}._dry_is_price_crossed", side_effect=[False, True]) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() @@ -527,10 +517,10 @@ def test_dca_order_adjust_entry_replace_fails( .where(Order.ft_is_open.is_(True)) .where(Order.ft_order_side != "stoploss") .where(Order.ft_trade_id == Trade.id) - ).all() + ).all() assert len(trades) == 1 - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False) # Timeout to not interfere freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False) @@ -538,7 +528,7 @@ def test_dca_order_adjust_entry_replace_fails( # Create DCA order for 2nd trade (so we have 2 open orders on 2 trades) # this 2nd order won't fill. - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF')) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, "PeNF")) freqtrade.process() @@ -548,7 +538,7 @@ def test_dca_order_adjust_entry_replace_fails( .where(Order.ft_is_open.is_(True)) .where(Order.ft_order_side != "stoploss") .where(Order.ft_trade_id == Trade.id) - ).all() + ).all() assert len(trades) == 2 # We now have 2 orders open @@ -559,7 +549,7 @@ def test_dca_order_adjust_entry_replace_fails( .where(Order.ft_is_open.is_(True)) .where(Order.ft_order_side != "stoploss") .where(Order.ft_trade_id == Trade.id) - ).all() + ).all() assert len(trades) == 2 assert len(Order.get_open_orders()) == 2 # Entry adjustment is called @@ -568,8 +558,9 @@ def test_dca_order_adjust_entry_replace_fails( # Attempt order replacement - fails. freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234) - entry_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_entry', - return_value=False) + entry_mock = mocker.patch( + "freqtrade.freqtradebot.FreqtradeBot.execute_entry", return_value=False + ) msg = r"Could not replace order for.*" assert not log_has_re(msg, caplog) freqtrade.manage_open_orders() @@ -580,13 +571,13 @@ def test_dca_order_adjust_entry_replace_fails( assert len(Order.get_open_orders()) == 0 -@pytest.mark.parametrize('leverage', [1, 2]) +@pytest.mark.parametrize("leverage", [1, 2]) def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None: - default_conf_usdt['position_adjustment_enable'] = True + default_conf_usdt["position_adjustment_enable"] = True spot = leverage == 1 if not spot: - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) assert freqtrade.trading_mode == TradingMode.FUTURES if not spot else TradingMode.SPOT mocker.patch.multiple( @@ -599,7 +590,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera get_funding_fees=MagicMock(return_value=0), ) mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) - starting_amount = freqtrade.wallets.get_total('USDT') + starting_amount = freqtrade.wallets.get_total("USDT") assert starting_amount == 1000 patch_get_signal(freqtrade) @@ -613,11 +604,11 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert trade.leverage == leverage assert pytest.approx(trade.amount) == 30.0 * leverage assert trade.open_rate == 2.0 - assert pytest.approx(freqtrade.wallets.get_free('USDT')) == starting_amount - 60 + assert pytest.approx(freqtrade.wallets.get_free("USDT")) == starting_amount - 60 if spot: - assert pytest.approx(freqtrade.wallets.get_total('USDT')) == starting_amount - 60 + assert pytest.approx(freqtrade.wallets.get_total("USDT")) == starting_amount - 60 else: - assert freqtrade.wallets.get_total('USDT') == starting_amount + assert freqtrade.wallets.get_total("USDT") == starting_amount # Too small size freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59) @@ -627,28 +618,29 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert pytest.approx(trade.stake_amount) == 60 assert pytest.approx(trade.amount) == 30.0 * leverage assert log_has_re( - r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog) + r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog + ) - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES')) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, "PES")) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 - assert trade.orders[-1].ft_order_side == 'sell' - assert trade.orders[-1].ft_order_tag == 'PES' + assert trade.orders[-1].ft_order_side == "sell" + assert trade.orders[-1].ft_order_tag == "PES" assert pytest.approx(trade.stake_amount) == 40 assert pytest.approx(trade.amount) == 20 * leverage assert trade.open_rate == 2.0 assert trade.is_open assert trade.realized_profit > 0.098 * leverage expected_profit = starting_amount - 40 + trade.realized_profit - assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit + assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit if spot: - assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit + assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit else: # total won't change in futures mode, only free / used will. - assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit + assert freqtrade.wallets.get_total("USDT") == starting_amount + trade.realized_profit caplog.clear() # Sell more than what we got (we got ~20 coins left) @@ -666,14 +658,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert len(trade.orders) == 2 # Amount exactly comes out as exactly 0 - freqtrade.strategy.adjust_trade_position = MagicMock( - return_value=-trade.stake_amount) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-trade.stake_amount) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 3 - assert trade.orders[-1].ft_order_side == 'sell' + assert trade.orders[-1].ft_order_side == "sell" assert pytest.approx(trade.stake_amount) == 40 assert trade.is_open is False @@ -683,14 +674,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 3 - assert trade.orders[-1].ft_order_side == 'sell' + assert trade.orders[-1].ft_order_side == "sell" assert pytest.approx(trade.stake_amount) == 40 assert trade.is_open is False - assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog) + assert log_has_re("Amount to exit is 0.0 due to exchange limits - not exiting.", caplog) expected_profit = starting_amount - 60 + trade.realized_profit - assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit + assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit if spot: - assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit + assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit else: # total won't change in futures mode, only free / used will. - assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit + assert freqtrade.wallets.get_total("USDT") == starting_amount + trade.realized_profit diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index be1c313f6..b78cdde30 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -9,96 +9,103 @@ from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver def test_hyperoptlossresolver_noname(default_conf): - with pytest.raises(OperationalException, - match="No Hyperopt loss set. Please use `--hyperopt-loss` to specify " - "the Hyperopt-Loss class to use."): + with pytest.raises( + OperationalException, + match="No Hyperopt loss set. Please use `--hyperopt-loss` to specify " + "the Hyperopt-Loss class to use.", + ): HyperOptLossResolver.load_hyperoptloss(default_conf) def test_hyperoptlossresolver(mocker, default_conf) -> None: - hl = ShortTradeDurHyperOptLoss mocker.patch( - 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object', - MagicMock(return_value=hl()) + "freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object", + MagicMock(return_value=hl()), ) - default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'}) + default_conf.update({"hyperopt_loss": "SharpeHyperOptLossDaily"}) x = HyperOptLossResolver.load_hyperoptloss(default_conf) assert hasattr(x, "hyperopt_loss_function") def test_hyperoptlossresolver_wrongname(default_conf) -> None: - default_conf.update({'hyperopt_loss': "NonExistingLossClass"}) + default_conf.update({"hyperopt_loss": "NonExistingLossClass"}) - with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'): + with pytest.raises(OperationalException, match=r"Impossible to load HyperoptLoss.*"): HyperOptLossResolver.load_hyperoptloss(default_conf) def test_loss_calculation_prefer_correct_trade_count(hyperopt_conf, hyperopt_results) -> None: - hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"}) + hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) - correct = hl.hyperopt_loss_function(hyperopt_results, 600, - datetime(2019, 1, 1), datetime(2019, 5, 1)) - over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100, - datetime(2019, 1, 1), datetime(2019, 5, 1)) - under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100, - datetime(2019, 1, 1), datetime(2019, 5, 1)) + correct = hl.hyperopt_loss_function( + hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) + over = hl.hyperopt_loss_function( + hyperopt_results, 600 + 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) + under = hl.hyperopt_loss_function( + hyperopt_results, 600 - 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) assert over > correct assert under > correct def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results) -> None: resultsb = hyperopt_results.copy() - resultsb.loc[1, 'trade_duration'] = 20 + resultsb.loc[1, "trade_duration"] = 20 - hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"}) + hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) - longer = hl.hyperopt_loss_function(hyperopt_results, 100, - datetime(2019, 1, 1), datetime(2019, 5, 1)) - shorter = hl.hyperopt_loss_function(resultsb, 100, - datetime(2019, 1, 1), datetime(2019, 5, 1)) + longer = hl.hyperopt_loss_function( + hyperopt_results, 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) + shorter = hl.hyperopt_loss_function(resultsb, 100, datetime(2019, 1, 1), datetime(2019, 5, 1)) assert shorter < longer def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None: results_over = hyperopt_results.copy() - results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2 + results_over["profit_ratio"] = hyperopt_results["profit_ratio"] * 2 results_under = hyperopt_results.copy() - results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2 + results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2 - hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"}) + hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) - correct = hl.hyperopt_loss_function(hyperopt_results, 600, - datetime(2019, 1, 1), datetime(2019, 5, 1)) - over = hl.hyperopt_loss_function(results_over, 600, - datetime(2019, 1, 1), datetime(2019, 5, 1)) - under = hl.hyperopt_loss_function(results_under, 600, - datetime(2019, 1, 1), datetime(2019, 5, 1)) + correct = hl.hyperopt_loss_function( + hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) + over = hl.hyperopt_loss_function(results_over, 600, datetime(2019, 1, 1), datetime(2019, 5, 1)) + under = hl.hyperopt_loss_function( + results_under, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + ) assert over < correct assert under > correct -@pytest.mark.parametrize('lossfunction', [ - "OnlyProfitHyperOptLoss", - "SortinoHyperOptLoss", - "SortinoHyperOptLossDaily", - "SharpeHyperOptLoss", - "SharpeHyperOptLossDaily", - "MaxDrawDownHyperOptLoss", - "MaxDrawDownRelativeHyperOptLoss", - "CalmarHyperOptLoss", - "ProfitDrawDownHyperOptLoss", - -]) +@pytest.mark.parametrize( + "lossfunction", + [ + "OnlyProfitHyperOptLoss", + "SortinoHyperOptLoss", + "SortinoHyperOptLossDaily", + "SharpeHyperOptLoss", + "SharpeHyperOptLossDaily", + "MaxDrawDownHyperOptLoss", + "MaxDrawDownRelativeHyperOptLoss", + "CalmarHyperOptLoss", + "ProfitDrawDownHyperOptLoss", + ], +) def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None: results_over = hyperopt_results.copy() - results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2 + 0.2 - results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2 + results_over["profit_abs"] = hyperopt_results["profit_abs"] * 2 + 0.2 + results_over["profit_ratio"] = hyperopt_results["profit_ratio"] * 2 results_under = hyperopt_results.copy() - results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2 - 0.2 - results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2 + results_under["profit_abs"] = hyperopt_results["profit_abs"] / 2 - 0.2 + results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2 - default_conf.update({'hyperopt_loss': lossfunction}) + default_conf.update({"hyperopt_loss": lossfunction}) hl = HyperOptLossResolver.load_hyperoptloss(default_conf) correct = hl.hyperopt_loss_function( hyperopt_results, @@ -107,7 +114,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct max_date=datetime(2019, 5, 1), config=default_conf, processed=None, - backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()} + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, ) over = hl.hyperopt_loss_function( results_over, @@ -116,7 +123,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct max_date=datetime(2019, 5, 1), config=default_conf, processed=None, - backtest_stats={'profit_total': results_over['profit_abs'].sum()} + backtest_stats={"profit_total": results_over["profit_abs"].sum()}, ) under = hl.hyperopt_loss_function( results_under, @@ -125,7 +132,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct max_date=datetime(2019, 5, 1), config=default_conf, processed=None, - backtest_stats={'profit_total': results_under['profit_abs'].sum()} + backtest_stats={"profit_total": results_under["profit_abs"].sum()}, ) assert over < correct assert under > correct diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index ca39c39b3..88e846d9d 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -51,7 +51,7 @@ def _backup_file(file: Path, copy_file: bool = False) -> None: :param copy_file: keep file in place too. :return: None """ - file_swp = str(file) + '.swp' + file_swp = str(file) + ".swp" if file.is_file(): file.rename(file_swp) @@ -60,149 +60,172 @@ def _backup_file(file: Path, copy_file: bool = False) -> None: def test_text_table_bt_results(): - results = pd.DataFrame( { - 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], - 'profit_ratio': [0.1, 0.2, -0.05], - 'profit_abs': [0.2, 0.4, -0.1], - 'trade_duration': [10, 30, 20], + "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], + "profit_ratio": [0.1, 0.2, -0.05], + "profit_abs": [0.2, 0.4, -0.1], + "trade_duration": [10, 30, 20], } ) result_str = ( - '| Pair | Entries | Avg Profit % | Tot Profit BTC | ' - 'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n' - '|---------+-----------+----------------+------------------+' - '----------------+----------------+-------------------------|\n' - '| ETH/BTC | 3 | 8.33 | 0.50000000 | ' - '12.50 | 0:20:00 | 2 0 1 66.7 |\n' - '| TOTAL | 3 | 8.33 | 0.50000000 | ' - '12.50 | 0:20:00 | 2 0 1 66.7 |' + "| Pair | Entries | Avg Profit % | Tot Profit BTC | " + "Tot Profit % | Avg Duration | Win Draw Loss Win% |\n" + "|---------+-----------+----------------+------------------+" + "----------------+----------------+-------------------------|\n" + "| ETH/BTC | 3 | 8.33 | 0.50000000 | " + "12.50 | 0:20:00 | 2 0 1 66.7 |\n" + "| TOTAL | 3 | 8.33 | 0.50000000 | " + "12.50 | 0:20:00 | 2 0 1 66.7 |" ) - pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC', - starting_balance=4, results=results) - assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str + pair_results = generate_pair_metrics( + ["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results + ) + assert text_table_bt_results(pair_results, stake_currency="BTC") == result_str def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): - default_conf.update({'strategy': CURRENT_TEST_STRATEGY}) + default_conf.update({"strategy": CURRENT_TEST_STRATEGY}) StrategyResolver.load_strategy(default_conf) - results = {'DefStrat': { - 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", - "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], - "open_date": [dt_utc(2017, 11, 14, 19, 32, 00), - dt_utc(2017, 11, 14, 21, 36, 00), - dt_utc(2017, 11, 14, 22, 12, 00), - dt_utc(2017, 11, 14, 22, 44, 00)], - "close_date": [dt_utc(2017, 11, 14, 21, 35, 00), - dt_utc(2017, 11, 14, 22, 10, 00), - dt_utc(2017, 11, 14, 22, 43, 00), - dt_utc(2017, 11, 14, 22, 58, 00)], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], - "trade_duration": [123, 34, 31, 14], - "is_open": [False, False, False, True], - "is_short": [False, False, False, False], - "stake_amount": [0.01, 0.01, 0.01, 0.01], - "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS, - ExitType.ROI, ExitType.FORCE_EXIT] - }), - 'config': default_conf, - 'locks': [], - 'final_balance': 1000.02, - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'backtest_start_time': dt_ts() // 1000, - 'backtest_end_time': dt_ts() // 1000, - 'run_id': '123', + results = { + "DefStrat": { + "results": pd.DataFrame( + { + "pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], + "open_date": [ + dt_utc(2017, 11, 14, 19, 32, 00), + dt_utc(2017, 11, 14, 21, 36, 00), + dt_utc(2017, 11, 14, 22, 12, 00), + dt_utc(2017, 11, 14, 22, 44, 00), + ], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + dt_utc(2017, 11, 14, 22, 43, 00), + dt_utc(2017, 11, 14, 22, 58, 00), + ], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], + "trade_duration": [123, 34, 31, 14], + "is_open": [False, False, False, True], + "is_short": [False, False, False, False], + "stake_amount": [0.01, 0.01, 0.01, 0.01], + "exit_reason": [ + ExitType.ROI, + ExitType.STOP_LOSS, + ExitType.ROI, + ExitType.FORCE_EXIT, + ], + } + ), + "config": default_conf, + "locks": [], + "final_balance": 1000.02, + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "backtest_start_time": dt_ts() // 1000, + "backtest_end_time": dt_ts() // 1000, + "run_id": "123", } - } - timerange = TimeRange.parse_timerange('1510688220-1510700340') + } + timerange = TimeRange.parse_timerange("1510688220-1510700340") min_date = dt_from_ts(1510688220) max_date = dt_from_ts(1510700340) - btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) + btdata = history.load_data( + testdatadir, "1m", ["UNITTEST/BTC"], timerange=timerange, fill_up_missing=True + ) stats = generate_backtest_stats(btdata, results, min_date, max_date) assert isinstance(stats, dict) - assert 'strategy' in stats - assert 'DefStrat' in stats['strategy'] - assert 'strategy_comparison' in stats - strat_stats = stats['strategy']['DefStrat'] - assert strat_stats['backtest_start'] == min_date.strftime(DATETIME_PRINT_FORMAT) - assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT) - assert strat_stats['total_trades'] == len(results['DefStrat']['results']) + assert "strategy" in stats + assert "DefStrat" in stats["strategy"] + assert "strategy_comparison" in stats + strat_stats = stats["strategy"]["DefStrat"] + assert strat_stats["backtest_start"] == min_date.strftime(DATETIME_PRINT_FORMAT) + assert strat_stats["backtest_end"] == max_date.strftime(DATETIME_PRINT_FORMAT) + assert strat_stats["total_trades"] == len(results["DefStrat"]["results"]) # Above sample had no losing trade - assert strat_stats['max_drawdown_account'] == 0.0 + assert strat_stats["max_drawdown_account"] == 0.0 # Retry with losing trade - results = {'DefStrat': { - 'results': pd.DataFrame( - {"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], - "open_date": [dt_utc(2017, 11, 14, 19, 32, 00), - dt_utc(2017, 11, 14, 21, 36, 00), - dt_utc(2017, 11, 14, 22, 12, 00), - dt_utc(2017, 11, 14, 22, 44, 00)], - "close_date": [dt_utc(2017, 11, 14, 21, 35, 00), - dt_utc(2017, 11, 14, 22, 10, 00), - dt_utc(2017, 11, 14, 22, 43, 00), - dt_utc(2017, 11, 14, 22, 58, 00)], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], - "trade_duration": [123, 34, 31, 14], - "is_open": [False, False, False, True], - "is_short": [False, False, False, False], - "stake_amount": [0.01, 0.01, 0.01, 0.01], - "exit_reason": [ExitType.ROI, ExitType.ROI, - ExitType.STOP_LOSS, ExitType.FORCE_EXIT] - }), - 'config': default_conf, - 'locks': [], - 'final_balance': 1000.02, - 'rejected_signals': 20, - 'timedout_entry_orders': 0, - 'timedout_exit_orders': 0, - 'canceled_trade_entries': 0, - 'canceled_entry_orders': 0, - 'replaced_entry_orders': 0, - 'backtest_start_time': dt_ts() // 1000, - 'backtest_end_time': dt_ts() // 1000, - 'run_id': '124', + results = { + "DefStrat": { + "results": pd.DataFrame( + { + "pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], + "open_date": [ + dt_utc(2017, 11, 14, 19, 32, 00), + dt_utc(2017, 11, 14, 21, 36, 00), + dt_utc(2017, 11, 14, 22, 12, 00), + dt_utc(2017, 11, 14, 22, 44, 00), + ], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + dt_utc(2017, 11, 14, 22, 43, 00), + dt_utc(2017, 11, 14, 22, 58, 00), + ], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], + "trade_duration": [123, 34, 31, 14], + "is_open": [False, False, False, True], + "is_short": [False, False, False, False], + "stake_amount": [0.01, 0.01, 0.01, 0.01], + "exit_reason": [ + ExitType.ROI, + ExitType.ROI, + ExitType.STOP_LOSS, + ExitType.FORCE_EXIT, + ], + } + ), + "config": default_conf, + "locks": [], + "final_balance": 1000.02, + "rejected_signals": 20, + "timedout_entry_orders": 0, + "timedout_exit_orders": 0, + "canceled_trade_entries": 0, + "canceled_entry_orders": 0, + "replaced_entry_orders": 0, + "backtest_start_time": dt_ts() // 1000, + "backtest_end_time": dt_ts() // 1000, + "run_id": "124", } } stats = generate_backtest_stats(btdata, results, min_date, max_date) assert isinstance(stats, dict) - assert 'strategy' in stats - assert 'DefStrat' in stats['strategy'] - assert 'strategy_comparison' in stats - strat_stats = stats['strategy']['DefStrat'] + assert "strategy" in stats + assert "DefStrat" in stats["strategy"] + assert "strategy_comparison" in stats + strat_stats = stats["strategy"]["DefStrat"] - assert pytest.approx(strat_stats['max_drawdown_account']) == 1.399999e-08 - assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00' - assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00' - assert strat_stats['drawdown_end_ts'] == 1510699380000 - assert strat_stats['drawdown_start_ts'] == 1510697400000 - assert strat_stats['pairlist'] == ['UNITTEST/BTC'] + assert pytest.approx(strat_stats["max_drawdown_account"]) == 1.399999e-08 + assert strat_stats["drawdown_start"] == "2017-11-14 22:10:00" + assert strat_stats["drawdown_end"] == "2017-11-14 22:43:00" + assert strat_stats["drawdown_end_ts"] == 1510699380000 + assert strat_stats["drawdown_start_ts"] == 1510697400000 + assert strat_stats["pairlist"] == ["UNITTEST/BTC"] # Test storing stats - filename = tmp_path / 'btresult.json' + filename = tmp_path / "btresult.json" filename_last = tmp_path / LAST_BT_RESULT_FN _backup_file(filename_last, copy_file=True) assert not filename.is_file() - store_backtest_stats(filename, stats, '2022_01_01_15_05_13') + store_backtest_stats(filename, stats, "2022_01_01_15_05_13") # get real Filename (it's btresult-.json) last_fn = get_latest_backtest_filename(filename_last.parent) @@ -211,9 +234,9 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): filename1 = tmp_path / last_fn assert filename1.is_file() content = filename1.read_text() - assert 'max_drawdown_account' in content - assert 'strategy' in content - assert 'pairlist' in content + assert "max_drawdown_account" in content + assert "strategy" in content + assert "pairlist" in content assert filename_last.is_file() @@ -222,149 +245,146 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): def test_store_backtest_stats(testdatadir, mocker): + dump_mock = mocker.patch("freqtrade.optimize.optimize_reports.bt_storage.file_dump_json") - dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.bt_storage.file_dump_json') - - data = {'metadata': {}, 'strategy': {}, 'strategy_comparison': []} - store_backtest_stats(testdatadir, data, '2022_01_01_15_05_13') + data = {"metadata": {}, "strategy": {}, "strategy_comparison": []} + store_backtest_stats(testdatadir, data, "2022_01_01_15_05_13") assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) - assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'backtest-result')) + assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / "backtest-result")) dump_mock.reset_mock() - filename = testdatadir / 'testresult.json' - store_backtest_stats(filename, data, '2022_01_01_15_05_13') + filename = testdatadir / "testresult.json" + store_backtest_stats(filename, data, "2022_01_01_15_05_13") assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) # result will be testdatadir / testresult-.json - assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult')) + assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / "testresult")) def test_store_backtest_stats_real(tmp_path): - data = {'metadata': {}, 'strategy': {}, 'strategy_comparison': []} - store_backtest_stats(tmp_path, data, '2022_01_01_15_05_13') + data = {"metadata": {}, "strategy": {}, "strategy_comparison": []} + store_backtest_stats(tmp_path, data, "2022_01_01_15_05_13") - assert (tmp_path / 'backtest-result-2022_01_01_15_05_13.json').is_file() - assert (tmp_path / 'backtest-result-2022_01_01_15_05_13.meta.json').is_file() - assert not (tmp_path / 'backtest-result-2022_01_01_15_05_13_market_change.feather').is_file() + assert (tmp_path / "backtest-result-2022_01_01_15_05_13.json").is_file() + assert (tmp_path / "backtest-result-2022_01_01_15_05_13.meta.json").is_file() + assert not (tmp_path / "backtest-result-2022_01_01_15_05_13_market_change.feather").is_file() assert (tmp_path / LAST_BT_RESULT_FN).is_file() fn = get_latest_backtest_filename(tmp_path) - assert fn == 'backtest-result-2022_01_01_15_05_13.json' + assert fn == "backtest-result-2022_01_01_15_05_13.json" - store_backtest_stats(tmp_path, data, '2024_01_01_15_05_25', market_change_data=pd.DataFrame()) - assert (tmp_path / 'backtest-result-2024_01_01_15_05_25.json').is_file() - assert (tmp_path / 'backtest-result-2024_01_01_15_05_25.meta.json').is_file() - assert (tmp_path / 'backtest-result-2024_01_01_15_05_25_market_change.feather').is_file() + store_backtest_stats(tmp_path, data, "2024_01_01_15_05_25", market_change_data=pd.DataFrame()) + assert (tmp_path / "backtest-result-2024_01_01_15_05_25.json").is_file() + assert (tmp_path / "backtest-result-2024_01_01_15_05_25.meta.json").is_file() + assert (tmp_path / "backtest-result-2024_01_01_15_05_25_market_change.feather").is_file() assert (tmp_path / LAST_BT_RESULT_FN).is_file() # Last file reference should be updated fn = get_latest_backtest_filename(tmp_path) - assert fn == 'backtest-result-2024_01_01_15_05_25.json' + assert fn == "backtest-result-2024_01_01_15_05_25.json" def test_store_backtest_candles(testdatadir, mocker): + dump_mock = mocker.patch("freqtrade.optimize.optimize_reports.bt_storage.file_dump_joblib") - dump_mock = mocker.patch( - 'freqtrade.optimize.optimize_reports.bt_storage.file_dump_joblib') - - candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}} + candle_dict = {"DefStrat": {"UNITTEST/BTC": pd.DataFrame()}} # mock directory exporting - store_backtest_analysis_results(testdatadir, candle_dict, {}, '2022_01_01_15_05_13') + store_backtest_analysis_results(testdatadir, candle_dict, {}, "2022_01_01_15_05_13") assert dump_mock.call_count == 2 assert isinstance(dump_mock.call_args_list[0][0][0], Path) - assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl') + assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl") dump_mock.reset_mock() # mock file exporting - filename = Path(testdatadir / 'testresult') - store_backtest_analysis_results(filename, candle_dict, {}, '2022_01_01_15_05_13') + filename = Path(testdatadir / "testresult") + store_backtest_analysis_results(filename, candle_dict, {}, "2022_01_01_15_05_13") assert dump_mock.call_count == 2 assert isinstance(dump_mock.call_args_list[0][0][0], Path) # result will be testdatadir / testresult-_signals.pkl - assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl') + assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl") dump_mock.reset_mock() def test_write_read_backtest_candles(tmp_path): - - candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}} + candle_dict = {"DefStrat": {"UNITTEST/BTC": pd.DataFrame()}} # test directory exporting - sample_date = '2022_01_01_15_05_13' + sample_date = "2022_01_01_15_05_13" store_backtest_analysis_results(tmp_path, candle_dict, {}, sample_date) - stored_file = tmp_path / f'backtest-result-{sample_date}_signals.pkl' + stored_file = tmp_path / f"backtest-result-{sample_date}_signals.pkl" with stored_file.open("rb") as scp: pickled_signal_candles = joblib.load(scp) assert pickled_signal_candles.keys() == candle_dict.keys() - assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys() - assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \ - .equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC']) + assert pickled_signal_candles["DefStrat"].keys() == pickled_signal_candles["DefStrat"].keys() + assert pickled_signal_candles["DefStrat"]["UNITTEST/BTC"].equals( + pickled_signal_candles["DefStrat"]["UNITTEST/BTC"] + ) _clean_test_file(stored_file) # test file exporting - filename = tmp_path / 'testresult' + filename = tmp_path / "testresult" store_backtest_analysis_results(filename, candle_dict, {}, sample_date) - stored_file = tmp_path / f'testresult-{sample_date}_signals.pkl' + stored_file = tmp_path / f"testresult-{sample_date}_signals.pkl" with stored_file.open("rb") as scp: pickled_signal_candles = joblib.load(scp) assert pickled_signal_candles.keys() == candle_dict.keys() - assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys() - assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \ - .equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC']) + assert pickled_signal_candles["DefStrat"].keys() == pickled_signal_candles["DefStrat"].keys() + assert pickled_signal_candles["DefStrat"]["UNITTEST/BTC"].equals( + pickled_signal_candles["DefStrat"]["UNITTEST/BTC"] + ) _clean_test_file(stored_file) def test_generate_pair_metrics(): - results = pd.DataFrame( { - 'pair': ['ETH/BTC', 'ETH/BTC'], - 'profit_ratio': [0.1, 0.2], - 'profit_abs': [0.2, 0.4], - 'trade_duration': [10, 30], - 'wins': [2, 0], - 'draws': [0, 0], - 'losses': [0, 0] + "pair": ["ETH/BTC", "ETH/BTC"], + "profit_ratio": [0.1, 0.2], + "profit_abs": [0.2, 0.4], + "trade_duration": [10, 30], + "wins": [2, 0], + "draws": [0, 0], + "losses": [0, 0], } ) - pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC', - starting_balance=2, results=results) + pair_results = generate_pair_metrics( + ["ETH/BTC"], stake_currency="BTC", starting_balance=2, results=results + ) assert isinstance(pair_results, list) assert len(pair_results) == 2 - assert pair_results[-1]['key'] == 'TOTAL' + assert pair_results[-1]["key"] == "TOTAL" assert ( - pytest.approx(pair_results[-1]['profit_mean_pct']) == pair_results[-1]['profit_mean'] * 100) - assert ( - pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100) + pytest.approx(pair_results[-1]["profit_mean_pct"]) == pair_results[-1]["profit_mean"] * 100 + ) + assert pytest.approx(pair_results[-1]["profit_sum_pct"]) == pair_results[-1]["profit_sum"] * 100 def test_generate_daily_stats(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) res = generate_daily_stats(bt_data) assert isinstance(res, dict) - assert round(res['backtest_best_day'], 4) == 0.1796 - assert round(res['backtest_worst_day'], 4) == -0.1468 - assert res['winning_days'] == 19 - assert res['draw_days'] == 0 - assert res['losing_days'] == 2 + assert round(res["backtest_best_day"], 4) == 0.1796 + assert round(res["backtest_worst_day"], 4) == -0.1468 + assert res["winning_days"] == 19 + assert res["draw_days"] == 0 + assert res["losing_days"] == 2 # Select empty dataframe! - res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :]) + res = generate_daily_stats(bt_data.loc[bt_data["open_date"] == "2000-01-01", :]) assert isinstance(res, dict) - assert round(res['backtest_best_day'], 4) == 0.0 - assert res['winning_days'] == 0 - assert res['draw_days'] == 0 - assert res['losing_days'] == 0 + assert round(res["backtest_best_day"], 4) == 0.0 + assert res["winning_days"] == 0 + assert res["draw_days"] == 0 + assert res["losing_days"] == 0 def test_generate_trading_stats(testdatadir): @@ -372,22 +392,24 @@ def test_generate_trading_stats(testdatadir): bt_data = load_backtest_data(filename) res = generate_trading_stats(bt_data) assert isinstance(res, dict) - assert res['winner_holding_avg'] == timedelta(seconds=1440) - assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420) - assert 'wins' in res - assert 'losses' in res - assert 'draws' in res + assert res["winner_holding_avg"] == timedelta(seconds=1440) + assert res["loser_holding_avg"] == timedelta(days=1, seconds=21420) + assert "wins" in res + assert "losses" in res + assert "draws" in res # Select empty dataframe! - res = generate_trading_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :]) - assert res['wins'] == 0 - assert res['losses'] == 0 + res = generate_trading_stats(bt_data.loc[bt_data["open_date"] == "2000-01-01", :]) + assert res["wins"] == 0 + assert res["losses"] == 0 def test_calc_streak(testdatadir): - df = pd.DataFrame({ - 'profit_ratio': [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03], - }) + df = pd.DataFrame( + { + "profit_ratio": [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03], + } + ) # 4 consecutive wins, 3 consecutive losses res = calc_streak(df) assert res == (4, 3) @@ -396,12 +418,14 @@ def test_calc_streak(testdatadir): # invert situation df1 = df.copy() - df1['profit_ratio'] = df1['profit_ratio'] * -1 + df1["profit_ratio"] = df1["profit_ratio"] * -1 assert calc_streak(df1) == (3, 4) - df_empty = pd.DataFrame({ - 'profit_ratio': [], - }) + df_empty = pd.DataFrame( + { + "profit_ratio": [], + } + ) assert df_empty.empty assert calc_streak(df_empty) == (0, 0) @@ -411,131 +435,133 @@ def test_calc_streak(testdatadir): def test_text_table_exit_reason(): - results = pd.DataFrame( { - 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], - 'profit_ratio': [0.1, 0.2, -0.1], - 'profit_abs': [0.2, 0.4, -0.2], - 'trade_duration': [10, 30, 10], - 'wins': [2, 0, 0], - 'draws': [0, 0, 0], - 'losses': [0, 0, 1], - 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] + "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], + "profit_ratio": [0.1, 0.2, -0.1], + "profit_abs": [0.2, 0.4, -0.2], + "trade_duration": [10, 30, 10], + "wins": [2, 0, 0], + "draws": [0, 0, 0], + "losses": [0, 0, 1], + "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], } ) result_str = ( - '| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |' - ' Avg Duration | Win Draw Loss Win% |\n' - '|---------------+---------+----------------+------------------+----------------+' - '----------------+-------------------------|\n' - '| roi | 2 | 15.00 | 0.60000000 | 2.73 |' - ' 0:20:00 | 2 0 0 100 |\n' - '| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |' - ' 0:10:00 | 0 0 1 0 |\n' - '| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |' - ' 0:17:00 | 2 0 1 66.7 |' + "| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |" + " Avg Duration | Win Draw Loss Win% |\n" + "|---------------+---------+----------------+------------------+----------------+" + "----------------+-------------------------|\n" + "| roi | 2 | 15.00 | 0.60000000 | 2.73 |" + " 0:20:00 | 2 0 0 100 |\n" + "| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |" + " 0:10:00 | 0 0 1 0 |\n" + "| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |" + " 0:17:00 | 2 0 1 66.7 |" ) - exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22, - results=results, skip_nan=False) - assert text_table_tags('exit_tag', exit_reason_stats, 'BTC') == result_str + exit_reason_stats = generate_tag_metrics( + "exit_reason", starting_balance=22, results=results, skip_nan=False + ) + assert text_table_tags("exit_tag", exit_reason_stats, "BTC") == result_str def test_generate_sell_reason_stats(): - results = pd.DataFrame( { - 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], - 'profit_ratio': [0.1, 0.2, -0.1], - 'profit_abs': [0.2, 0.4, -0.2], - 'trade_duration': [10, 30, 10], - 'wins': [2, 0, 0], - 'draws': [0, 0, 0], - 'losses': [0, 0, 1], - 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value] + "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], + "profit_ratio": [0.1, 0.2, -0.1], + "profit_abs": [0.2, 0.4, -0.2], + "trade_duration": [10, 30, 10], + "wins": [2, 0, 0], + "draws": [0, 0, 0], + "losses": [0, 0, 1], + "exit_reason": [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value], } ) - exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22, - results=results, skip_nan=False) + exit_reason_stats = generate_tag_metrics( + "exit_reason", starting_balance=22, results=results, skip_nan=False + ) roi_result = exit_reason_stats[0] - assert roi_result['key'] == 'roi' - assert roi_result['trades'] == 2 - assert pytest.approx(roi_result['profit_mean']) == 0.15 - assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2) - assert pytest.approx(roi_result['profit_mean']) == 0.15 - assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2) + assert roi_result["key"] == "roi" + assert roi_result["trades"] == 2 + assert pytest.approx(roi_result["profit_mean"]) == 0.15 + assert roi_result["profit_mean_pct"] == round(roi_result["profit_mean"] * 100, 2) + assert pytest.approx(roi_result["profit_mean"]) == 0.15 + assert roi_result["profit_mean_pct"] == round(roi_result["profit_mean"] * 100, 2) stop_result = exit_reason_stats[1] - assert stop_result['key'] == 'stop_loss' - assert stop_result['trades'] == 1 - assert pytest.approx(stop_result['profit_mean']) == -0.1 - assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) - assert pytest.approx(stop_result['profit_mean']) == -0.1 - assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) + assert stop_result["key"] == "stop_loss" + assert stop_result["trades"] == 1 + assert pytest.approx(stop_result["profit_mean"]) == -0.1 + assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2) + assert pytest.approx(stop_result["profit_mean"]) == -0.1 + assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2) def test_text_table_strategy(testdatadir): filename = testdatadir / "backtest_results/backtest-result_multistrat.json" bt_res_data = load_backtest_stats(filename) - bt_res_data_comparison = bt_res_data.pop('strategy_comparison') + bt_res_data_comparison = bt_res_data.pop("strategy_comparison") result_str = ( - '| Strategy | Entries | Avg Profit % | Tot Profit BTC |' - ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' - '|----------------+-----------+----------------+------------------+' - '----------------+----------------+-------------------------+-----------------------|\n' - '| StrategyTestV2 | 179 | 0.08 | 0.02608550 |' - ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n' - '| TestStrategy | 179 | 0.08 | 0.02608550 |' - ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |' + "| Strategy | Entries | Avg Profit % | Tot Profit BTC |" + " Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n" + "|----------------+-----------+----------------+------------------+" + "----------------+----------------+-------------------------+-----------------------|\n" + "| StrategyTestV2 | 179 | 0.08 | 0.02608550 |" + " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n" + "| TestStrategy | 179 | 0.08 | 0.02608550 |" + " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |" ) - strategy_results = generate_strategy_comparison(bt_stats=bt_res_data['strategy']) + strategy_results = generate_strategy_comparison(bt_stats=bt_res_data["strategy"]) assert strategy_results == bt_res_data_comparison - assert text_table_strategy(strategy_results, 'BTC') == result_str + assert text_table_strategy(strategy_results, "BTC") == result_str def test_generate_edge_table(): - results = {} - results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) - assert generate_edge_table(results).count('+') == 7 - assert generate_edge_table(results).count('| ETH/BTC |') == 1 - assert generate_edge_table(results).count( - '| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1 + results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) + assert generate_edge_table(results).count("+") == 7 + assert generate_edge_table(results).count("| ETH/BTC |") == 1 + assert ( + generate_edge_table(results).count( + "| Risk Reward Ratio | Required Risk Reward | Expectancy |" + ) + == 1 + ) def test_generate_periodic_breakdown_stats(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename).to_dict(orient='records') + bt_data = load_backtest_data(filename).to_dict(orient="records") - res = generate_periodic_breakdown_stats(bt_data, 'day') + res = generate_periodic_breakdown_stats(bt_data, "day") assert isinstance(res, list) assert len(res) == 21 day = res[0] - assert 'date' in day - assert 'draws' in day - assert 'loses' in day - assert 'wins' in day - assert 'profit_abs' in day + assert "date" in day + assert "draws" in day + assert "loses" in day + assert "wins" in day + assert "profit_abs" in day # Select empty dataframe! - res = generate_periodic_breakdown_stats([], 'day') + res = generate_periodic_breakdown_stats([], "day") assert res == [] def test__get_resample_from_period(): - - assert _get_resample_from_period('day') == '1d' - assert _get_resample_from_period('week') == '1W-MON' - assert _get_resample_from_period('month') == '1ME' + assert _get_resample_from_period("day") == "1d" + assert _get_resample_from_period("week") == "1W-MON" + assert _get_resample_from_period("month") == "1ME" with pytest.raises(ValueError, match=r"Period noooo is not supported."): - _get_resample_from_period('noooo') + _get_resample_from_period("noooo") for period in BACKTEST_BREAKDOWNS: assert isinstance(_get_resample_from_period(period), str) @@ -544,11 +570,11 @@ def test__get_resample_from_period(): def test_show_sorted_pairlist(testdatadir, default_conf, capsys): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_stats(filename) - default_conf['backtest_show_pair_list'] = True + default_conf["backtest_show_pair_list"] = True show_sorted_pairlist(default_conf, bt_data) out, _err = capsys.readouterr() - assert 'Pairs for Strategy StrategyTestV3: \n[' in out - assert 'TOTAL' not in out + assert "Pairs for Strategy StrategyTestV3: \n[" in out + assert "TOTAL" not in out assert '"ETH/BTC", // ' in out diff --git a/tests/persistence/test_trade_custom_data.py b/tests/persistence/test_trade_custom_data.py index b244c61e8..b2971883d 100644 --- a/tests/persistence/test_trade_custom_data.py +++ b/tests/persistence/test_trade_custom_data.py @@ -20,7 +20,7 @@ from tests.conftest import ( @pytest.mark.parametrize("use_db", [True, False]) def test_trade_custom_data(fee, use_db): if not use_db: - disable_database_use('5m') + disable_database_use("5m") Trade.reset_trades() CustomDataWrapper.reset_custom_data() @@ -31,120 +31,121 @@ def test_trade_custom_data(fee, use_db): trade1.id = 1 assert trade1.get_all_custom_data() == [] - trade1.set_custom_data('test_str', 'test_value') - trade1.set_custom_data('test_int', 1) - trade1.set_custom_data('test_float', 1.55) - trade1.set_custom_data('test_bool', True) - trade1.set_custom_data('test_dict', {'test': 'dict'}) + trade1.set_custom_data("test_str", "test_value") + trade1.set_custom_data("test_int", 1) + trade1.set_custom_data("test_float", 1.55) + trade1.set_custom_data("test_bool", True) + trade1.set_custom_data("test_dict", {"test": "dict"}) assert len(trade1.get_all_custom_data()) == 5 - assert trade1.get_custom_data('test_str') == 'test_value' - trade1.set_custom_data('test_str', 'test_value_updated') - assert trade1.get_custom_data('test_str') == 'test_value_updated' + assert trade1.get_custom_data("test_str") == "test_value" + trade1.set_custom_data("test_str", "test_value_updated") + assert trade1.get_custom_data("test_str") == "test_value_updated" - assert trade1.get_custom_data('test_int') == 1 - assert isinstance(trade1.get_custom_data('test_int'), int) + assert trade1.get_custom_data("test_int") == 1 + assert isinstance(trade1.get_custom_data("test_int"), int) - assert trade1.get_custom_data('test_float') == 1.55 - assert isinstance(trade1.get_custom_data('test_float'), float) + assert trade1.get_custom_data("test_float") == 1.55 + assert isinstance(trade1.get_custom_data("test_float"), float) - assert trade1.get_custom_data('test_bool') is True - assert isinstance(trade1.get_custom_data('test_bool'), bool) + assert trade1.get_custom_data("test_bool") is True + assert isinstance(trade1.get_custom_data("test_bool"), bool) - assert trade1.get_custom_data('test_dict') == {'test': 'dict'} - assert isinstance(trade1.get_custom_data('test_dict'), dict) + assert trade1.get_custom_data("test_dict") == {"test": "dict"} + assert isinstance(trade1.get_custom_data("test_dict"), dict) if not use_db: enable_database_use() def test_trade_custom_data_strategy_compat(mocker, default_conf_usdt, fee): - - mocker.patch(f'{EXMS}.get_rate', return_value=0.50) - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=None) - default_conf_usdt["minimal_roi"] = {"0": 100} + mocker.patch(f"{EXMS}.get_rate", return_value=0.50) + mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=None) + default_conf_usdt["minimal_roi"] = {"0": 100} freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) create_mock_trades_usdt(fee) - trade1 = Trade.get_trades_proxy(pair='ADA/USDT')[0] - trade1.set_custom_data('test_str', 'test_value') - trade1.set_custom_data('test_int', 1) + trade1 = Trade.get_trades_proxy(pair="ADA/USDT")[0] + trade1.set_custom_data("test_str", "test_value") + trade1.set_custom_data("test_int", 1) def custom_exit(pair, trade, **kwargs): - - if pair == 'ADA/USDT': - custom_val = trade.get_custom_data('test_str') - custom_val_i = trade.get_custom_data('test_int') + if pair == "ADA/USDT": + custom_val = trade.get_custom_data("test_str") + custom_val_i = trade.get_custom_data("test_int") return f"{custom_val}_{custom_val_i}" freqtrade.strategy.custom_exit = custom_exit - ff_spy = mocker.spy(freqtrade.strategy, 'custom_exit') + ff_spy = mocker.spy(freqtrade.strategy, "custom_exit") trades = Trade.get_open_trades() freqtrade.exit_positions(trades) Trade.commit() - trade_after = Trade.get_trades_proxy(pair='ADA/USDT')[0] - assert trade_after.get_custom_data('test_str') == 'test_value' - assert trade_after.get_custom_data('test_int') == 1 + trade_after = Trade.get_trades_proxy(pair="ADA/USDT")[0] + assert trade_after.get_custom_data("test_str") == "test_value" + assert trade_after.get_custom_data("test_int") == 1 # 2 open pairs eligible for exit assert ff_spy.call_count == 2 - assert trade_after.exit_reason == 'test_value_1' + assert trade_after.exit_reason == "test_value_1" def test_trade_custom_data_strategy_backtest_compat(mocker, default_conf_usdt, fee): - - 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=10) - 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=10) mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.1, 0.1)) - mocker.patch('freqtrade.optimize.backtesting.Backtesting._run_funding_fees') + mocker.patch("freqtrade.optimize.backtesting.Backtesting._run_funding_fees") patch_exchange(mocker) - default_conf_usdt.update({ - "stake_amount": 100.0, - "max_open_trades": 2, - "dry_run_wallet": 1000.0, - "strategy": "StrategyTestV3", - "trading_mode": "futures", - "margin_mode": "isolated", - "stoploss": -2, - "minimal_roi": {"0": 100}, - }) - default_conf_usdt['pairlists'] = [{'method': 'StaticPairList', 'allow_inactive': True}] + default_conf_usdt.update( + { + "stake_amount": 100.0, + "max_open_trades": 2, + "dry_run_wallet": 1000.0, + "strategy": "StrategyTestV3", + "trading_mode": "futures", + "margin_mode": "isolated", + "stoploss": -2, + "minimal_roi": {"0": 100}, + } + ) + default_conf_usdt["pairlists"] = [{"method": "StaticPairList", "allow_inactive": True}] backtesting = Backtesting(default_conf_usdt) - df = generate_test_data(default_conf_usdt['timeframe'], 100, '2022-01-01 00:00:00+00:00') + df = generate_test_data(default_conf_usdt["timeframe"], 100, "2022-01-01 00:00:00+00:00") - pair_exp = 'XRP/USDT:USDT' + pair_exp = "XRP/USDT:USDT" def custom_exit(pair, trade, **kwargs): - custom_val = trade.get_custom_data('test_str') - custom_val_i = trade.get_custom_data('test_int', 0) + custom_val = trade.get_custom_data("test_str") + custom_val_i = trade.get_custom_data("test_int", 0) if pair == pair_exp: - trade.set_custom_data('test_str', 'test_value') - trade.set_custom_data('test_int', custom_val_i + 1) + trade.set_custom_data("test_str", "test_value") + trade.set_custom_data("test_int", custom_val_i + 1) if custom_val_i >= 2: return f"{custom_val}_{custom_val_i}" backtesting._set_strategy(backtesting.strategylist[0]) - processed = backtesting.strategy.advise_all_indicators({ - pair_exp: df, - 'BTC/USDT:USDT': df, - }) + processed = backtesting.strategy.advise_all_indicators( + { + pair_exp: df, + "BTC/USDT:USDT": df, + } + ) def fun(dataframe, *args, **kwargs): - dataframe.loc[dataframe.index == 50, 'enter_long'] = 1 + dataframe.loc[dataframe.index == 50, "enter_long"] = 1 return dataframe backtesting.strategy.advise_entry = fun backtesting.strategy.leverage = MagicMock(return_value=1) backtesting.strategy.custom_exit = custom_exit - ff_spy = mocker.spy(backtesting.strategy, 'custom_exit') + ff_spy = mocker.spy(backtesting.strategy, "custom_exit") min_date, max_date = get_timerange(processed) @@ -153,13 +154,13 @@ def test_trade_custom_data_strategy_backtest_compat(mocker, default_conf_usdt, f start_date=min_date, end_date=max_date, ) - results = result['results'] + results = result["results"] assert not results.empty assert len(results) == 2 - assert results['pair'][0] == pair_exp - assert results['pair'][1] == 'BTC/USDT:USDT' - assert results['exit_reason'][0] == 'test_value_2' - assert results['exit_reason'][1] == 'exit_signal' + assert results["pair"][0] == pair_exp + assert results["pair"][1] == "BTC/USDT:USDT" + assert results["exit_reason"][0] == "test_value_2" + assert results["exit_reason"][1] == "exit_signal" assert ff_spy.call_count == 7 Backtesting.cleanup()