BT: Refactor open order management.

This commit is contained in:
eSeR1805 2022-05-01 18:06:20 +03:00
parent 9d205132d0
commit 4e43194dfe
No known key found for this signature in database
GPG Key ID: BA53686259B46936

View File

@ -872,13 +872,51 @@ class Backtesting:
self.protections.stop_per_pair(pair, current_time) self.protections.stop_per_pair(pair, current_time)
self.protections.global_stop(current_time) self.protections.global_stop(current_time)
def check_order_replace(self, trade: LocalTrade, current_time, row: Tuple) -> bool: def manage_open_orders(self, trade: LocalTrade, current_time, row: Tuple) -> bool:
""" """
Check if an entry order has to be replaced and do so. If user requested cancellation Check if any open order needs to be cancelled or replaced.
and there are no filled orders in the trade will instruct caller to delete the trade.
Returns True if the trade should be deleted. Returns True if the trade should be deleted.
""" """
for order in [o for o in trade.orders if o.ft_is_open]: for order in [o for o in trade.orders if o.ft_is_open]:
if self.check_order_cancel(trade, order, current_time):
# delete trade due to order timeout
return True
elif self.check_order_replace(trade, order, current_time, row):
# delete trade due to user request
return True
# default maintain trade
return False
def check_order_cancel(self, trade: LocalTrade, order: Order, current_time) -> bool:
"""
Check if current analyzed order has to be canceled.
Returns True if the trade should be Deleted (initial order was canceled).
"""
timedout = self.strategy.ft_check_timed_out(trade, order, current_time)
if timedout:
if order.side == trade.entry_side:
self.timedout_entry_orders += 1
if trade.nr_of_successful_entries == 0:
# Remove trade due to entry timeout expiration.
return True
else:
# Close additional entry order
del trade.orders[trade.orders.index(order)]
if order.side == trade.exit_side:
self.timedout_exit_orders += 1
# Close exit order and retry exiting on next signal.
del trade.orders[trade.orders.index(order)]
return False
def check_order_replace(self, trade: LocalTrade, order: Order, current_time,
row: Tuple) -> bool:
"""
Check if current analyzed entry order has to be replaced and do so.
If user requested cancellation and there are no filled orders in the trade will
instruct caller to delete the trade.
Returns True if the trade should be deleted.
"""
# only check on new candles for open entry orders # only check on new candles for open entry orders
if order.side == trade.entry_side and current_time > order.order_date_utc: if order.side == trade.entry_side and current_time > order.order_date_utc:
requested_rate = strategy_safe_wrapper(self.strategy.adjust_entry_price, requested_rate = strategy_safe_wrapper(self.strategy.adjust_entry_price,
@ -906,30 +944,6 @@ class Backtesting:
return (trade.nr_of_successful_entries == 0) return (trade.nr_of_successful_entries == 0)
return False return False
def check_order_cancel(self, trade: LocalTrade, current_time) -> bool:
"""
Check if an order has been canceled.
Returns True if the trade should be Deleted (initial order was canceled).
"""
for order in [o for o in trade.orders if o.ft_is_open]:
timedout = self.strategy.ft_check_timed_out(trade, order, current_time)
if timedout:
if order.side == trade.entry_side:
self.timedout_entry_orders += 1
if trade.nr_of_successful_entries == 0:
# Remove trade due to entry timeout expiration.
return True
else:
# Close additional entry order
del trade.orders[trade.orders.index(order)]
if order.side == trade.exit_side:
self.timedout_exit_orders += 1
# Close exit order and retry exiting on next signal.
del trade.orders[trade.orders.index(order)]
return False
def validate_row( def validate_row(
self, data: Dict, pair: str, row_index: int, current_time: datetime) -> Optional[Tuple]: self, data: Dict, pair: str, row_index: int, current_time: datetime) -> Optional[Tuple]:
try: try:
@ -999,17 +1013,14 @@ class Backtesting:
self.dataprovider._set_dataframe_max_index(row_index) self.dataprovider._set_dataframe_max_index(row_index)
for t in list(open_trades[pair]): for t in list(open_trades[pair]):
# 1. Cancel expired entry/exit orders. # 1. Manage currently open orders of active trades
order_cancel = self.check_order_cancel(t, current_time) if self.manage_open_orders(t, current_time, row):
# 2. Replace/cancel (user requested) entry orders. # Close trade
order_replace = self.check_order_replace(t, current_time, row)
if order_cancel or order_replace:
# Close trade due to entry timeout expiration or cancellation.
open_trade_count -= 1 open_trade_count -= 1
open_trades[pair].remove(t) open_trades[pair].remove(t)
self.wallets.update() self.wallets.update()
# 3. Process entries. # 2. Process entries.
# 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
@ -1032,7 +1043,7 @@ class Backtesting:
open_trades[pair].append(trade) open_trades[pair].append(trade)
for trade in list(open_trades[pair]): for trade in list(open_trades[pair]):
# 4. Process entry orders. # 3. Process entry orders.
order = trade.select_order(trade.entry_side, is_open=True) order = trade.select_order(trade.entry_side, is_open=True)
if order and self._get_order_filled(order.price, row): if order and self._get_order_filled(order.price, row):
order.close_bt_order(current_time) order.close_bt_order(current_time)
@ -1040,11 +1051,11 @@ class Backtesting:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
self.wallets.update() self.wallets.update()
# 5. 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) # Place exit order if necessary
# 6. 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)
if order and self._get_order_filled(order.price, row): if order and self._get_order_filled(order.price, row):
trade.open_order_id = None trade.open_order_id = None