diff --git a/docs/superpowers/specs/2026-07-14-ibkr-paper-cockpit-parity-design.md b/docs/superpowers/specs/2026-07-14-ibkr-paper-cockpit-parity-design.md index dd1890b..a7a722f 100644 --- a/docs/superpowers/specs/2026-07-14-ibkr-paper-cockpit-parity-design.md +++ b/docs/superpowers/specs/2026-07-14-ibkr-paper-cockpit-parity-design.md @@ -7,24 +7,33 @@ ## Goal Bring the IBKR paper strategies to cockpit parity with the Bybit book: show their **backtest** in the sim -and their **real paper-account trading** (actual DU-account fills / positions / NAV) in the cockpit — so the -paper trading that is already working is *visible and verifiable*, as the honest pre-live confidence path. +and their **real paper-account trading** (actual DU-account fills / positions / NAV) across the cockpit — +overview (at-a-glance), strategy detail (the slice), and an account-level view (the whole DU pot) — so the +paper trading that is already working is *visible and verifiable* at every level, as the honest pre-live +confidence path. UX is a first-class requirement, and the design scales to multiple executing strategies by +adding a row, not a redesign. ## Scope **In:** - D1 — the IBKR strategies' honest-cost backtest curve in `/paper/sim`. -- D2 — capture the **real** IBKR DU paper-account state (fills, positions, NAV), render it as a cockpit book, - and reconcile it against the recomputed (idealized) `multistrat` sim forward. +- D2 — capture the **real** IBKR DU paper-account state (fills, positions, NAV), render it on the strategy + **detail** page, and reconcile it against the recomputed (idealized) `multistrat` sim forward. +- D3 — surface the executed reality on the **overview** page (a per-strategy EXEC + real-vs-sim divergence + signal, and an account-rollup summary), so the fleet view shows what is actually paper-trading at a glance. +- D4 — an account-level **IBKR paper account view** (the whole DU account: total NAV, all positions/fills, and + a per-strategy breakdown) — the design that answers "how do I see it when more strats execute." + +Designed for scale (UX is a first-class requirement): today only `multistrat` executes on DU (vrp exec is +suspended on IBKR options permission; sixtyforty is a benchmark), so the account currently *is* the multistrat +book. But ALL captured data is tagged by strategy and the views are built at both the per-strategy AND the +account level, so a second executor arming is a new ROW, never a redesign. **Out (explicit):** - No live / real-capital execution, no arm step, no promotion→allocation lifecycle changes ("still on paper, no live yet"). Gate/promotion/allocation semantics are UNCHANGED. - Bybit stays exactly as-is (a pure simulated book — no EU Bybit-testnet API keys are available, so there is no real Bybit account to ingest). This design does not touch the Bybit `/paper` book or its sim. -- Per-strategy attribution of the DU account. Today only `multistrat` executes on DU (vrp exec is suspended on - IBKR options permission; sixtyforty is a benchmark), so the DU account *is* the multistrat book. Attribution - across multiple executors is deferred until a second executor arms (YAGNI). ## Current state (grounded) @@ -98,12 +107,73 @@ verification: it answers "does the executed paper book track the simulation, and cost?" Surface a compact headline (e.g. cumulative sim-vs-executed divergence + median fill slippage) on the detail page. `multistrat_exec` (the observed envelope-return track) remains the gated exec twin, unchanged. +## D3 — Overview page: the executed reality at a glance + +The overview (`/`, `dashboard_service.fleet()`/`overview()`) already renders each strategy with gate/health/ +backtest/venue badges. Add, for strategies that execute on a real paper account: +- an **EXEC** badge (distinct from the existing venue "ref" badge) — "this strategy is placing real orders on + the IBKR paper account," so a viewer instantly separates *executing* strats from pure-sim ones; +- a **real-vs-sim divergence chip** — the executed track's return vs the idealized sim, rendered with the same + semantic-color language as the health/gate badges (near-zero = neutral, widening = amber). This is the + headline "is execution tracking the model" signal, visible without drilling in. + +Add a compact **IBKR paper account rollup** to the overview (a single card/line): total DU NAV, number of +executing strategies, and aggregate real-vs-sim. Today it reflects `multistrat`; it is account-level so it +simply grows as strats arm. It links to the account view (D4). + +## D4 — IBKR paper account view (account-level; the multi-strat home) + +A dedicated view of the DU account **as an account** (route e.g. `/paper/ibkr`), reachable from the overview +rollup and the exec-venue badge: +- the **total** real account NAV curve (from `ibkr_account_nav`), +- **all** current positions and recent fills across every executing strategy, +- a **per-strategy breakdown table** — one row per executing strat: its attributed executed return, its + attributed positions/fills, and its real-vs-sim divergence. One row today (`multistrat`); N rows as strats + arm — the answer to "how do I see it with more strats." + +This is the account-level truth (the DU account is one pot of capital); the per-strategy detail pages (D2.2) +are the slices. Both are driven by the same strategy-tagged capture. + +## UX & information architecture + +Three levels, progressive disclosure, one consistent divergence signal at every level: + +``` +Overview / -> fleet scan: per-strat [gate][health][EXEC][sim-vs-exec chip] + | + IBKR paper account rollup card (total NAV, N strats, agg divergence) + |-- Strategy detail /strategy/multistrat -> this strat's slice: real NAV curve, its positions + fills + | (per-fill slippage), reconciliation headline vs its sim + '-- Account view /paper/ibkr -> the whole DU account: total NAV, all positions/fills, + per-strategy breakdown table (1 row now, N later) +``` + +UX principles (professional ops UI): +- **At-a-glance first:** the overview must answer "which strats are actually paper-trading, and is execution + tracking the model?" from badges/chips alone — no drill-in required. +- **Progressive disclosure:** overview (fleet) → strategy detail (one slice) → account view (the pot). Each + level adds resolution, none duplicates another's job. +- **One divergence signal, rendered consistently** at all three levels (chip on overview, headline on detail, + breakdown on account) — the same computation, the same color language. +- **Reuse the existing visual system:** the gate/health badge palette + `exec_badge` macro; do NOT invent a new + palette. The EXEC badge and divergence chip are new *states* in the existing badge vocabulary. +- **Scale by row, not redesign:** every view renders a *set* of executing strats (size 1 today); adding a strat + never changes the layout, only adds a row/chip. +- **Honest empty/degraded states:** no captured account yet, or ib-gateway unreachable, renders a clear "no + executed data yet" state — never a 500, never a fabricated number. Consistent with the health axis (a stalled + exec track already surfaces as STALE). + ## Data model -- **Reuse** `exec_fills` (`ExecFillRow`) — populate it (currently empty). No schema change. +- **Reuse** `exec_fills` (`ExecFillRow`) — populate it (currently empty). It already carries `rebalance_id`; + ensure each rebalance's `rebalance_id` maps to its `strategy_id` (a `strategy_id` column or a rebalance→strat + mapping) so fills are attributable per strategy for D4's breakdown. No breaking schema change (add the tag). - **New** `ibkr_account_nav` (per-run real account snapshot): `run_date` PK, `nlv`, `cash`, `gross`, - `positions_json`, `at`. Idempotent create + replace-per-run; migrate follows the existing - `forward_nav`/`paper_repo`/`strategy_health` pattern (no heavy startup DDL). + `positions_json`, `at`. Account-level (the whole DU pot). Idempotent create + replace-per-run; migrate follows + the existing `forward_nav`/`paper_repo`/`strategy_health` pattern (no heavy startup DDL). +- **Per-strategy executed return** already exists as the `*_exec` observed forward tracks (`multistrat_exec` + today; each future executor gets its own, same pattern) — the account view's per-strategy breakdown reads + these + the strategy-tagged `exec_fills`. The account NAV rolls up the account; per-strat return stays on the + `*_exec` tracks. No new per-strat NAV store. - **No change** to `forward_summary`, `backtest_summary`, `strategy_health`, gate/promotion/allocation. ## Architecture / components @@ -116,7 +186,12 @@ detail page. `multistrat_exec` (the observed envelope-return track) remains the - `adapters/persistence/` — the `ibkr_account_nav` repo (create/replace/read a NAV series + latest positions), following `forward_nav`/`paper_repo` conventions. - `adapters/web/app.py` + templates — D1 sim book-list/curve-source dispatch; D2 the multistrat-detail "IBKR - paper account" section (real NAV curve + positions + fills + reconciliation headline). + paper account" section (real NAV curve + positions + fills + reconciliation headline); D3 the overview EXEC + badge + divergence chip + account-rollup card (via `dashboard_service.fleet()`/`overview()` + the badge + macros); D4 the `/paper/ibkr` account view (total NAV + all positions/fills + per-strategy breakdown table). +- `application/dashboard_service.py` — compute the per-strategy exec status + real-vs-sim divergence (one shared + function feeding overview chip, detail headline, account breakdown — the "one signal, three places" rule) and + the account rollup. - `application/backtest_refs*` / the nightly asset — persist the IBKR recompute-replay curve where the sim can read it cheaply (mirrors `bybit_sleeve_ret`). @@ -140,6 +215,14 @@ detail page. `multistrat_exec` (the observed envelope-return track) remains the - **D2.2/2.3:** the multistrat detail page renders the real NAV curve, positions, fills; the sim-vs-executed reconciliation computes the divergence on a fixture (known sim series vs known executed fills → expected divergence + median slippage); empty data → empty section, no 500. +- **D3:** the shared divergence function returns the expected value on a fixture; the overview renders the EXEC + badge + divergence chip for an executing strat and NOT for a pure-sim strat; the account-rollup card shows + total NAV + strat count; no-executed-data → no badge/empty card, no 500. +- **D4:** `/paper/ibkr` renders total NAV + all positions/fills + a per-strategy breakdown row for `multistrat`; + the breakdown is a SET (assert it renders correctly for one strat and would render N — e.g. a two-strat + fixture yields two rows); empty account → clean empty state, no 500. +- **Scale test:** a fixture with a second (synthetic) executing strat proves overview/account views render both + without layout change — the "new row not redesign" invariant. ## Constraints @@ -152,5 +235,14 @@ detail page. `multistrat_exec` (the observed envelope-return track) remains the ## Sequencing -D1 first (low-risk, backtests exist, immediate parity), then D2 (D2.1 capture → D2.2 book view → D2.3 -reconciliation). Each is independently testable and shippable. +1. **D1** — sim curve for IBKR (low-risk, backtests exist, immediate parity). +2. **D2.1** — capture the real account (fills → `exec_fills` tagged by strategy; snapshot → `ibkr_account_nav`). + The data foundation everything else reads. +3. **D2.2 + D2.3** — the multistrat detail "IBKR paper account" section + the sim-vs-executed reconciliation + (build the ONE shared divergence function here). +4. **D3** — overview EXEC badge + divergence chip (reusing the shared function) + account-rollup card. +5. **D4** — the `/paper/ibkr` account view (total NAV + all positions/fills + per-strategy breakdown). + +Each step is independently testable and shippable; D2.1 is the pivot (it fills `exec_fills` + the account +snapshot that D2.2/D3/D4 all render). The shared divergence function (built in step 3) is reused by D3 and D4 +so the signal is computed once and rendered three places.