Allow "detailed" backtesting timeframe to look into the candle
This commit is contained in:
parent
123971d271
commit
88172fab82
@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
|||||||
"max_open_trades", "stake_amount", "fee", "pairs"]
|
"max_open_trades", "stake_amount", "fee", "pairs"]
|
||||||
|
|
||||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
||||||
"enable_protections", "dry_run_wallet",
|
"enable_protections", "dry_run_wallet", "detail_timeframe",
|
||||||
"strategy_list", "export", "exportfilename"]
|
"strategy_list", "export", "exportfilename"]
|
||||||
|
|
||||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||||
|
@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
help='Override the value of the `stake_amount` configuration setting.',
|
help='Override the value of the `stake_amount` configuration setting.',
|
||||||
),
|
),
|
||||||
# Backtesting
|
# Backtesting
|
||||||
|
"detail_timeframe": Arg(
|
||||||
|
'--timeframe-detail',
|
||||||
|
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
|
||||||
|
),
|
||||||
"position_stacking": Arg(
|
"position_stacking": Arg(
|
||||||
'--eps', '--enable-position-stacking',
|
'--eps', '--enable-position-stacking',
|
||||||
help='Allow buying the same pair multiple times (position stacking).',
|
help='Allow buying the same pair multiple times (position stacking).',
|
||||||
|
@ -242,6 +242,9 @@ class Configuration:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='detail_timeframe',
|
||||||
|
logstring='Parameter --detail-timeframe detected, '
|
||||||
|
'using {} for intra-candle backtesting')
|
||||||
self._args_to_config(config, argname='stake_amount',
|
self._args_to_config(config, argname='stake_amount',
|
||||||
logstring='Parameter --stake-amount detected, '
|
logstring='Parameter --stake-amount detected, '
|
||||||
'overriding stake_amount to: {} ...')
|
'overriding stake_amount to: {} ...')
|
||||||
|
@ -86,6 +86,16 @@ class Backtesting:
|
|||||||
"configuration or as cli argument `--timeframe 5m`")
|
"configuration or as cli argument `--timeframe 5m`")
|
||||||
self.timeframe = str(self.config.get('timeframe'))
|
self.timeframe = str(self.config.get('timeframe'))
|
||||||
self.timeframe_min = timeframe_to_minutes(self.timeframe)
|
self.timeframe_min = timeframe_to_minutes(self.timeframe)
|
||||||
|
# Load detail timeframe if specified
|
||||||
|
self.timeframe_detail = str(self.config.get('detail_timeframe', ''))
|
||||||
|
if self.timeframe_detail:
|
||||||
|
self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail)
|
||||||
|
if self.timeframe_min <= self.timeframe_detail_min:
|
||||||
|
raise OperationalException(
|
||||||
|
"Detail timeframe must be smaller than strategy timeframe.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.timeframe_detail_min = 0
|
||||||
|
|
||||||
self.pairlists = PairListManager(self.exchange, self.config)
|
self.pairlists = PairListManager(self.exchange, self.config)
|
||||||
if 'VolumePairList' in self.pairlists.name_list:
|
if 'VolumePairList' in self.pairlists.name_list:
|
||||||
@ -158,7 +168,7 @@ class Backtesting:
|
|||||||
conf['protections'] = strategy.protections
|
conf['protections'] = strategy.protections
|
||||||
self.protections = ProtectionManager(self.config, strategy.protections)
|
self.protections = ProtectionManager(self.config, strategy.protections)
|
||||||
|
|
||||||
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:
|
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange, Dict[str, DataFrame]]:
|
||||||
"""
|
"""
|
||||||
Loads backtest data and returns the data combined with the timerange
|
Loads backtest data and returns the data combined with the timerange
|
||||||
as tuple.
|
as tuple.
|
||||||
@ -174,6 +184,18 @@ class Backtesting:
|
|||||||
fail_without_data=True,
|
fail_without_data=True,
|
||||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||||
)
|
)
|
||||||
|
if self.timeframe_detail:
|
||||||
|
detail_data = history.load_data(
|
||||||
|
datadir=self.config['datadir'],
|
||||||
|
pairs=self.pairlists.whitelist,
|
||||||
|
timeframe=self.timeframe_detail,
|
||||||
|
timerange=self.timerange,
|
||||||
|
startup_candles=0,
|
||||||
|
fail_without_data=True,
|
||||||
|
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
detail_data = None
|
||||||
|
|
||||||
min_date, max_date = history.get_timerange(data)
|
min_date, max_date = history.get_timerange(data)
|
||||||
|
|
||||||
@ -186,7 +208,7 @@ class Backtesting:
|
|||||||
self.required_startup, min_date)
|
self.required_startup, min_date)
|
||||||
|
|
||||||
self.progress.set_new_value(1)
|
self.progress.set_new_value(1)
|
||||||
return data, self.timerange
|
return data, self.timerange, detail_data
|
||||||
|
|
||||||
def prepare_backtest(self, enable_protections):
|
def prepare_backtest(self, enable_protections):
|
||||||
"""
|
"""
|
||||||
@ -318,7 +340,8 @@ class Backtesting:
|
|||||||
else:
|
else:
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
|
||||||
|
sell_row: Tuple) -> Optional[LocalTrade]:
|
||||||
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
||||||
sell_candle_time, sell_row[BUY_IDX],
|
sell_candle_time, sell_row[BUY_IDX],
|
||||||
@ -346,6 +369,29 @@ class Backtesting:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
||||||
|
if self.timeframe_detail:
|
||||||
|
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
|
sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min)
|
||||||
|
|
||||||
|
detail_data = self.detail_data[trade.pair]
|
||||||
|
detail_data = detail_data.loc[
|
||||||
|
(detail_data['date'] >= sell_candle_time) &
|
||||||
|
(detail_data['date'] < sell_candle_end)
|
||||||
|
]
|
||||||
|
detail_data['buy'] = sell_row[BUY_IDX]
|
||||||
|
detail_data['sell'] = sell_row[SELL_IDX]
|
||||||
|
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
|
||||||
|
for det_row in detail_data[headers].values.tolist():
|
||||||
|
res = self._get_sell_trade_entry_for_candle(trade, det_row)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._get_sell_trade_entry_for_candle(trade, sell_row)
|
||||||
|
|
||||||
def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]:
|
def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]:
|
||||||
try:
|
try:
|
||||||
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
||||||
@ -591,7 +637,7 @@ class Backtesting:
|
|||||||
"""
|
"""
|
||||||
data: Dict[str, Any] = {}
|
data: Dict[str, Any] = {}
|
||||||
|
|
||||||
data, timerange = self.load_bt_data()
|
data, timerange, self.detail_data = self.load_bt_data()
|
||||||
logger.info("Dataload complete. Calculating indicators")
|
logger.info("Dataload complete. Calculating indicators")
|
||||||
|
|
||||||
for strat in self.strategylist:
|
for strat in self.strategylist:
|
||||||
|
Loading…
Reference in New Issue
Block a user