Dashboard walkthrough — from cold start to first paper fill¶
End-to-end tour of QE Dashboard.app: setup → research →
backtest → live paper trading. Read this once; come back to
the per-feature docs for depth.
Cross-references:
- Connectivity:
docs/ibkr-connectivity.md - Safety model:
docs/live-trading-safety.md - Strategy DSL:
docs/qe-language.md - First-IBKR-paper checklist:
docs/ibkr-paper-verification.md
0. One-time setup¶
Build¶
cmake --preset=release
cmake --build --preset=release -j
open "build/release/bin/QE Dashboard.app" # macOS
./build/release/bin/qe_dashboard # Linux
First launch writes a workspace at
~/Library/Application Support/qe_dashboard/workspace/ (macOS)
or the XDG equivalent on Linux: six starter example_*.qe
files plus a positions.json. F3 WKSP opens to that folder so
you're never staring at an empty pane.
Pick a broker¶
Two paths, both safe to learn on. Configure once, change later
via Cmd+,.
| Broker | Cred source | Notes |
|---|---|---|
alpaca-paper |
ALPACA_KEY_ID / ALPACA_SECRET_KEY env vars |
US equities only. No setup beyond the env vars. |
ibkr-paper |
TWS or IB Gateway logged into a paper account | Multi-asset, multi-currency. Need Gateway running first — see ibkr-connectivity.md. |
ibkr-live |
TWS/Gateway logged into a live account | Requires IBKR_LIVE_TRADING=I_KNOW_WHAT_I_AM_DOING env var and a typed-confirmation modal. Don't pick this until you've done the paper checklist end-to-end. |
1. Cmd+, — Settings pass¶
Open the Settings modal, walk top-to-bottom:
| Section | Field | What to set |
|---|---|---|
| General | Refresh interval | 5s default is fine |
| General | Vim mode | On if you want vim motions in F3 |
| Data feeds | Yahoo news | On — drives F1 NEWS |
| Data feeds | L2 streaming | On — drives F2 DEPTH (needs Alpaca creds) |
| Trading | Broker | alpaca-paper or ibkr-paper |
| Trading | IBKR host / port | 127.0.0.1 / 4002 for Gateway paper |
| Trading | IBKR client id | 1 (any unused int) |
| Research | Factor window / K | Defaults are fine |
Close Settings → top bar shows the broker badge (PAPER ·
alpaca-paper, PAPER · ibkr-paper, or LIVE in red). Edits
auto-save on close and on app exit.
The watchlist is not edited from Settings — edit it in-place on F1 MAIN (Cmd+P style fuzzy add, ↑/↓ to focus).
2. The screens, in workflow order¶
Tab / Shift+Tab cycles through F1–F6 in display order. F-keys
jump directly. F12 quits, Cmd+Q also quits.
F1 MAIN — see the market¶
Watchlist + ImPlot candles + positions/tape + Yahoo news.
↑/↓(orj/kin vim mode) — focus prev/next symbol- Click a watchlist row — focus that symbol (chart panel follows)
+ ADD— async fuzzy search by ticker or company name- POSITIONS rows are LIVE from
positions_json; TAPE shows the most recent fills your broker reported - NEWS panel — Yahoo headlines for every watched symbol on
a 60 s refresh. The
[all]/[focus]chip under the panel header (EPIC-71) toggles between every cached headline and only headlines whoserelated_tickersincludes the focused symbol (the one highlighted in the watchlist). The cache keeps sweeping the full watchlist regardless — swapping focus refreshes the filtered view instantly and the focused symbol's coverage stays as fresh as everything else. State persists in the dashboard config (news_filter_focused).
F2 DEPTH — drill into microstructure¶
L2 book + Time & Sales + spread/imbalance/vol-burst widgets + ORDER TICKET.
- Use this to time an entry — look at where the liquidity is, what's clearing, what the imbalance is doing.
- ORDER TICKET is where new orders get submitted (qty, side,
type, limit price →
Submit). Read-only whenbroker = Disabled. - WORKING ORDERS at the bottom of this screen mirrors F6's blotter — convenient while you're staring at the book.
F3 WKSP — write / edit strategies¶
File tree (left) + multi-tab editor (right) + run log (bottom).
- Click any
.qefile in the tree to open as a tab. Shift+H/Shift+Lcycle tabs;Cmd+Wcloses the active tab.Cmd+Sis the bus stop:.qebacktest → forksqe_run, stdout/stderr into the log,results.jsonlands → F4 BCKT auto-refreshes..qesweep → loads spec into F4 BCKT's sweep panel, runs it.positions.json→ re-mounts as live positions, F1 / F5 auto-refresh.- Vim mode (toggle
Cmd+Shift+V):i a I A o Oinsert,h j k l w b e 0 ^ $ gg Gmotions with counts,x X dd yy p P u Ctrl-Redits,D C v Vline/visual operations,:w :bd :qex.
Minimal .qe example (in backtests/example_ma_spy.qe):
let fast = 10
let slow = 50
backtest(
data = yahoo("SPY", "1d", "2024-01-01", "2024-12-31"),
strategy = signal(
entry = cross_above(sma(close, fast), sma(close, slow)),
exit = cross_below(sma(close, fast), sma(close, slow)),
symbol = "SPY",
size = 1.0,
),
execution = execution(capital = 100_000, commission_bps = 1.0),
output = output(results = "out/ma_spy.json"),
)
Full grammar: docs/qe-language.md.
F4 BCKT — review results¶
Sweep matrix (top) + equity curve + KPIs + recent fills + monthly returns heatmap.
- If F3 saved a single-config backtest: equity / KPIs / monthly
matrix render off the produced
results.json. - If F3 saved a sweep: sweep matrix on top (2D bucketed, ◆ on
the best cell). Click any cell → re-runs that config with
let-overrides, re-points
results_jsonat the new file, rest of the screen swaps to that cell's results. - Monthly matrix uses a 3-tier diverging palette (red / neutral / green); NaN cells are dim gray.
F5 RISK — portfolio risk¶
Always-on meta strip (NAV / Gross / Net / Leverage / Long-Short / VaR95 / ES99) over four sub-tabs:
| Tab | What's live |
|---|---|
| Overview | Concentration glance (per-symbol or per-sector if mapped) |
| Greeks | Black-Scholes Δ/Γ/ν/Θ/ρ per leg + Σ; needs positions_json.options[]. Equity-only Δ$ if no options. |
| VaR & Stress | VaR/ES via historical / parametric + 8 spot+vol scenarios |
| Factor | PCA correlation matrix + portfolio exposures + per-PC stats + strategy IC |
Each tab degrades to a "no data — set positions_json" hint
when its inputs aren't available. Active tab is persisted
across launches.
F6 TRADE — manage live orders + deploy daemons¶
F6 has two layouts depending on whether qe_daemon is attached:
No daemon attached (Deploy panel takes the top half):
DEPLOY · start qe_daemon on a live(...) .qe (F3 → Cmd+S → switch here)
─────────────────────────────────────────────────────────────────────
File: ~/Documents/quant-strategy/deploy_it_long_only_1000_paper.qe
Broker: ibkr-paper [PAPER]
IBKR: 127.0.0.1:4002 client_id=1
Symbols: 30 (AAPL, ...)
Capital: $1000
[Arm deploy] (two-click guard; live brokers also gate
on typing the broker name into a confirm box)
ACCOUNT · USD ORDER LOG
... empty until a daemon attaches ... ... empty ...
SAFETY · kill: armed · reconcile: — · broker_session: OFFLINE
· daemon: — · broker: —
[Trip kill-switch] [Reconnect broker] [Re-reconcile now]
[Stop daemon]
Daemon attached (normal 4-panel layout, reading from the daemon's control socket):
WORKING ORDERS EXECUTIONS (last 50)
ID SYM SIDE QTY PX STATE TIME SYM SIDE QTY PX
o-17 AAPL BUY 0/10 150.50 WORK 14:32:05 SPY BUY 1 495.10
o-18 SPY SELL 3/5 495.50 PART 14:31:48 AAPL SELL 5 149.80
[Cancel] disabled in daemon mode …
STAGED ORDERS (only for staged-entry strategies)
AAPL BUY 1/2 5 PENDING Tue 09:30:30
ACCOUNT · USD ORDER LOG
cash 100,000.00 14:30:01 submit_attempt AAPL BUY 10 @ 150.50
buying power 400,000.00 14:30:02 submit_accepted AAPL
equity 100,231.12 14:31:48 fill SPY 1 @ 495.10
day P&L +231.12 (+0.40%) 14:32:05 fill AAPL 5 @ 150.42
account: DUQ526944 … auto-stick to bottom
SAFETY · kill: armed · reconcile: clean · broker_session: PAPER ibkr-paper
· daemon: attached · broker: connected
EVAL · last 2026-06-04 20:00Z ok 3 orders · 2 alerts
[Trip kill-switch] [Reconnect broker] [Re-reconcile now]
[Stop daemon]
- Deploy panel (EPIC-66) — visible only when no daemon is
attached. Reads
cfg.active_live_path(set by F3 Cmd+S on alive(...)file), previews the file, two-click guard for paper brokers + typed broker-name confirm for live. Spawnsqe_daemonvia double-fork + setsid so the daemon survives the dashboard exiting; redirects stdio to~/Library/Application Support/qe_daemon/logs/<stem>-<pid>.log. Pollskill(pid, 0)each frame after spawn and tails the log's[error]lines if the daemon dies pre-attach so you see the failure inline instead of digging through logs. - WORKING ORDERS / EXECUTIONS / ACCOUNT / ORDER LOG (EPIC-67) —
when the daemon is attached, all four read from
DaemonOrderCachewhich polls the daemon'sorders/positions/equity/log_tailcontrol verbs every 3 s. SameOrderSnapshotshape as the local-broker path, so the render code is uniform. - Cancel button is disabled in daemon mode — the daemon
doesn't yet expose a
cancel_ordercontrol verb. The tooltip points at the broker's own UI (TWS / Client Portal), which is the only place a working order can be pulled. Trip kill-switch is not an alternative: no kill-switch in this codebase cancels a working order — each one only blocks new submissions — and the F6 button trips the dashboard's switch, not the daemon's. See Emergency stop. - STAGED ORDERS sub-panel (EPIC-74) — appears under WORKING
ORDERS only when the deployed strategy uses
staged_entry/staged_exitand at least one slice is pending or recently fired. One row per slice: symbol, side,n/Nfired, qty, state (pending / fired / cancelled), next fire time. See Staged entry / exit. - EVAL line (EPIC-75) — one inline row in the SAFETY footer:
EVAL · last <close> ok 3 orders · N alerts. Green ok / yellow empty / red error; the alerts chip turns red on anyeval_missed/eval_replay_mismatch/eval_idempotency_violation. Hidden until the daemon has eval activity. See Eval self-healing — including same-evening recovery viaqe_daemon backfill. - Resizable panels (EPIC-82) — the 2×2 grid has a draggable
vertical splitter between the columns (shared across both rows)
and a horizontal one between the rows. Positions persist in
config.json(layout.trade.left_col_w/top_row_h). The SAFETY footer keeps a fixed height. - SAFETY footer badges:
kill:— KillSwitch state.armed(green) /TRIPPED (reason)(red).reconcile:— reconcile-worker state if the dashboard owns the broker;—otherwise.broker_session:— dashboard's own broker session (legacy / pre-daemon mode).daemon:— control-socket attach state.attached(green) means the dashboard can read state + send commands.broker:— daemon's view of its broker link (EPIC-70).connected(green) /paused · Ns(amber, with miss count — soft-pause, reversible on reconnect) /TRIPPED (reason)(red, hard trip).
- SAFETY footer buttons: Trip kill-switch · Reconnect broker ·
Re-reconcile now · Stop daemon (EPIC-66 — sends
stopcontrol verb for a graceful shutdown).- "Trip kill-switch" trips the dashboard's switch, in the dashboard's process. It does not reach an attached daemon, and it cancels nothing at the venue.
- DAEMON KILL row (EPIC-88 T88.10, decision D2) — the control that reaches the daemon's switch, which is the one that governs the broker session. Two deliberate clicks inside a 5 s window; the arming dies if the state changes under it. Disabled outright when the control socket is down, rather than firing into a dead fd and reporting success. The row states, permanently and not only after you press it, that a kill blocks new submissions and does not cancel working orders.
- CANCEL ALL row (EPIC-88 T88.1) — pulls every working order at
the venue via the daemon's
cancel_allverb. Also two clicks. It runs off the UI thread, so a slow venue does not freeze the window. Three things it will not do: report a count it did not measure (a book the daemon could not read renders as UNKNOWN, never as "0 working orders"), fold per-order refusals into a total (each one is printed with its own reason), or let you believe the book will stay flat — if the kill switch is not tripped it says so, because the strategy will submit again on its next cycle. Kill first, then cancel.
By design F6 has no order ticket — new orders are submitted from F2 DEPTH so you're looking at the book while you size. F6 is for managing what's already live + lifecycle (deploy / stop).
F8 MAP — sector-grouped market mood¶
WINDOW [1D] [5D] [1M] [3M] [YTD] REFRESH LAST 14:31:48 · 491/503 OK
┌──────────────────────────────┬──────────────────────────┬────────┐
│ Information Technology │ Financials │ Energy │
│ ┌─────┬─────┬───┬───┬───┐ │ ┌────┬───┬──┬──┬──┐ │ ┌──┐ │
│ │AAPL │MSFT │NVDA│GOO│META│ │ │JPM │BAC│..│..│..│ │ │XOM│ │
│ ├─────┼─────┼───┴───┴───┤ │ ├────┴───┴──┴──┴──┘ │ ├──┤ │
│ │AMZN │CRM │ ... 50+ │ │ │ ... 70+ tiles ... │ │CVX│ │
│ └─────┴─────┴───────────┘ │ └────────────────────────┘ └──┘ │
├──────────────────────────────┴──────────────────────────┴────────┤
│ ... 8 more sectors ... │
└──────────────────────────────────────────────────────────────────┘
- One tile per S&P 500 constituent. Area is market cap where
the universe file has one, and equal-weight for any constituent
it does not — so a treemap can be a mix of the two. The controls
row states which rule produced the picture you are looking at
(
SIZE MKT CAP,SIZE EQUAL, orSIZE MKT CAP · N of M have none), because a $3 T tile sitting next to a fallback tile is otherwise a picture of nothing. Color is the % change over the selected window, clamped at ±3 %, finviz-style red ↔ grey ↔ green. - Hover a tile for ticker / name / sector / industry / window % / last + prev close.
- Right-click copies the ticker to the clipboard — paste
straight into F1 watchlist or an F3 WKSP
.qeconfig. - REFRESH forces an immediate sweep; the cache otherwise refreshes every 30 s.
- Universe lives at
data/universe/sp500_gics.csv(regenerated bytools/gen_sp500_universe.pyfrom Wikipedia). If the file is missing, the panel renders an empty-state hint and the rest of the dashboard is unaffected.
3. End-to-end paper-trade flow¶
0. Gateway / Alpaca creds OK · top-bar badge shows PAPER
1. F1 MAIN : add symbol · ↑/↓ to focus the one you want
2. F2 DEPTH : eyeball the book · pick a limit price
3. F2 ticket: Submit · WORKING ORDERS row appears
4. F6 TRADE : watch the fill · ORDER LOG / EXECUTIONS update
5. (repeat 2–4 as needed)
6. Cmd+Shift+X if anything looks wrong → KILL · no new orders
7. Cmd+Q
Research-only session (no live broker):
1. F3 WKSP : edit a .qe · vim or plain text
2. Cmd+S : qe_run forks · log streams into F3
3. F4 BCKT : equity / KPIs · auto-refreshes on save
4. (iterate)
5. Cmd+Q
4. Safety net — what each guard does¶
| Trigger | Effect |
|---|---|
Cmd+Shift+X |
Trips the dashboard's own kill-switch (not the daemon's — they are separate objects in separate processes). To reach an attached daemon's switch use the F6 DAEMON KILL row (EPIC-88 T88.10); this chord deliberately has no daemon equivalent, because a one-way remote kill should not be a keystroke. New submit_order is rejected. Orders already working at the venue are untouched — nothing here cancels them; cancel + read-only calls stay open precisely so you can go drain them yourself. First-trip-wins: only the first chord captures a reason; later presses are no-ops. Persists until restart. See Emergency stop. |
| Actual positions ≠ broker positions | Reconcile worker pops a read-only drift modal. No auto-fix. You decide whether to adjust manually in the broker UI. See live-trading-safety.md. |
| Broker socket drops | Top-bar badge → BROKER OFFLINE. Reconnect via Broker menu or F6 SAFETY [Reconnect broker]. |
| Day-loss limit exceeded | In the daemon (EPIC-88 T88.4): evaluated continuously, on every mark update, against the strategy's marked-to-market P&L anchored on execution(capital = ...). Two consecutive breaching marks trip the kill-switch — so a position can no longer bleed through the limit all session just because nothing was being submitted. New submissions then stop; working orders are not cancelled. In the standalone dashboard, SafeBroker's per-submit check is still the only gate: it rejects that one order and trips nothing. IBKR day_pl arrives via the account stream. |
ibkr-live selection |
Requires IBKR_LIVE_TRADING=I_KNOW_WHAT_I_AM_DOING env var and typing the broker name into the confirmation modal. Triple gate; deliberately annoying. |
5. Persistence — what survives a restart¶
| State | Persisted? | Where |
|---|---|---|
| Watchlist / focused symbol | ✓ | config.json (auto-save on exit) |
| Broker selection + IBKR knobs | ✓ | config.json |
| Workspace path + active backtest/sweep file | ✓ | config.json |
| Active F5 RISK tab | ✓ | config.json |
| Layout splitter positions | ✓ | config.json (per-screen) |
| F3 editor open tabs | ✗ | Re-open from the tree next session |
| Working orders at the broker | ✓ (broker side) | TWS / Gateway / Alpaca keeps them; dashboard re-syncs on reconnect |
| Kill-switch state | ✗ | Always armed on launch — by design |
6. Common surprises¶
- F6 ACCOUNT shows zeros under IBKR. Older builds returned
an empty
AccountInfostub. Update to a recent build with the account-stream wiring; cash / buying_power / equity / day_pl populate fromREQ_ACCOUNT_DATA. - Dashboard launches with
BROKER OFFLINEeven though Gateway is up. Expected for a second or two: the IBKR broker now constructs on a background thread (EPIC-81) so the window appears immediately instead of blocking on::connect()— which used to look like a ~75 s freeze when the Gateway was down. The badge flips toREADYwhen the connect resolves; if it staysOFFLINE, check the Gateway and the log. Historical Data Farm is Inactive: ushmdsin Gateway. Informational, not an error. Means "no one's requesting historical bars; the connection is dormant". The dashboard itself doesn't use IBKR historical data (Yahoo handles its charts);qe_daemononly requests it once per deploy for warmup. Ignore.- F4 BCKT empty after
Cmd+S. The.qe'soutput(results = …)path is relative to the workspace root; check the path printed in F3's log pane resolves to a writable directory. - Vim cursor not highlighted on F3 tree. Already fixed in a recent build; if you see this, rebuild.
- F6 TRADE blotter row says
WORKbut never moves. Check the order is within trading hours for the symbol's exchange. Outside hours, IBKR holds the order; Alpaca paper accepts but won't simulate fills.
7. Where to look next¶
- Sweep workflow & cell-click reruns:
docs/qe-language.md§sweep - Multi-strategy portfolios:
docs/multi-strategy.md - Walk-forward harness:
docs/walk-forward.md - Options pricing model:
docs/options-model.md - TWS protocol details (for hacking on
ibkr_connection.cpp):docs/ibkr-tws-protocol.md