Verify order fills on "detail" timeframe
This commit is contained in:
parent
25b8d34fe2
commit
a11d579bc2
@ -688,10 +688,11 @@ class Backtesting:
|
|||||||
trade.orders.append(order)
|
trade.orders.append(order)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
def _get_exit_trade_entry(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]:
|
def _get_exit_trade_entry(
|
||||||
|
self, trade: LocalTrade, row: Tuple, is_first: bool) -> Optional[LocalTrade]:
|
||||||
exit_candle_time: datetime = row[DATE_IDX].to_pydatetime()
|
exit_candle_time: datetime = row[DATE_IDX].to_pydatetime()
|
||||||
|
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if is_first and self.trading_mode == TradingMode.FUTURES:
|
||||||
trade.funding_fees = self.exchange.calculate_funding_fees(
|
trade.funding_fees = self.exchange.calculate_funding_fees(
|
||||||
self.futures_data[trade.pair],
|
self.futures_data[trade.pair],
|
||||||
amount=trade.amount,
|
amount=trade.amount,
|
||||||
@ -700,32 +701,7 @@ class Backtesting:
|
|||||||
close_date=exit_candle_time,
|
close_date=exit_candle_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.timeframe_detail and trade.pair in self.detail_data:
|
return self._get_exit_trade_entry_for_candle(trade, row)
|
||||||
exit_candle_end = exit_candle_time + timedelta(minutes=self.timeframe_min)
|
|
||||||
|
|
||||||
detail_data = self.detail_data[trade.pair]
|
|
||||||
detail_data = detail_data.loc[
|
|
||||||
(detail_data['date'] >= exit_candle_time) &
|
|
||||||
(detail_data['date'] < exit_candle_end)
|
|
||||||
].copy()
|
|
||||||
if len(detail_data) == 0:
|
|
||||||
# Fall back to "regular" data if no detail data was found for this candle
|
|
||||||
return self._get_exit_trade_entry_for_candle(trade, row)
|
|
||||||
detail_data.loc[:, 'enter_long'] = row[LONG_IDX]
|
|
||||||
detail_data.loc[:, 'exit_long'] = row[ELONG_IDX]
|
|
||||||
detail_data.loc[:, 'enter_short'] = row[SHORT_IDX]
|
|
||||||
detail_data.loc[:, 'exit_short'] = row[ESHORT_IDX]
|
|
||||||
detail_data.loc[:, 'enter_tag'] = row[ENTER_TAG_IDX]
|
|
||||||
detail_data.loc[:, 'exit_tag'] = row[EXIT_TAG_IDX]
|
|
||||||
for det_row in detail_data[HEADERS].values.tolist():
|
|
||||||
res = self._get_exit_trade_entry_for_candle(trade, det_row)
|
|
||||||
if res:
|
|
||||||
return res
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self._get_exit_trade_entry_for_candle(trade, row)
|
|
||||||
|
|
||||||
def get_valid_price_and_stake(
|
def get_valid_price_and_stake(
|
||||||
self, pair: str, row: Tuple, propose_rate: float, stake_amount: float,
|
self, pair: str, row: Tuple, propose_rate: float, stake_amount: float,
|
||||||
@ -1070,7 +1046,7 @@ 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) -> int:
|
max_open_trades: 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.
|
||||||
|
|
||||||
@ -1088,9 +1064,11 @@ class Backtesting:
|
|||||||
# without positionstacking, we can only have one open trade per pair.
|
# without positionstacking, we can only have one open trade per pair.
|
||||||
# max_open_trades must be respected
|
# max_open_trades must be respected
|
||||||
# don't open on the last row
|
# don't open on the last row
|
||||||
|
# We only open trades on the initial candle.
|
||||||
trade_dir = self.check_for_trade_entry(row)
|
trade_dir = self.check_for_trade_entry(row)
|
||||||
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 self.trade_slot_available(max_open_trades, open_trade_count_start)
|
and self.trade_slot_available(max_open_trades, 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
|
||||||
@ -1116,7 +1094,7 @@ class Backtesting:
|
|||||||
|
|
||||||
# 4. Create exit orders (if any)
|
# 4. Create exit orders (if any)
|
||||||
if not trade.open_order_id:
|
if not trade.open_order_id:
|
||||||
self._get_exit_trade_entry(trade, row) # Place exit order if necessary
|
self._get_exit_trade_entry(trade, row, is_first) # Place exit order if necessary
|
||||||
|
|
||||||
# 5. Process exit orders.
|
# 5. Process exit orders.
|
||||||
order = trade.select_order(trade.exit_side, is_open=True)
|
order = trade.select_order(trade.exit_side, is_open=True)
|
||||||
@ -1167,7 +1145,6 @@ class Backtesting:
|
|||||||
|
|
||||||
self.progress.init_step(BacktestState.BACKTEST, int(
|
self.progress.init_step(BacktestState.BACKTEST, int(
|
||||||
(end_date - start_date) / timedelta(minutes=self.timeframe_min)))
|
(end_date - start_date) / timedelta(minutes=self.timeframe_min)))
|
||||||
|
|
||||||
# Loop timerange and get candle for each pair at that point in time
|
# Loop timerange and get candle for each pair at that point in time
|
||||||
while current_time <= end_date:
|
while current_time <= end_date:
|
||||||
open_trade_count_start = LocalTrade.bt_open_open_trade_count
|
open_trade_count_start = LocalTrade.bt_open_open_trade_count
|
||||||
@ -1181,9 +1158,35 @@ class Backtesting:
|
|||||||
row_index += 1
|
row_index += 1
|
||||||
indexes[pair] = row_index
|
indexes[pair] = row_index
|
||||||
self.dataprovider._set_dataframe_max_index(row_index)
|
self.dataprovider._set_dataframe_max_index(row_index)
|
||||||
|
current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
|
||||||
|
if self.timeframe_detail and pair in self.detail_data:
|
||||||
|
exit_candle_end = current_detail_time + timedelta(minutes=self.timeframe_min)
|
||||||
|
|
||||||
open_trade_count_start = self.backtest_loop(
|
detail_data = self.detail_data[pair]
|
||||||
row, pair, current_time, end_date, max_open_trades, open_trade_count_start)
|
detail_data = detail_data.loc[
|
||||||
|
(detail_data['date'] >= current_detail_time) &
|
||||||
|
(detail_data['date'] < exit_candle_end)
|
||||||
|
].copy()
|
||||||
|
if len(detail_data) == 0:
|
||||||
|
# Fall back to "regular" data if no detail data was found for this candle
|
||||||
|
open_trade_count_start = self.backtest_loop(
|
||||||
|
row, pair, current_time, end_date, max_open_trades,
|
||||||
|
open_trade_count_start)
|
||||||
|
detail_data.loc[:, 'enter_long'] = row[LONG_IDX]
|
||||||
|
detail_data.loc[:, 'exit_long'] = row[ELONG_IDX]
|
||||||
|
detail_data.loc[:, 'enter_short'] = row[SHORT_IDX]
|
||||||
|
detail_data.loc[:, 'exit_short'] = row[ESHORT_IDX]
|
||||||
|
detail_data.loc[:, 'enter_tag'] = row[ENTER_TAG_IDX]
|
||||||
|
detail_data.loc[:, 'exit_tag'] = row[EXIT_TAG_IDX]
|
||||||
|
is_first = True
|
||||||
|
for det_row in detail_data[HEADERS].values.tolist():
|
||||||
|
open_trade_count_start = self.backtest_loop(
|
||||||
|
det_row, pair, current_time, end_date, max_open_trades,
|
||||||
|
open_trade_count_start, is_first)
|
||||||
|
is_first = False
|
||||||
|
else:
|
||||||
|
open_trade_count_start = self.backtest_loop(
|
||||||
|
row, pair, current_time, end_date, max_open_trades, open_trade_count_start)
|
||||||
|
|
||||||
# Move time one configured time_interval ahead.
|
# Move time one configured time_interval ahead.
|
||||||
self.progress.increment()
|
self.progress.increment()
|
||||||
|
Loading…
Reference in New Issue
Block a user