{"record":{"id":"4f57a4b8caa883d1","repo":"HKUDS/Vibe-Trading","slug":"binance-usd-m-name-payload-is-invalid","errorCode":null,"errorMessage":"Binance USD-M {name} payload is invalid","messagePattern":"Binance USD-M (.+?) payload is invalid","errorType":"exception","errorClass":"UsdMObservationError","httpStatus":null,"severity":"error","filePath":"agent/src/trading/connectors/binance/usdm.py","lineNumber":286,"sourceCode":"            account[\"total_initial_margin\"],\n            sum(row[\"initial_margin\"] for row in positions),\n        ),\n        (\n            account[\"total_maintenance_margin\"],\n            sum(row[\"maintenance_margin\"] for row in positions),\n        ),\n        (\n            account[\"margin_balance\"],\n            account[\"wallet_balance\"] + account[\"total_unrealized_pnl\"],\n        ),\n    )\n    if any(not close_enough(reported, derived) for reported, derived in comparisons):\n        raise UsdMObservationError(\"Binance USD-M account totals are incoherent\")\n\n\ndef _mapping_rows(value: Any, name: str) -> list[Mapping[str, Any]]:\n    if not isinstance(value, list) or any(not isinstance(row, Mapping) for row in value):\n        raise UsdMObservationError(f\"Binance USD-M {name} payload is invalid\")\n    return value\n\n\ndef _require_one_way(rows: list[Mapping[str, Any]]) -> None:\n    if any(str(row.get(\"positionSide\") or \"\").upper() != \"BOTH\" for row in rows):\n        raise UsdMObservationError(\"Binance USD-M Shadow Account requires one-way position mode\")\n\n\ndef _active_by_symbol(rows: list[Mapping[str, Any]], source: str) -> dict[str, Mapping[str, Any]]:\n    active: dict[str, Mapping[str, Any]] = {}\n    for row in rows:\n        quantity = _number(row.get(\"positionAmt\"), \"positionAmt\")\n        if quantity == 0:\n            continue\n        symbol = str(row.get(\"symbol\") or \"\").upper()\n        _canonical_symbol(symbol)\n        if symbol in active:\n            raise UsdMObservationError(f\"duplicate {source} position symbol\")","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/trading/connectors/binance/usdm.py#L268-L304","documentation":"Raised by _mapping_rows when a payload expected to be a list of Mapping rows (e.g. positionRisk or balance arrays) is not a list, or contains elements that are not Mapping-like. It is a shape check that runs before any field extraction on exchange responses. Any non-list (dict, None, string) or non-dict element triggers it.","triggerScenarios":"Passing a dict keyed by symbol instead of a list of rows to _join_positions or _require_usdt_assets; a None payload from a failed upstream call; elements that are strings or numbers instead of objects; API responses where Binance wrapped the array in an error object.","commonSituations":"Binance returning an error object {'code': -1, 'msg': ...} instead of the expected array; mocks returning json.loads of the wrong fixture; upstream HTTP client returning None on non-200 without raising; version changes in the connector changing the expected payload shape.","solutions":["Log the raw payload and verify it is actually a JSON array of objects before parsing","Check the HTTP status / error body of the upstream Binance call — an error envelope commonly masquerades as this failure","Fix the caller to pass response['data'] (the list) rather than the whole response dict","Update mocks/fixtures to return lists of objects"],"exampleFix":"// before\nrows = response  # dict {\"code\":0, \"msg\":\"ok\", \"data\":[...]}\n_join_positions(rows)\n// after\nrows = response[\"data\"]\n_join_positions(rows)","handlingStrategy":"type-guard","validationCode":"from collections.abc import Mapping\n\ndef is_row_list(value) -> bool:\n    return isinstance(value, list) and all(isinstance(r, Mapping) for r in value)\n\nrows = response.get(\"data\") if isinstance(response, dict) else response\nassert is_row_list(rows), f\"unexpected payload shape: {type(response)}\"","typeGuard":"from collections.abc import Mapping\nfrom typing import Any\n\ndef is_payload_rows(value: Any) -> TypeGuard[list[Mapping[str, Any]]]:\n    return isinstance(value, list) and all(isinstance(r, Mapping) for r in value)","tryCatchPattern":"try:\n    obs = connector.read_account_observation(...)\nexcept UsdMObservationError as exc:\n    if \"payload is invalid\" in str(exc):\n        logger.error(\"bad payload shape from exchange: %s\", raw_response)\n    raise","preventionTips":["Check HTTP status and error codes before parsing exchange bodies","Validate list-of-objects shape before passing payloads","Extract nested arrays (response['data']) explicitly"],"tags":["binance","usdm","payload-validation","type-check"],"backgroundTag":"payload-shape-validation","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}