feat: Implement live "replace_exit_order" functionality
This commit is contained in:
+48
-31
@@ -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}."
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user