{"record":{"id":"87444070ba2cf7f2","repo":"HKUDS/Vibe-Trading","slug":"binance-usd-m-position-reads-are-incoherent","errorCode":null,"errorMessage":"Binance USD-M position reads are incoherent","messagePattern":"Binance USD-M position reads are incoherent","errorType":"exception","errorClass":"UsdMObservationError","httpStatus":null,"severity":"error","filePath":"agent/src/trading/connectors/binance/usdm.py","lineNumber":157,"sourceCode":"    ):\n        raise UsdMObservationError(\"Binance USD-M account totals and USDT asset totals are incoherent\")\n    return values\n\n\ndef _join_positions(\n    account: Mapping[str, Any],\n    position_risk: list[Any],\n    close_enough: CloseEnough,\n) -> list[dict[str, Any]]:\n    account_rows = _mapping_rows(account.get(\"positions\"), \"account positions\")\n    risk_rows = _mapping_rows(position_risk, \"position risk\")\n    _require_one_way(account_rows)\n    _require_one_way(risk_rows)\n\n    account_active = _active_by_symbol(account_rows, \"account\")\n    risk_active = _active_by_symbol(risk_rows, \"position risk\")\n    if set(account_active) != set(risk_active):\n        raise UsdMObservationError(\"Binance USD-M position reads are incoherent\")\n\n    result: list[dict[str, Any]] = []\n    for raw_symbol in sorted(account_active):\n        account_row = account_active[raw_symbol]\n        risk_row = risk_active[raw_symbol]\n        quantity = _number(account_row.get(\"positionAmt\"), \"positionAmt\")\n        risk_quantity = _number(risk_row.get(\"positionAmt\"), \"positionAmt\")\n        entry_price = _number(account_row.get(\"entryPrice\"), \"entryPrice\", positive=True)\n        risk_entry = _number(risk_row.get(\"entryPrice\"), \"entryPrice\", positive=True)\n        if not math.isclose(quantity, risk_quantity, rel_tol=0, abs_tol=1e-12) or not math.isclose(\n            entry_price, risk_entry, rel_tol=0, abs_tol=1e-12\n        ):\n            raise UsdMObservationError(\"Binance USD-M position reads are incoherent\")\n        open_order_margins = (\n            _number(\n                account_row.get(\"openOrderInitialMargin\"),\n                \"openOrderInitialMargin\",\n                non_negative=True,","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/trading/connectors/binance/usdm.py#L139-L175","documentation":"Raised by _join_positions when the set of active position symbols reported by Binance USD-M Futures' account endpoint (v2/account or account) does not match the set from the position-risk endpoint (v2/positionRisk). The library cross-reads both endpoints and treats any disagreement in the *presence* of open positions as an incoherent snapshot, refusing to build the observation.","triggerScenarios":"Calling read_account_observation while a position is being opened/closed/filled so one endpoint reflects the change before the other; querying the two endpoints non-atomically (sequentially, not via the same moment); a symbol with positionAmt != 0 on one endpoint but 0/absent on the other.","commonSituations":"Race conditions during concurrent manual trading on the same account, slow API replication between Binance's account and risk services, partial fills in flight, or a stale cached response from one of the two endpoints.","solutions":["Retry the observation after a short delay (e.g. 1-2s) so both endpoints converge","Ensure no other bot/manual session trades this account while observations are read","Check whether one endpoint was served from cache/stale data and force a fresh request","Inspect the raw account_rows vs risk_rows symbol sets to confirm which side is stale"],"exampleFix":"// before\nobservation = await read_account_observation()\n\n// after\nfor attempt in range(3):\n    try:\n        observation = await read_account_observation()\n        break\n    except UsdMObservationError:\n        if attempt == 2:\n            raise\n        await asyncio.sleep(1.5)","handlingStrategy":"retry","validationCode":"# fetch both endpoints and compare active symbol sets before observing\nacct = await client.futures_account()\nrisk = await client.futures_position_risk()\na_syms = {p['symbol'] for p in acct['positions'] if float(p['positionAmt']) != 0}\nr_syms = {p['symbol'] for p in risk if float(p['positionAmt']) != 0}\nassert a_syms == r_syms, f\"stale snapshot: {a_syms ^ r_syms}\"","typeGuard":null,"tryCatchPattern":"try:\n    obs = await connector.read_account_observation()\nexcept UsdMObservationError as e:\n    if \"incoherent\" in str(e):\n        await asyncio.sleep(1.5)\n        obs = await connector.read_account_observation()\n    else:\n        raise","preventionTips":["Read observations when the account is quiet (no in-flight orders/fills)","Serialize observations with trading actions via a lock so they never overlap","Retry transient incoherence once or twice before surfacing it"],"tags":["binance","futures","data-consistency","race-condition"],"backgroundTag":"inconsistent-api-snapshot","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}