Skip to content

Commit 99cc36e

Browse files
committed
(feat) improve pair deregistration and validation for OB
1 parent f6b556f commit 99cc36e

1 file changed

Lines changed: 65 additions & 2 deletions

File tree

services/unified_connector_service.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)