Merge pull request #2801 from freqtrade/backtest_arguments_2

Backtest arguments instead of dictionary
This commit is contained in:
Matthias 2020-01-25 13:11:23 +01:00 committed by GitHub
commit a97bb10877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 82 deletions

View File

@ -279,30 +279,28 @@ class Backtesting:
return bt_res return bt_res
return None return None
def backtest(self, args: Dict) -> DataFrame: def backtest(self, processed: Dict, stake_amount: float,
start_date, end_date,
max_open_trades: int = 0, position_stacking: bool = False) -> DataFrame:
""" """
Implements backtesting functionality Implement backtesting functionality
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.
Of course try to not have ugly code. By some accessor are sometime slower than functions. Of course try to not have ugly code. By some accessor are sometime slower than functions.
Avoid, logging on this method Avoid extensive logging in this method and functions it calls.
:param args: a dict containing: :param processed: a processed dictionary with format {pair, data}
stake_amount: btc amount to use for each trade :param stake_amount: amount to use for each trade
processed: a processed dictionary with format {pair, data} :param start_date: backtesting timerange start datetime
max_open_trades: maximum number of concurrent trades (default: 0, disabled) :param end_date: backtesting timerange end datetime
position_stacking: do we allow position stacking? (default: False) :param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited
:return: DataFrame :param position_stacking: do we allow position stacking?
:return: DataFrame with trades (results of backtesting)
""" """
# Arguments are long and noisy, so this is commented out. logger.debug(f"Run backtest, stake_amount: {stake_amount}, "
# Uncomment if you need to debug the backtest() method. f"start_date: {start_date}, end_date: {end_date}, "
# logger.debug(f"Start backtest, args: {args}") f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
processed = args['processed'] )
stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0)
position_stacking = args.get('position_stacking', False)
start_date = args['start_date']
end_date = args['end_date']
trades = [] trades = []
trade_count_lock: Dict = {} trade_count_lock: Dict = {}
@ -369,18 +367,21 @@ class Backtesting:
def start(self) -> None: def start(self) -> None:
""" """
Run a backtesting end-to-end Run backtesting end-to-end
:return: None :return: None
""" """
data: Dict[str, Any] = {} data: Dict[str, Any] = {}
logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using stake_amount: %s ...', self.config['stake_amount']) logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
# 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 self.config.get('use_max_market_positions', True):
max_open_trades = self.config['max_open_trades'] max_open_trades = self.config['max_open_trades']
else: else:
logger.info('Ignoring max_open_trades (--disable-max-market-positions was used) ...') logger.info('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0 max_open_trades = 0
position_stacking = self.config.get('position_stacking', False)
data, timerange = self.load_bt_data() data, timerange = self.load_bt_data()
@ -403,14 +404,12 @@ class Backtesting:
) )
# Execute backtest and print results # Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest( all_results[self.strategy.get_strategy_name()] = self.backtest(
{ processed=preprocessed,
'stake_amount': self.config.get('stake_amount'), stake_amount=self.config['stake_amount'],
'processed': preprocessed, start_date=min_date,
'max_open_trades': max_open_trades, end_date=max_date,
'position_stacking': self.config.get('position_stacking', False), max_open_trades=max_open_trades,
'start_date': min_date, position_stacking=position_stacking,
'end_date': max_date,
}
) )
for strategy, results in all_results.items(): for strategy, results in all_results.items():

View File

@ -372,14 +372,12 @@ class Hyperopt:
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
backtesting_results = self.backtesting.backtest( backtesting_results = self.backtesting.backtest(
{ processed=processed,
'stake_amount': self.config['stake_amount'], stake_amount=self.config['stake_amount'],
'processed': processed, start_date=min_date,
'max_open_trades': self.max_open_trades, end_date=max_date,
'position_stacking': self.position_stacking, max_open_trades=self.max_open_trades,
'start_date': min_date, position_stacking=self.position_stacking,
'end_date': max_date,
}
) )
return self._get_results_dict(backtesting_results, min_date, max_date, return self._get_results_dict(backtesting_results, min_date, max_date,
params_dict, params_details) params_dict, params_details)

View File

@ -382,13 +382,11 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
data_processed = {pair: frame.copy()} data_processed = {pair: frame.copy()}
min_date, max_date = get_timerange({pair: frame}) min_date, max_date = get_timerange({pair: frame})
results = backtesting.backtest( results = backtesting.backtest(
{ processed=data_processed,
'stake_amount': default_conf['stake_amount'], stake_amount=default_conf['stake_amount'],
'processed': data_processed, start_date=min_date,
'max_open_trades': 10, end_date=max_date,
'start_date': min_date, max_open_trades=10,
'end_date': max_date,
}
) )
assert len(results) == len(data.trades) assert len(results) == len(data.trades)

View File

@ -103,14 +103,12 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict) assert isinstance(processed, dict)
results = backtesting.backtest( results = backtesting.backtest(
{ processed=processed,
'stake_amount': config['stake_amount'], stake_amount=config['stake_amount'],
'processed': processed, start_date=min_date,
'max_open_trades': 1, end_date=max_date,
'position_stacking': False, max_open_trades=1,
'start_date': min_date, position_stacking=False,
'end_date': max_date,
}
) )
# results :: <class 'pandas.core.frame.DataFrame'> # results :: <class 'pandas.core.frame.DataFrame'>
assert len(results) == num_results assert len(results) == num_results
@ -132,7 +130,7 @@ def _load_pair_as_ticks(pair, tickfreq):
# FIX: fixturize this? # FIX: fixturize this?
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC', record=None): def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair]) data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
data = trim_dictlist(data, -201) data = trim_dictlist(data, -201)
patch_exchange(mocker) patch_exchange(mocker)
@ -140,13 +138,12 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC', record=
processed = backtesting.strategy.tickerdata_to_dataframe(data) processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
return { return {
'stake_amount': conf['stake_amount'],
'processed': processed, 'processed': processed,
'max_open_trades': 10, 'stake_amount': conf['stake_amount'],
'position_stacking': False,
'record': record,
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 10,
'position_stacking': False,
} }
@ -422,14 +419,12 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
data_processed = backtesting.strategy.tickerdata_to_dataframe(data) data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timerange(data_processed) min_date, max_date = get_timerange(data_processed)
results = backtesting.backtest( results = backtesting.backtest(
{ processed=data_processed,
'stake_amount': default_conf['stake_amount'], stake_amount=default_conf['stake_amount'],
'processed': data_processed, start_date=min_date,
'max_open_trades': 10, end_date=max_date,
'position_stacking': False, max_open_trades=10,
'start_date': min_date, position_stacking=False,
'end_date': max_date,
}
) )
assert not results.empty assert not results.empty
assert len(results) == 2 assert len(results) == 2
@ -478,14 +473,12 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -
processed = backtesting.strategy.tickerdata_to_dataframe(data) processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
results = backtesting.backtest( results = backtesting.backtest(
{ processed=processed,
'stake_amount': default_conf['stake_amount'], stake_amount=default_conf['stake_amount'],
'processed': processed, start_date=min_date,
'max_open_trades': 1, end_date=max_date,
'position_stacking': False, max_open_trades=1,
'start_date': min_date, position_stacking=False,
'end_date': max_date,
}
) )
assert not results.empty assert not results.empty
assert len(results) == 1 assert len(results) == 1
@ -525,7 +518,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.strategy.advise_buy = fun # Override backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override backtesting.strategy.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(**backtest_conf)
assert results.empty assert results.empty
@ -540,7 +533,7 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.strategy.advise_buy = fun # Override backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override backtesting.strategy.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(**backtest_conf)
assert results.empty assert results.empty
@ -553,7 +546,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override backtesting.strategy.advise_sell = _trend_alternate # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(**backtest_conf)
backtesting._store_backtest_result("test_.json", results) backtesting._store_backtest_result("test_.json", results)
# 200 candles in backtest data # 200 candles in backtest data
# won't buy on first (shifted by 1) # won't buy on first (shifted by 1)
@ -598,15 +591,15 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
data_processed = backtesting.strategy.tickerdata_to_dataframe(data) data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timerange(data_processed) min_date, max_date = get_timerange(data_processed)
backtest_conf = { backtest_conf = {
'stake_amount': default_conf['stake_amount'],
'processed': data_processed, 'processed': data_processed,
'max_open_trades': 3, 'stake_amount': default_conf['stake_amount'],
'position_stacking': False,
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 3,
'position_stacking': False,
} }
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(**backtest_conf)
# Make sure we have parallel trades # Make sure we have parallel trades
assert len(evaluate_result_multi(results, '5m', 2)) > 0 assert len(evaluate_result_multi(results, '5m', 2)) > 0
@ -614,14 +607,14 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
assert len(evaluate_result_multi(results, '5m', 3)) == 0 assert len(evaluate_result_multi(results, '5m', 3)) == 0
backtest_conf = { backtest_conf = {
'stake_amount': default_conf['stake_amount'],
'processed': data_processed, 'processed': data_processed,
'max_open_trades': 1, 'stake_amount': default_conf['stake_amount'],
'position_stacking': False,
'start_date': min_date, 'start_date': min_date,
'end_date': max_date, 'end_date': max_date,
'max_open_trades': 1,
'position_stacking': False,
} }
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(**backtest_conf)
assert len(evaluate_result_multi(results, '5m', 1)) == 0 assert len(evaluate_result_multi(results, '5m', 1)) == 0