diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0dd99aea3..455dceda2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1123,6 +1123,7 @@ class Backtesting: if self.manage_open_orders(t, current_time, row): # Close trade open_trade_count -= 1 + open_trade_count_start -= 1 open_trades[pair].remove(t) LocalTrade.trades_open.remove(t) self.wallets.update() diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 907e97fb7..fa59762db 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -799,6 +799,35 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)) +def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) -> None: + # This strategy intentionally places unfillable orders. + default_conf['strategy'] = 'StrategyTestV3CustomEntryPrice' + default_conf['startup_candle_count'] = 0 + # Cancel unfilled order after 4 minutes on 5m timeframe. + default_conf["unfilledtimeout"] = {"entry": 4} + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + patch_exchange(mocker) + backtesting = Backtesting(default_conf) + backtesting._set_strategy(backtesting.strategylist[0]) + # Testing dataframe contains 11 candles. Expecting 10 timed out orders. + timerange = TimeRange('date', 'date', 1517227800, 1517231100) + data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], + timerange=timerange) + min_date, max_date = get_timerange(data) + + result = backtesting.backtest( + processed=deepcopy(data), + start_date=min_date, + end_date=max_date, + max_open_trades=1, + position_stacking=False, + ) + + assert result['timedout_entry_orders'] == 10 + + def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index f0d74698e..6c28c1cac 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1457,6 +1457,7 @@ def test_api_strategies(botclient, tmpdir): 'InformativeDecoratorTest', 'StrategyTestV2', 'StrategyTestV3', + 'StrategyTestV3CustomEntryPrice', 'StrategyTestV3Futures', 'freqai_test_classifier', 'freqai_test_multimodel_strat', diff --git a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py new file mode 100644 index 000000000..872984156 --- /dev/null +++ b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py @@ -0,0 +1,37 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement + +from datetime import datetime +from typing import Optional + +from pandas import DataFrame +from strategy_test_v3 import StrategyTestV3 + + +class StrategyTestV3CustomEntryPrice(StrategyTestV3): + """ + Strategy used by tests freqtrade bot. + Please do not modify this strategy, it's intended for internal use only. + Please look at the SampleStrategy in the user_data/strategy directory + or strategy repository https://github.com/freqtrade/freqtrade-strategies + for samples and inspiration. + """ + new_entry_price: float = 0.001 + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + dataframe['volume'] > 0, + 'enter_long'] = 1 + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + return dataframe + + def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + entry_tag: Optional[str], side: str, **kwargs) -> float: + + return self.new_entry_price diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index ae5a4024c..2d13fc380 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -34,7 +34,7 @@ def test_search_all_strategies_no_failed(): directory = Path(__file__).parent / "strats" strategies = StrategyResolver._search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) - assert len(strategies) == 9 + assert len(strategies) == 10 assert isinstance(strategies[0], dict) @@ -42,10 +42,11 @@ def test_search_all_strategies_with_failed(): directory = Path(__file__).parent / "strats" strategies = StrategyResolver._search_all_objects(directory, enum_failed=True) assert isinstance(strategies, list) - assert len(strategies) == 10 + assert len(strategies) == 11 # with enum_failed=True search_all_objects() shall find 2 good strategies # and 1 which fails to load - assert len([x for x in strategies if x['class'] is not None]) == 9 + assert len([x for x in strategies if x['class'] is not None]) == 10 + assert len([x for x in strategies if x['class'] is None]) == 1 directory = Path(__file__).parent / "strats_nonexistingdir"