HKUDS/Vibe-Trading · error · UsdMObservationError

Binance USD-M cross position must report zero isolated margi

Error message

Binance USD-M cross position must report zero isolated margin

What it means

The inverse invariant: a cross-margin position (isolated=false) must report isolatedMargin == 0. Any non-zero isolatedMargin on a cross position means the data is inconsistent and the snapshot is rejected.

Source

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

            if not close_enough(
                _number(account_row.get(account_field), account_field),
                _number(risk_row.get(risk_field), risk_field),
            ):
                raise UsdMObservationError("Binance USD-M position reads are incoherent")
        if str(risk_row.get("marginAsset") or "").upper() != "USDT":
            raise UsdMObservationError("Binance USD-M Shadow Account supports USDT collateral only")
        isolated = account_row.get("isolated")
        if not isinstance(isolated, bool):
            raise UsdMObservationError("Binance USD-M margin mode is missing")
        isolated_margin = _number(
            risk_row.get("isolatedMargin"),
            "isolatedMargin",
            non_negative=True,
        )
        if isolated and isolated_margin == 0:
            raise UsdMObservationError("Binance USD-M isolated position requires positive isolated margin")
        if not isolated and isolated_margin != 0:
            raise UsdMObservationError("Binance USD-M cross position must report zero isolated margin")
        result.append(
            {
                "symbol": _canonical_symbol(raw_symbol),
                "quantity": quantity,
                "entry_price": entry_price,
                "leverage": _number(account_row.get("leverage"), "leverage", positive=True),
                "margin_mode": "isolated" if isolated else "cross",
                "isolated_margin": isolated_margin if isolated else None,
                "unrealized_pnl": _number(risk_row.get("unRealizedProfit"), "unRealizedProfit"),
                "initial_margin": _number(
                    risk_row.get("positionInitialMargin"),
                    "positionInitialMargin",
                    non_negative=True,
                ),
                "maintenance_margin": _number(risk_row.get("maintMargin"), "maintMargin", non_negative=True),
                "update_time": _integer(risk_row.get("updateTime"), "updateTime"),
            }
        )

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Retry after a short delay once the margin-type change propagates
  2. Verify current margin type via GET /fapi/v2/positionRisk and compare
  3. Avoid flipping margin types while the observation loop is running

Example fix

// before
await client.futures_change_margin_type(symbol='BTCUSDT', marginType='CROSSED')
obs = await connector.read_account_observation()

// after
await client.futures_change_margin_type(symbol='BTCUSDT', marginType='CROSSED')
await asyncio.sleep(2)
obs = await connector.read_account_observation()
Defensive patterns

Strategy: retry

Validate before calling

risk = await client.futures_position_risk()
for p in risk:
    if p['marginType'].lower() == 'cross' and float(p['isolatedMargin']) != 0:
        raise RuntimeError(f"{p['symbol']}: cross position still reports isolated margin; wait for propagation")

Try / catch

try:
    obs = await connector.read_account_observation()
except UsdMObservationError as e:
    if "zero isolated margin" in str(e):
        await asyncio.sleep(2)
        obs = await connector.read_account_observation()
    else:
        raise

Prevention

When it happens

Trigger: A position switched from isolated to cross while stale risk data still carries the old isolatedMargin value; mid-transition reads; Binance service lag after POST /fapi/v1/marginType.

Common situations: Reading observations within seconds of toggling margin type, or a partially propagated account state after a position was converted.

Related errors


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