docs: spec — expand IBKR paper parity to full UX (overview + account view + multi-strat)

Add D3 (overview EXEC badge + real-vs-sim divergence chip + account rollup),
D4 (/paper/ibkr account-level view: total NAV + all positions/fills +
per-strategy breakdown), a UX & information-architecture section (3-level
progressive disclosure, one divergence signal rendered consistently), and a
strategy-tagged data model so a second executor is a new ROW, not a redesign.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-07-14 22:27:15 +02:00
parent 5c0b9ddb27
commit 8a99aff76a

View File

@@ -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.