@@ -268,8 +268,54 @@ async def ensure_data_connector_started(
268268
269269 except Exception as e :
270270 logger .error (f"Error starting data connector { connector_name } : { e } " )
271+ self ._purge_trading_pair_registration (connector , trading_pair )
271272 return False
272273
274+ async def _is_trading_pair_supported (
275+ self ,
276+ connector : ConnectorBase ,
277+ trading_pair : str
278+ ) -> bool :
279+ """Check that a trading pair exists in the connector's symbol map.
280+
281+ Registering an unknown pair poisons the order book WebSocket loop for every
282+ other pair: the data source iterates over all registered pairs when
283+ subscribing, so a single unknown pair raises and aborts the whole
284+ subscription, which then retries and fails forever.
285+ """
286+ if not hasattr (connector , "exchange_symbol_associated_to_pair" ):
287+ return True
288+ try :
289+ await connector .exchange_symbol_associated_to_pair (trading_pair = trading_pair )
290+ return True
291+ except KeyError :
292+ return False
293+ except Exception as e :
294+ # Symbol map unavailable (network error, etc.) - don't block on it
295+ logger .warning (f"Could not validate { trading_pair } against symbol map: { e } " )
296+ return True
297+
298+ def _purge_trading_pair_registration (
299+ self ,
300+ connector : ConnectorBase ,
301+ trading_pair : str
302+ ):
303+ """Unregister a trading pair from the connector and its order book tracker.
304+
305+ Used to roll back a failed registration so a pair that never got an order
306+ book cannot break the shared WebSocket subscription.
307+ """
308+ targets = [getattr (connector , "_trading_pairs" , None )]
309+ tracker = getattr (connector , "order_book_tracker" , None )
310+ if tracker is not None :
311+ targets .append (getattr (tracker , "_trading_pairs" , None ))
312+
313+ for pairs in targets :
314+ # Connector and tracker often share the same list object
315+ if pairs is not None and trading_pair in pairs :
316+ pairs .remove (trading_pair )
317+ logger .info (f"Unregistered { trading_pair } after failed order book initialization" )
318+
273319 # =========================================================================
274320 # Best Connector Selection (THE KEY FIX)
275321 # =========================================================================
@@ -373,6 +419,14 @@ async def initialize_order_book(
373419 except Exception :
374420 pass
375421
422+ # Reject pairs the exchange doesn't list before registering them anywhere
423+ if not await self ._is_trading_pair_supported (connector , trading_pair ):
424+ logger .error (
425+ f"Trading pair { trading_pair } is not listed on { connector_name } - "
426+ f"refusing to register it (check the quote asset)"
427+ )
428+ return False
429+
376430 # For data connectors, ensure network is started
377431 if connector_name in self ._data_connectors :
378432 if not self ._data_connectors_started .get (connector_name , False ):
@@ -480,10 +534,12 @@ async def _add_trading_pair_to_tracker(
480534 logger .error (f"Fallback order book initialization failed: { e } " )
481535
482536 logger .error (f"Failed to add { trading_pair } to order book tracker" )
537+ self ._purge_trading_pair_registration (connector , trading_pair )
483538 return False
484539
485540 except Exception as e :
486541 logger .error (f"Error adding trading pair { trading_pair } : { e } " , exc_info = True )
542+ self ._purge_trading_pair_registration (connector , trading_pair )
487543 return False
488544
489545 async def remove_trading_pair (
@@ -540,11 +596,18 @@ async def _remove_trading_pair_from_tracker(
540596 tracker = connector .order_book_tracker
541597 if trading_pair in tracker .order_books :
542598 del tracker .order_books [trading_pair ]
543- if trading_pair in tracker ._trading_pairs :
544- tracker ._trading_pairs .remove (trading_pair )
599+ self ._purge_trading_pair_registration (connector , trading_pair )
545600 logger .info (f"Removed trading pair { trading_pair } via manual fallback" )
546601 return True
547602
603+ # No order book, but the pair may still be registered - a pair stuck in
604+ # the subscription list with no order book is exactly what breaks the
605+ # WebSocket loop, so unregister it anyway.
606+ if trading_pair in getattr (tracker , "_trading_pairs" , []):
607+ self ._purge_trading_pair_registration (connector , trading_pair )
608+ logger .info (f"Unregistered untracked trading pair { trading_pair } " )
609+ return True
610+
548611 logger .warning (f"Trading pair { trading_pair } not found" )
549612 return False
550613
0 commit comments