HKUDS/Vibe-Trading · critical · UsdMObservationError

Binance USD-M account totals are incoherent

Error message

Binance USD-M account totals are incoherent

What it means

Raised by _require_coherent_totals when reported account totals do not match derived values within tolerance: margin_balance must equal wallet_balance + total_unrealized_pnl (and similar comparisons checked via close_enough). This guards against partially-read or inconsistent snapshots from the exchange. The error indicates the account payload is internally contradictory, so risk computations would be wrong.

Source

Thrown at agent/src/trading/connectors/binance/usdm.py:281

        (
            account["total_unrealized_pnl"],
            sum(row["unrealized_pnl"] for row in positions),
        ),
        (
            account["total_initial_margin"],
            sum(row["initial_margin"] for row in positions),
        ),
        (
            account["total_maintenance_margin"],
            sum(row["maintenance_margin"] for row in positions),
        ),
        (
            account["margin_balance"],
            account["wallet_balance"] + account["total_unrealized_pnl"],
        ),
    )
    if any(not close_enough(reported, derived) for reported, derived in comparisons):
        raise UsdMObservationError("Binance USD-M account totals are incoherent")


def _mapping_rows(value: Any, name: str) -> list[Mapping[str, Any]]:
    if not isinstance(value, list) or any(not isinstance(row, Mapping) for row in value):
        raise UsdMObservationError(f"Binance USD-M {name} payload is invalid")
    return value


def _require_one_way(rows: list[Mapping[str, Any]]) -> None:
    if any(str(row.get("positionSide") or "").upper() != "BOTH" for row in rows):
        raise UsdMObservationError("Binance USD-M Shadow Account requires one-way position mode")


def _active_by_symbol(rows: list[Mapping[str, Any]], source: str) -> dict[str, Mapping[str, Any]]:
    active: dict[str, Mapping[str, Any]] = {}
    for row in rows:
        quantity = _number(row.get("positionAmt"), "positionAmt")
        if quantity == 0:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Retry the observation — transient incoherence from concurrent position updates usually resolves
  2. Fetch account balances and positions atomically (same snapshot / Binance userData stream instead of two REST calls)
  3. Increase absolute_tolerance in the connector configuration if the account is large and discrepancies are float noise
  4. Fix mock/fixture numbers so margin_balance == wallet_balance + total_unrealized_pnl

Example fix

// before
{"wallet_balance": 1000.0, "total_unrealized_pnl": 5.0, "margin_balance": 1010.0}
// after
{"wallet_balance": 1000.0, "total_unrealized_pnl": 5.0, "margin_balance": 1005.0}
Defensive patterns

Strategy: retry

Validate before calling

def totals_coherent(acc: dict, tol: float) -> bool:
    return abs(acc["margin_balance"] - (acc["wallet_balance"] + acc["total_unrealized_pnl"])) <= tol

if not totals_coherent(acc, tol):
    acc = refetch_account()

Try / catch

for attempt in range(3):
    try:
        obs = connector.read_account_observation(...)
        break
    except UsdMObservationError as exc:
        if "incoherent" not in str(exc) or attempt == 2:
            raise
        sleep(0.5)

Prevention

When it happens

Trigger: read_account_observation receives an account dict where margin_balance deviates from wallet_balance + total_unrealized_pnl beyond absolute_tolerance, typically because balances and positions were fetched at different times or from different endpoints.

Common situations: Active positions changing between the balance and positionRisk REST calls; using absolute_tolerance too small for large accounts (floating point noise); mocks with hand-picked inconsistent numbers; exchange during settlement/funding events reporting transiently inconsistent totals.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/9abea2300c7489f1. Report an issue: GitHub.