(fix) sync throttler pair-templated rate limits on dynamic pair registration - #211
(fix) sync throttler pair-templated rate limits on dynamic pair registration#211fengtality wants to merge 2 commits into
Conversation
…tration
Connectors are created with trading_pairs=[], so AsyncThrottler builds its
rate limits from an empty pair list at init. Pairs registered dynamically
never reach the throttler, and pair-scoped limit_ids (e.g. bybit's
v5/order/create-{PAIR}, v5/position/set-leverage-{PAIR}) resolve to None:
AttributeError 'NoneType' object has no attribute 'weight'.
Adds UnifiedConnectorService.sync_pair_derived_state(), which appends the
pair and mutates the existing throttler in place via add_rate_limits()
(the instance WebAssistantsFactory captured at init — reassigning
_throttler provably has no effect). Wired into every dynamic registration
site: TradingService.add_market (hoisted before its early return so a
transiently failed sync retries), ensure_data_connector_started,
_add_trading_pair_to_tracker (covers the data-connector bootstrap path),
and set_leverage (pair-scoped endpoint that bypassed registration).
Fixes #207
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile SummaryThis PR synchronizes state derived from dynamically registered trading pairs, particularly pair-templated throttler limits, while preserving the throttler instance captured during connector initialization.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up-review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| services/unified_connector_service.py | Adds centralized pair validation and registration with in-place synchronization of pair-derived throttler limits, then uses it in data and order-book connector paths. |
| services/trading_service.py | Moves dynamic pair synchronization ahead of the existing-market early return and makes the registration helper asynchronous. |
| services/perpetual_trading_service.py | Synchronizes pair-derived connector state before executing pair-scoped leverage requests. |
| test/test_sync_pair_derived_state.py | Covers pair registration, throttler identity, idempotency, missing pair lists, unknown-pair rejection, and repeated synchronization. |
Sequence Diagram
sequenceDiagram
participant Caller
participant Service
participant Connector
participant Throttler
participant Exchange
Caller->>Service: Register pair or set leverage
Service->>Connector: Resolve trading-pair symbol
Connector-->>Service: Pair recognized
Service->>Connector: Append pair if absent
Service->>Connector: Rebuild rate-limit rules
Service->>Throttler: add_rate_limits(rules)
Note over Throttler: Existing instance is mutated in place
Service->>Exchange: Initialize market or execute request
Exchange-->>Caller: Result
Reviews (2): Last reviewed commit: "(fix) adversarial-review fixes: validate..." | Re-trigger Greptile
…uard property eval - Validate the pair against the connector's symbol map BEFORE appending to _trading_pairs, raising ValueError for unknown pairs. There is no rollback path, and a poisoned entry breaks every per-pair consumer: status polling raises KeyError on the symbol map inside gathers without return_exceptions (killing balance/position/order updates until restart), and per-pair trading-rules rebuilds fail for ALL pairs. Previously a typo'd pair via add_market or set_leverage was registered permanently. Already-registered pairs skip re-validation (the symbol map may be transiently unavailable). - Evaluate rate_limits_rules inside the try: it is a property that can itself raise, and hasattr() evaluated it outside the guard (any non-AttributeError escaping aborted registration; an internal AttributeError silently skipped the sync with zero logging). - Scope comments to what this PR implements (the rules-fetch retry rationale belongs to the stacked #212). Findings from adversarial review; tests added for rejection-without- registration and skip-revalidation-when-registered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Adversarial review (independent reviewer + author verification) — verdict: core throttler fix confirmed sound (in-place mutation genuinely required;
Also corrected: the hoist-rationale comment referenced a "rules fetch" that only exists in the stacked #212. Verified clean: Gateway/Cython |
|
Test update:
Bybit Perpetual leverage for a dynamically registered pair ✅Add a second valid pair through the market/order-book flow ✅
Hummingbot currently has synchronous overrides for Backpack spot and Backpack perpetual. ❌
|
Fixes #207
Trading connectors are created with
trading_pairs=[], soAsyncThrottlerbuilds its rate limits from an empty pair list at init. Pairs registered dynamically never reach the throttler, and pair-templatedlimit_ids (bybit'sv5/order/create-{PAIR},v5/position/set-leverage-{PAIR}) resolve toNone:AttributeError: 'NoneType' object has no attribute 'weight'.Fix
UnifiedConnectorService.sync_pair_derived_state(connector, trading_pair)— idempotent, appends the pair and mutates the existing throttler in place viaadd_rate_limits()(the instanceWebAssistantsFactorycaptured at init; reassigning_throttlerprovably has no effect — see #207).add_rate_limitsskips knownlimit_ids, so repeat calls are free.Wired into every dynamic registration site:
TradingService.add_market— registration hoisted before the early return, so a transiently failed sync retries instead of sticking until restartensure_data_connector_startedand_add_trading_pair_to_tracker(covers the data-connector bootstrap path the issue flags)set_leverage— pair-scoped endpoint that bypassed all registration paths (the issue's headline symptom)Tests
5 tests using the real
AsyncThrottler, including an object-identity check that the captured instance learns the new limit, and idempotency (no duplicate limits). Suite: 90 passed; the 7 failures are pre-existing onmain(gateway-LP / controller-config).Stacked series: this PR → #208 fix (trading rules) → #210 fix (position mode).
🤖 Generated with Claude Code