@@ -730,7 +730,7 @@ def process_open_trade_positions(self):
730730 for trade in Trade .get_open_trades ():
731731 # If there is any open orders, wait for them to finish.
732732 # TODO Remove to allow mul open orders
733- if not trade .has_open_orders :
733+ if trade . has_open_position or trade .has_open_orders :
734734 # Do a wallets update (will be ratelimited to once per hour)
735735 self .wallets .update (False )
736736 try :
@@ -808,7 +808,10 @@ def check_and_call_adjust_trade_position(self, trade: Trade):
808808 )
809809
810810 if amount == 0.0 :
811- logger .info ("Amount to exit is 0.0 due to exchange limits - not exiting." )
811+ logger .info (
812+ f"Wanted to exit of { stake_amount } amount, "
813+ "but exit amount is now 0.0 due to exchange limits - not exiting."
814+ )
812815 return
813816
814817 remaining = (trade .amount - amount ) * current_exit_rate
@@ -923,6 +926,10 @@ def execute_entry(
923926 ):
924927 logger .info (f"User denied entry for { pair } ." )
925928 return False
929+
930+ if trade and self .handle_similar_open_order (trade , enter_limit_requested , amount , side ):
931+ return False
932+
926933 order = self .exchange .create_order (
927934 pair = pair ,
928935 ordertype = order_type ,
@@ -1303,8 +1310,8 @@ def exit_positions(self, trades: list[Trade]) -> int:
13031310 logger .warning (
13041311 f"Unable to handle stoploss on exchange for { trade .pair } : { exception } "
13051312 )
1306- # Check if we can exit our current pair
1307- if not trade .has_open_orders and trade .is_open and self .handle_trade (trade ):
1313+ # Check if we can exit our current position for this trade
1314+ if trade .has_open_position and trade .is_open and self .handle_trade (trade ):
13081315 trades_closed += 1
13091316
13101317 except DependencyException as exception :
@@ -1448,9 +1455,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
14481455 self .handle_protections (trade .pair , trade .trade_direction )
14491456 return True
14501457
1451- if trade .has_open_orders or not trade .is_open :
1452- # Trade has an open order, Stoploss-handling can't happen in this case
1453- # as the Amount on the exchange is tied up in another trade.
1458+ if not trade .has_open_position or not trade .is_open :
14541459 # The trade can be closed already (sell-order fill confirmation came in this iteration)
14551460 return False
14561461
@@ -1718,30 +1723,75 @@ def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade)
17181723 logger .warning (f"Unable to replace order for { trade .pair } : { exception } " )
17191724 self .replace_order_failed (trade , f"Could not replace order for { trade } ." )
17201725
1726+ def cancel_open_orders_of_trade (
1727+ self , trade : Trade , sides : list [str ], reason : str , replacing : bool = False
1728+ ) -> None :
1729+ """
1730+ Cancel trade orders of specified sides that are currently open
1731+ :param trade: Trade object of the trade we're analyzing
1732+ :param reason: The reason for that cancellation
1733+ :param sides: The sides where cancellation should take place
1734+ :return: None
1735+ """
1736+
1737+ for open_order in trade .open_orders :
1738+ try :
1739+ order = self .exchange .fetch_order (open_order .order_id , trade .pair )
1740+ except ExchangeError :
1741+ logger .info ("Can't query order for %s due to %s" , trade , traceback .format_exc ())
1742+ continue
1743+
1744+ if order ["side" ] in sides :
1745+ if order ["side" ] == trade .entry_side :
1746+ self .handle_cancel_enter (trade , order , open_order , reason , replacing )
1747+
1748+ elif order ["side" ] == trade .exit_side :
1749+ self .handle_cancel_exit (trade , order , open_order , reason )
1750+
17211751 def cancel_all_open_orders (self ) -> None :
17221752 """
17231753 Cancel all orders that are currently open
17241754 :return: None
17251755 """
17261756
17271757 for trade in Trade .get_open_trades ():
1728- for open_order in trade .open_orders :
1729- try :
1730- order = self .exchange .fetch_order (open_order .order_id , trade .pair )
1731- except ExchangeError :
1732- logger .info ("Can't query order for %s due to %s" , trade , traceback .format_exc ())
1733- continue
1758+ self .cancel_open_orders_of_trade (
1759+ trade , [trade .entry_side , trade .exit_side ], constants .CANCEL_REASON ["ALL_CANCELLED" ]
1760+ )
17341761
1735- if order ["side" ] == trade .entry_side :
1736- self .handle_cancel_enter (
1737- trade , order , open_order , constants .CANCEL_REASON ["ALL_CANCELLED" ]
1738- )
1762+ Trade .commit ()
17391763
1740- elif order ["side" ] == trade .exit_side :
1741- self .handle_cancel_exit (
1742- trade , order , open_order , constants .CANCEL_REASON ["ALL_CANCELLED" ]
1764+ def handle_similar_open_order (
1765+ self , trade : Trade , price : float , amount : float , side : str
1766+ ) -> bool :
1767+ """
1768+ Keep existing open order if same amount and side otherwise cancel
1769+ :param trade: Trade object of the trade we're analyzing
1770+ :param price: Limit price of the potential new order
1771+ :param amount: Quantity of assets of the potential new order
1772+ :param side: Side of the potential new order
1773+ :return: True if an existing similar order was found
1774+ """
1775+ if trade .has_open_orders :
1776+ oo = trade .select_order (side , True )
1777+ if oo is not None :
1778+ if (price == oo .price ) and (side == oo .side ) and (amount == oo .amount ):
1779+ logger .info (
1780+ f"A similar open order was found for { trade .pair } . "
1781+ f"Keeping existing { trade .exit_side } order. { price = } , { amount = } "
17431782 )
1744- Trade .commit ()
1783+ return True
1784+ # cancel open orders of this trade if order is different
1785+ self .cancel_open_orders_of_trade (
1786+ trade ,
1787+ [trade .entry_side , trade .exit_side ],
1788+ constants .CANCEL_REASON ["REPLACE" ],
1789+ True ,
1790+ )
1791+ Trade .commit ()
1792+ return False
1793+
1794+ return False
17451795
17461796 def handle_cancel_enter (
17471797 self ,
@@ -1924,7 +1974,11 @@ def _safe_exit_amount(self, trade: Trade, pair: str, amount: float) -> float:
19241974 return amount
19251975
19261976 trade_base_currency = self .exchange .get_pair_base_currency (pair )
1927- wallet_amount = self .wallets .get_free (trade_base_currency )
1977+ # Free + Used - open orders will eventually still be canceled.
1978+ wallet_amount = self .wallets .get_free (trade_base_currency ) + self .wallets .get_used (
1979+ trade_base_currency
1980+ )
1981+
19281982 logger .debug (f"{ pair } - Wallet: { wallet_amount } - Trade-amount: { amount } " )
19291983 if wallet_amount >= amount :
19301984 return amount
@@ -2017,6 +2071,10 @@ def execute_trade_exit(
20172071 logger .info (f"User denied exit for { trade .pair } ." )
20182072 return False
20192073
2074+ if trade .has_open_orders :
2075+ if self .handle_similar_open_order (trade , limit , amount , trade .exit_side ):
2076+ return False
2077+
20202078 try :
20212079 # Execute sell and update trade record
20222080 order = self .exchange .create_order (
0 commit comments