Skip to content

Portfolio, positions and risk

VerifiedOwner Backend / platformLast verified backend@efa38e04e2db37a444e9f17100323c2ad4b83647 nautilus@2.0.0rc3

Account state, positions, orders and fills are Nautilus's own domain (portfolio, accounting, and risk engine) — ONIX transports them, it does not recompute them. This page covers the surfaces that expose that state, their per-node availability, what happens to that state across a process restart, and the one arithmetic-fidelity limit worth stating precisely.

Read surfaces, and why "empty" is the wrong word for them

Route Returns
GET /v2/account official Nautilus account state, balances as decimal strings
GET /v2/accounts multi-account-safe list form, each with its own AccountStateSummary and provenance envelope
GET /v2/portfolio official portfolio snapshot: net positions, unrealized PnL
GET /v2/positions paginated PositionSummary rows, filterable by OPEN/CLOSED state
GET /v2/orders paginated OrderSummary rows, filterable by OPEN (every non-terminal status) or HISTORY
GET /v2/fills paginated FillSummary rows

Every row carries decimal strings for money/quantity and nanosecond-string timestamps — never floats on the wire — and, where applicable, an onix.execution_provenance envelope naming the exact environment, account, venue, OMS type, and strategy/revision/job/run identity that produced it. Position rows additionally carry state (OPEN/CLOSED), mark_px/ts_mark (the price and time unrealized PnL was computed at — always stated per-position, never claimed as a portfolio- level mark), and duration_ns. An unknown future enum value (an order status Nautilus has not yet had at the time a client was built, for example) is required to be preserved and treated as non-terminal, not silently coerced to a known value or dropped.

Availability is per node, not a single build-wide answer, and none of these six routes serve "empty" state on a deferring node. onix_engine.spine.deferred.DEFERRED_OPERATIONS covers this whole lane (spine.account.get, spine.accounts.list, spine.portfolio.get, spine.orders.list, spine.fills.list, spine.positions.list, plus the execution SSE stream and the three sandbox-lifecycle operations):

Operation Core spine Binance node IBKR node
account, accounts, portfolio UNAVAILABLE (501) UNAVAILABLE (501) READY
orders, fills, positions UNAVAILABLE (501) UNAVAILABLE (501) READY
execution SSE stream UNAVAILABLE (501) UNSUPPORTED (404) UNSUPPORTED (404)

The IBKR node implements this read surface against a live execution-projection (see execution-runtime.md) and serves it through the same shared gateway_v2 handlers every other node uses — it is a working operator surface, not a deferred one, and marking it unavailable merely because the paper-execution lane is deferred elsewhere would take that surface off any consumer. Read availability from the node you are talking to.

On the nodes that defer this lane, the previous behavior was not to serve genuinely empty state: the routes stayed wired and answered 200 with a zeroed portfolio and 200 [] for orders/fills/ positions, while the capability registry reported all of them READY. That was corrected specifically because "200, no orders" is indistinguishable from "this account has never traded" to a caller. The corrected behavior is a named, typed HTTP 501 refusal (capability.status: "UNAVAILABLE", reason: "DEFERRED_PAPER_PRODUCT") — distinct from the HTTP 404 NOT_SUPPORTED_ON_THIS_NODE a node without the concept at all would answer. This documentation therefore does not describe the core-spine/Binance state as "empty" anywhere: it is a deliberately refused capability, not a successfully served absence.

Persistence and recovery across a process restart

Because no market-spine process registers any execution client (see execution-runtime.md) and the paper-execution lane above is deferred on every node that would otherwise use it, there is currently no live order/position state on the core spine or Binance node for a restart to lose. Stated for completeness anyway, because the fact generalizes: Nautilus's own Cache save/load is a proven no-op at this pin (no backing-store field exists on CacheConfig; see ../runtime/cache-message-bus.md), so if a future release attaches a real execution client to a Nautilus node in this fleet, that node's Portfolio/Cache state would not currently survive a process restart through any Nautilus-provided persistence mechanism — ONIX would need to add its own, the same way it already does for hydration scheduling state and consolidation watermarks (see ../data/catalog.md), not assume Nautilus does this for free. On the IBKR node specifically, "recovery" after a reconnect is not a persistence question at all: orders/ fills/positions/portfolio are read live off the official IBKR execution client's own account/order state each call, so a reconnect re-populates them from the broker's own reconciliation, not from anything ONIX stored — an unconfigured or never-connected exec client has simply never been able to produce that state, reported honestly by name (ACCOUNT_NOT_CONFIGURED / EXEC_CLIENT_NOT_CONNECTED) rather than as an empty-but-successful read.

Nautilus's own PnL arithmetic — stated honestly

Nautilus computes position PnL in f64 (IEEE-754 double), then stores and transports the result as an exact decimal at the account currency's precision. This is a real, upstream property of the engine ONIX depends on, and ONIX states it rather than concealing it: values ONIX itself computes (detector/signal arithmetic) are fixed-point decimal end to end, with float arithmetic denied at the lint level — but a realized_pnl or unrealized_pnl field originating from Nautilus's own Position accounting is exact as transported, not exact as computed: the underlying arithmetic that produced it was IEEE-754, not decimal. ONIX does not attempt to "fix" this by reimplementing position accounting — doing so would replace a well-tested upstream component with a less-tested local one, and would violate the boundary described in ../nautilus-boundary.md. What ONIX does instead: it constructs prices and quantities from decimal strings (Nautilus's own Price.from_str/Quantity.from_str) rather than float constructors on its own side of the boundary, so no additional float error is introduced by ONIX code — the remaining, small float-rounding characteristic is Nautilus's own and is named as such.

Risk

The risk engine attached to each Nautilus node runs with Nautilus's own default configuration; no ONIX-authored risk engine, position sizing, or exposure system exists anywhere in this backend — per ../nautilus-boundary.md, risk is explicitly Nautilus's responsibility, not ONIX's. Because no market-spine process registers any execution client (see execution-runtime.md), those risk-engine instances currently have no order flow to evaluate at all in the tracked production and development-activation states. A backtest run's risk behavior is Nautilus's own simulated-execution risk handling, exercised against a sealed strategy revision, never against live capital. A sandbox run would exercise the same mechanism if activated, but sandbox execution is itself deferred (UNAVAILABLE/501) at this pin — see execution-runtime.md.

What this page does not claim

No account, position, or order surface documented here is evidence of live trading activity by itself — these are read contracts over whatever Nautilus state a given node actually holds. On the core spine and Binance node this whole lane is a deliberately refused capability (UNAVAILABLE/501, not a served empty state — see above); on the IBKR node it is a genuine, READY read surface that is nonetheless not evidence of live trading by itself, since it is disabled in the deployment activation roster and absent from the tracked production record (see execution-runtime.md). A backtest run's account/position/order state is Nautilus's own simulated-execution accounting, never live capital. See execution-runtime.md for the full account of what execution capability does and does not exist at this pin.

Evidence and source pins for this page

Verified. Current behaviour, confirmed in source at the pinned commit.

Verified on against the following immutable sources:

  • backend@efa38e04:docs/contracts/market-spine-v2.md
  • backend@efa38e04:docs/contracts/capability-registry-v1.md
  • backend@efa38e04:python/src/onix_engine/spine/deferred.py
  • backend@efa38e04:python/src/onix_engine/ibkr/execution.py

Status tokens are defined on the documentation and status model page. Every pin on this site is listed under versions and source pins.