Fixed max_open_trades update from hyperopt

Fixed max_open_trades update from hyperopt + removed max_open_trades as a param to backtesting + refactoring
This commit is contained in:
Antonio Della Fortuna 2023-01-08 12:39:39 +01:00
parent 8c3ac56bc5
commit 464cb4761c
7 changed files with 132 additions and 52 deletions

View File

@ -252,7 +252,7 @@ AVAILABLE_CLI_OPTIONS = {
'--spaces', '--spaces',
help='Specify which parameters to hyperopt. Space-separated list.', help='Specify which parameters to hyperopt. Space-separated list.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss', choices=['all', 'buy', 'sell', 'roi', 'stoploss',
'trailing', 'protection', 'default', 'trades'], 'trailing', 'protection', 'trades', 'default'],
nargs='+', nargs='+',
default='default', default='default',
), ),

View File

@ -920,8 +920,9 @@ class Backtesting:
trade.close(exit_row[OPEN_IDX], show_msg=False) trade.close(exit_row[OPEN_IDX], show_msg=False)
LocalTrade.close_bt_trade(trade) LocalTrade.close_bt_trade(trade)
def trade_slot_available(self, max_open_trades: int, open_trade_count: int) -> bool: def trade_slot_available(self, open_trade_count: int) -> bool:
# Always allow trades when max_open_trades is enabled. # Always allow trades when max_open_trades is enabled.
max_open_trades = self.config['max_open_trades']
if max_open_trades <= 0 or open_trade_count < max_open_trades: if max_open_trades <= 0 or open_trade_count < max_open_trades:
return True return True
# Rejected trade # Rejected trade
@ -1051,7 +1052,6 @@ class Backtesting:
def backtest_loop( def backtest_loop(
self, row: Tuple, pair: str, current_time: datetime, end_date: datetime, self, row: Tuple, pair: str, current_time: datetime, end_date: datetime,
max_open_trades: int,
open_trade_count_start: int, is_first: bool = True) -> int: open_trade_count_start: int, is_first: bool = True) -> int:
""" """
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized. NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
@ -1075,7 +1075,7 @@ class Backtesting:
if ( if (
(self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0) (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0)
and is_first and is_first
and self.trade_slot_available(max_open_trades, open_trade_count_start) and self.trade_slot_available(open_trade_count_start)
and current_time != end_date and current_time != end_date
and trade_dir is not None and trade_dir is not None
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir) and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
@ -1122,8 +1122,7 @@ class Backtesting:
return open_trade_count_start return open_trade_count_start
def backtest(self, processed: Dict, def backtest(self, processed: Dict,
start_date: datetime, end_date: datetime, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
max_open_trades: int = 0) -> Dict[str, Any]:
""" """
Implement backtesting functionality Implement backtesting functionality
@ -1135,7 +1134,6 @@ class Backtesting:
optimize memory usage! optimize memory usage!
:param start_date: backtesting timerange start datetime :param start_date: backtesting timerange start datetime
:param end_date: backtesting timerange end datetime :param end_date: backtesting timerange end datetime
:param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited
:return: DataFrame with trades (results of backtesting) :return: DataFrame with trades (results of backtesting)
""" """
self.prepare_backtest(self.enable_protections) self.prepare_backtest(self.enable_protections)
@ -1176,7 +1174,7 @@ class Backtesting:
if len(detail_data) == 0: if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle # Fall back to "regular" data if no detail data was found for this candle
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, max_open_trades, row, pair, current_time, end_date,
open_trade_count_start) open_trade_count_start)
continue continue
detail_data.loc[:, 'enter_long'] = row[LONG_IDX] detail_data.loc[:, 'enter_long'] = row[LONG_IDX]
@ -1189,13 +1187,13 @@ class Backtesting:
current_time_det = current_time current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist(): for det_row in detail_data[HEADERS].values.tolist():
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
det_row, pair, current_time_det, end_date, max_open_trades, det_row, pair, current_time_det, end_date,
open_trade_count_start, is_first) open_trade_count_start, is_first)
current_time_det += timedelta(minutes=self.timeframe_detail_min) current_time_det += timedelta(minutes=self.timeframe_detail_min)
is_first = False is_first = False
else: else:
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, max_open_trades, open_trade_count_start) row, pair, current_time, end_date, open_trade_count_start)
# Move time one configured time_interval ahead. # Move time one configured time_interval ahead.
self.progress.increment() self.progress.increment()
@ -1227,14 +1225,11 @@ class Backtesting:
self._set_strategy(strat) self._set_strategy(strat)
# Use max_open_trades in backtesting, except --disable-max-market-positions is set # Use max_open_trades in backtesting, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
# Must come from strategy config, as the strategy may modify this setting.
max_open_trades = self.strategy.config['max_open_trades'] \
if self.strategy.config['max_open_trades'] != float('inf') else -1
else:
logger.info( logger.info(
'Ignoring max_open_trades (--disable-max-market-positions was used) ...') 'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0 self.strategy.max_open_trades = -1
self.config.update({'max_open_trades': float('inf')})
# need to reprocess data every time to populate signals # need to reprocess data every time to populate signals
preprocessed = self.strategy.advise_all_indicators(data) preprocessed = self.strategy.advise_all_indicators(data)
@ -1257,7 +1252,6 @@ class Backtesting:
processed=preprocessed, processed=preprocessed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=max_open_trades,
) )
backtest_end_time = datetime.now(timezone.utc) backtest_end_time = datetime.now(timezone.utc)
results.update({ results.update({

View File

@ -118,14 +118,10 @@ class Hyperopt:
self.current_best_epoch: Optional[Dict[str, Any]] = None self.current_best_epoch: Optional[Dict[str, Any]] = None
# Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True): if not self.config.get('use_max_market_positions', True):
self.max_open_trades = self.config['max_open_trades'] \
if self.config['max_open_trades'] != float('inf') else -1
else:
logger.debug('Ignoring max_open_trades (--disable-max-market-positions was used) ...') logger.debug('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
self.max_open_trades = 0 self.backtesting.strategy.max_open_trades = -1
config.update({'max_open_trades': float('inf')})
print("Strategy max open trades", self.max_open_trades)
if HyperoptTools.has_space(self.config, 'sell'): if HyperoptTools.has_space(self.config, 'sell'):
# Make sure use_exit_signal is enabled # Make sure use_exit_signal is enabled
@ -214,7 +210,8 @@ class Hyperopt:
if HyperoptTools.has_space(self.config, 'trailing'): if HyperoptTools.has_space(self.config, 'trailing'):
result['trailing'] = self.custom_hyperopt.generate_trailing_params(params) result['trailing'] = self.custom_hyperopt.generate_trailing_params(params)
if HyperoptTools.has_space(self.config, 'trades'): if HyperoptTools.has_space(self.config, 'trades'):
result['max_open_trades'] = {p.name: params.get(p.name) for p in self.trades_space} result['max_open_trades'] = {
'max_open_trades': self.backtesting.strategy.max_open_trades}
return result return result
@ -342,7 +339,21 @@ class Hyperopt:
d['trailing_only_offset_is_reached'] d['trailing_only_offset_is_reached']
if HyperoptTools.has_space(self.config, 'trades'): if HyperoptTools.has_space(self.config, 'trades'):
self.max_open_trades = params_dict['max_open_trades'] if self.config["stake_amount"] == "unlimited" and \
(params_dict['max_open_trades'] == -1 or params_dict['max_open_trades'] == 0):
# Ignore unlimited max open trades if stake amount is unlimited
params_dict.update({'max_open_trades': self.config['max_open_trades']})
updated_config_max_open_trades = int(params_dict['max_open_trades']) \
if (params_dict['max_open_trades'] != -1
and params_dict['max_open_trades'] != 0) else float('inf')
updated_strategy_max_open_trades = int(updated_config_max_open_trades) \
if updated_config_max_open_trades != float('inf') else -1
self.config.update({'max_open_trades': updated_config_max_open_trades})
self.backtesting.strategy.max_open_trades = updated_strategy_max_open_trades
with self.data_pickle_file.open('rb') as f: with self.data_pickle_file.open('rb') as f:
processed = load(f, mmap_mode='r') processed = load(f, mmap_mode='r')
@ -353,8 +364,7 @@ class Hyperopt:
bt_results = self.backtesting.backtest( bt_results = self.backtesting.backtest(
processed=processed, processed=processed,
start_date=self.min_date, start_date=self.min_date,
end_date=self.max_date, end_date=self.max_date
max_open_trades=self.max_open_trades,
) )
backtest_end_time = datetime.now(timezone.utc) backtest_end_time = datetime.now(timezone.utc)
bt_results.update({ bt_results.update({

View File

@ -919,6 +919,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
default_conf["trailing_stop_positive"] = data.trailing_stop_positive default_conf["trailing_stop_positive"] = data.trailing_stop_positive
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
default_conf["use_exit_signal"] = data.use_exit_signal default_conf["use_exit_signal"] = data.use_exit_signal
default_conf["max_open_trades"] = 10
mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
@ -951,7 +952,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
processed=data_processed, processed=data_processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']

View File

@ -96,7 +96,6 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
'processed': processed, 'processed': processed,
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 10,
} }
@ -684,6 +683,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -701,7 +702,6 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty
@ -785,6 +785,8 @@ def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_de
def custom_entry_price(proposed_rate, **kwargs): def custom_entry_price(proposed_rate, **kwargs):
return proposed_rate * 0.997 return proposed_rate * 0.997
default_conf_usdt['max_open_trades'] = 10
backtesting = Backtesting(default_conf_usdt) backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.populate_entry_trend = advise_entry backtesting.strategy.populate_entry_trend = advise_entry
@ -805,7 +807,6 @@ def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_de
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty
@ -859,6 +860,7 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) 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')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
patch_exchange(mocker) patch_exchange(mocker)
default_conf['max_open_trades'] = 1
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
# Testing dataframe contains 11 candles. Expecting 10 timed out orders. # Testing dataframe contains 11 candles. Expecting 10 timed out orders.
@ -871,7 +873,6 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
processed=deepcopy(data), processed=deepcopy(data),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert result['timedout_entry_orders'] == 10 assert result['timedout_entry_orders'] == 10
@ -879,6 +880,7 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir)
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 1
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -896,7 +898,6 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert not results['results'].empty assert not results['results'].empty
assert len(results['results']) == 1 assert len(results['results']) == 1
@ -904,6 +905,8 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -927,7 +930,6 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
@ -948,6 +950,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000)
@ -981,7 +984,6 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
assert count == 5 assert count == 5
@ -998,6 +1000,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
default_conf['enable_protections'] = True default_conf['enable_protections'] = True
default_conf['timeframe'] = '1m' default_conf['timeframe'] = '1m'
default_conf['max_open_trades'] = 1
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -1024,7 +1027,6 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert len(results['results']) == numres assert len(results['results']) == numres
@ -1062,11 +1064,12 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict) assert isinstance(processed, dict)
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
results = backtesting.backtest( results = backtesting.backtest(
processed=processed, processed=processed,
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=1,
) )
assert len(results['results']) == expected assert len(results['results']) == expected
@ -1077,7 +1080,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
buy_value = 1 buy_value = 1
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1094,6 +1097,7 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1107,6 +1111,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) 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')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
default_conf['max_open_trades'] = 10
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir) pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m' default_conf['timeframe'] = '1m'
@ -1165,6 +1170,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
if tres > 0: if tres > 0:
data[pair] = data[pair][tres:].reset_index() data[pair] = data[pair][tres:].reset_index()
default_conf['timeframe'] = '5m' default_conf['timeframe'] = '5m'
default_conf['max_open_trades'] = 3
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
@ -1173,11 +1179,11 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
backtest_conf = { backtest_conf = {
'processed': deepcopy(processed), 'processed': deepcopy(processed),
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 3,
} }
results = backtesting.backtest(**backtest_conf) results = backtesting.backtest(**backtest_conf)
@ -1195,11 +1201,12 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0] backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count ) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
backtest_conf = { backtest_conf = {
'processed': deepcopy(processed), 'processed': deepcopy(processed),
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 1,
} }
results = backtesting.backtest(**backtest_conf) 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

View File

@ -17,6 +17,7 @@ from tests.conftest import patch_exchange
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision', mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision',
lambda x, *args, **kwargs: round(x, 8)) lambda x, *args, **kwargs: round(x, 8))
@ -41,7 +42,6 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
processed=deepcopy(processed), processed=deepcopy(processed),
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
max_open_trades=10,
) )
results = result['results'] results = result['results']
assert not results.empty assert not results.empty

View File

@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring,W0212,C0103 # pragma pylint: disable=missing-docstring,W0212,C0103
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import wraps
from pathlib import Path from pathlib import Path
from unittest.mock import ANY, MagicMock, PropertyMock from unittest.mock import ANY, MagicMock, PropertyMock
@ -336,8 +337,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -708,8 +708,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -782,8 +781,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -825,8 +823,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf['max_open_trades']
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt.backtesting, "_position_stacking") assert hasattr(hyperopt.backtesting, "_position_stacking")
@ -880,7 +877,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
assert hyperopt.backtesting.strategy.buy_rsi.value == 35 assert hyperopt.backtesting.strategy.buy_rsi.value == 35
assert hyperopt.backtesting.strategy.sell_rsi.value == 74 assert hyperopt.backtesting.strategy.sell_rsi.value == 74
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
assert hyperopt.max_open_trades == 1 assert hyperopt.backtesting.strategy.max_open_trades == 1
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range) assert isinstance(buy_rsi_range, range)
# Range from 0 - 50 (inclusive) # Range from 0 - 50 (inclusive)
@ -891,7 +888,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30 assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30
assert hyperopt.backtesting.strategy.buy_rsi.value != 35 assert hyperopt.backtesting.strategy.buy_rsi.value != 35
assert hyperopt.backtesting.strategy.sell_rsi.value != 74 assert hyperopt.backtesting.strategy.sell_rsi.value != 74
assert hyperopt.max_open_trades != 1 assert hyperopt.backtesting.strategy.max_open_trades != 1
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1' hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1'
with pytest.raises(OperationalException, match="Estimator ET1 not supported."): with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
@ -992,3 +989,75 @@ def test_SKDecimal():
assert space.transform([2.0]) == [200] assert space.transform([2.0]) == [200]
assert space.transform([1.0]) == [100] assert space.transform([1.0]) == [100]
assert space.transform([1.5, 1.6]) == [150, 160] assert space.transform([1.5, 1.6]) == [150, 160]
def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmpdir, fee) -> None:
# This test is to ensure that unlimited max_open_trades are ignored for the backtesting
# if we have an unlimited stake amount
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
hyperopt_conf.update({
'strategy': 'HyperoptableStrategy',
'user_data_dir': Path(tmpdir),
'hyperopt_random_state': 42,
'spaces': ['trades'],
'stake_amount': 'unlimited'
})
hyperopt = Hyperopt(hyperopt_conf)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._get_params_dict',
return_value={
'max_open_trades': -1
})
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert hyperopt.backtesting.strategy.max_open_trades == 1
hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 1
def test_max_open_trades_consistency(mocker, hyperopt_conf, tmpdir, fee) -> None:
# This test is to ensure that max_open_trades is the same across all functions needing it
# after it has been changed from the hyperopt
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
hyperopt_conf.update({
'strategy': 'HyperoptableStrategy',
'user_data_dir': Path(tmpdir),
'hyperopt_random_state': 42,
'spaces': ['trades'],
'stake_amount': 'unlimited',
'dry_run_wallet': 8,
'available_capital': 8,
'dry_run': True,
'epochs': 1
})
hyperopt = Hyperopt(hyperopt_conf)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
first_time_evaluated = False
def stake_amount_interceptor(func):
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal first_time_evaluated
stake_amount = func(*args, **kwargs)
if first_time_evaluated is False:
assert stake_amount == 1
first_time_evaluated = True
return stake_amount
return wrapper
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount = stake_amount_interceptor(
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount)
hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 8
assert hyperopt.config['max_open_trades'] == 8