feat: Implement live "replace_exit_order" functionality

This commit is contained in:
Matthias
2025-02-20 07:23:36 +01:00
parent 1970cc65c0
commit 0f9e61371c
+48 -31
View File
@@ -1603,27 +1603,29 @@ class FreqtradeBot(LoggingMixin):
self.replace_order(order, open_order, trade) self.replace_order(order, open_order, trade)
def handle_cancel_order( def handle_cancel_order(
self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str, replacing: bool = False
) -> None: ) -> bool:
""" """
Check if current analyzed order timed out and cancel if necessary. Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order() :param order: Order dict grabbed with exchange.fetch_order()
:param order_obj: Order object from the database. :param order_obj: Order object from the database.
:param trade: Trade object. :param trade: Trade object.
:return: None :return: True if the order was canceled, False otherwise.
""" """
if order["side"] == trade.entry_side: if order["side"] == trade.entry_side:
self.handle_cancel_enter(trade, order, order_obj, reason) return self.handle_cancel_enter(trade, order, order_obj, reason, replacing)
else: else:
canceled = self.handle_cancel_exit(trade, order, order_obj, reason) canceled = self.handle_cancel_exit(trade, order, order_obj, reason)
canceled_count = trade.get_canceled_exit_order_count() if not replacing:
max_timeouts = self.config.get("unfilledtimeout", {}).get("exit_timeout_count", 0) canceled_count = trade.get_canceled_exit_order_count()
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: max_timeouts = self.config.get("unfilledtimeout", {}).get("exit_timeout_count", 0)
logger.warning( if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
f"Emergency exiting trade {trade}, as the exit order " logger.warning(
f"timed out {max_timeouts} times. force selling {order['amount']}." f"Emergency exiting trade {trade}, as the exit order "
) f"timed out {max_timeouts} times. force selling {order['amount']}."
self.emergency_exit(trade, order["price"], order["amount"]) )
self.emergency_exit(trade, order["price"], order["amount"])
return canceled
def emergency_exit( def emergency_exit(
self, trade: Trade, price: float, sub_trade_amt: float | None = None self, trade: Trade, price: float, sub_trade_amt: float | None = None
@@ -1675,17 +1677,17 @@ class FreqtradeBot(LoggingMixin):
self.strategy.timeframe, latest_candle_open_date self.strategy.timeframe, latest_candle_open_date
) )
# Check if new candle # Check if new candle
if ( if order_obj and latest_candle_close_date > order_obj.order_date_utc:
order_obj is_entry = order_obj.side == trade.entry_side
and order_obj.side == trade.entry_side
and latest_candle_close_date > order_obj.order_date_utc
):
# New candle # New candle
proposed_rate = self.exchange.get_rate( proposed_rate = self.exchange.get_rate(
trade.pair, side="entry", is_short=trade.is_short, refresh=True trade.pair,
side="entry" if is_entry else "exit",
is_short=trade.is_short,
refresh=True,
) )
adjusted_entry_price = strategy_safe_wrapper( adjusted_entry_price = strategy_safe_wrapper(
self.strategy.adjust_entry_price, default_retval=order_obj.safe_placement_price self.strategy.adjust_order_price, default_retval=order_obj.safe_placement_price
)( )(
trade=trade, trade=trade,
order=order_obj, order=order_obj,
@@ -1695,6 +1697,7 @@ class FreqtradeBot(LoggingMixin):
current_order_rate=order_obj.safe_placement_price, current_order_rate=order_obj.safe_placement_price,
entry_tag=trade.enter_tag, entry_tag=trade.enter_tag,
side=trade.trade_direction, side=trade.trade_direction,
is_entry=is_entry,
) )
replacing = True replacing = True
@@ -1702,10 +1705,11 @@ class FreqtradeBot(LoggingMixin):
if not adjusted_entry_price: if not adjusted_entry_price:
replacing = False replacing = False
cancel_reason = constants.CANCEL_REASON["USER_CANCEL"] cancel_reason = constants.CANCEL_REASON["USER_CANCEL"]
if order_obj.safe_placement_price != adjusted_entry_price: if order_obj.safe_placement_price != adjusted_entry_price:
# cancel existing order if new price is supplied or None # cancel existing order if new price is supplied or None
res = self.handle_cancel_enter( res = self.handle_cancel_order(
trade, order, order_obj, cancel_reason, replacing=replacing order, order_obj, trade, cancel_reason, replacing=replacing
) )
if not res: if not res:
self.replace_order_failed( self.replace_order_failed(
@@ -1715,16 +1719,29 @@ class FreqtradeBot(LoggingMixin):
if adjusted_entry_price: if adjusted_entry_price:
# place new order only if new price is supplied # place new order only if new price is supplied
try: try:
if not self.execute_entry( if is_entry:
pair=trade.pair, succeeded = self.execute_entry(
stake_amount=( pair=trade.pair,
order_obj.safe_remaining * order_obj.safe_price / trade.leverage stake_amount=(
), order_obj.safe_remaining * order_obj.safe_price / trade.leverage
price=adjusted_entry_price, ),
trade=trade, price=adjusted_entry_price,
is_short=trade.is_short, trade=trade,
mode="replace", is_short=trade.is_short,
): mode="replace",
)
else:
succeeded = self.execute_trade_exit(
trade,
adjusted_entry_price,
exit_check=ExitCheckTuple(
exit_type=ExitType.CUSTOM_EXIT,
exit_reason=order_obj.ft_order_tag,
),
ordertype="limit",
sub_trade_amt=order_obj.safe_remaining,
)
if not succeeded:
self.replace_order_failed( self.replace_order_failed(
trade, f"Could not replace order for {trade}." trade, f"Could not replace order for {trade}."
) )