HKUDS/Vibe-Trading · error · UsdMObservationError

Binance USD-M Shadow Account requires one-way position mode

Error message

Binance USD-M Shadow Account requires one-way position mode

What it means

Raised by _require_one_way when any position row has positionSide != 'BOTH' (case-insensitive). Binance Hedge Mode sets positionSide to LONG or SHORT per side; this connector's Shadow Accounting only supports One-way Mode where a symbol has a single position with positionSide BOTH. The account must be switched to one-way mode on the exchange before the connector can read it.

Source

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

        ),
        (
            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:
            continue
        symbol = str(row.get("symbol") or "").upper()
        _canonical_symbol(symbol)
        if symbol in active:
            raise UsdMObservationError(f"duplicate {source} position symbol")
        active[symbol] = row
    return active


def _canonical_symbol(symbol: str) -> str:
    if not symbol.endswith("USDT") or len(symbol) <= 4 or not symbol.isalnum():

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Close all open positions and open orders, then switch to One-way Mode (Binance UI or POST /fapi/v1/positionSide/dual with dualSidePosition=false)
  2. Remove conflicting strategies/bots that require hedge mode from this account
  3. Update test fixtures so positionSide is 'BOTH' or absent (row.get defaults to '')... ensure it is literally BOTH

Example fix

# before
{"symbol": "BTCUSDT", "positionAmt": "1", "positionSide": "LONG"}
# after
{"symbol": "BTCUSDT", "positionAmt": "1", "positionSide": "BOTH"}
Defensive patterns

Strategy: validation

Validate before calling

def is_one_way(position_rows: list[dict]) -> bool:
    return all(str(r.get("positionSide") or "").upper() == "BOTH" for r in position_rows)

if not is_one_way(rows):
    raise RuntimeError("account is in hedge mode; switch to one-way")

Try / catch

try:
    obs = connector.read_account_observation(...)
except UsdMObservationError as exc:
    if "one-way position mode" in str(exc):
        notify_operator("switch Binance account to one-way mode and restart")
    raise

Prevention

When it happens

Trigger: The Binance futures account has Position Mode set to Hedge Side (dual-side); positionRisk rows then carry positionSide='LONG'/'SHORT' and _join_positions raises on the first such row.

Common situations: Account was switched to Hedge Mode manually on the Binance UI or via POST /fapi/v1/positionSide/dual to run another strategy; existing open hedge-mode positions block switching back; CI fixtures written for hedge mode.

Related errors


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