A real-time trading dashboard built on FastAPI and WebSockets — the kind of interface you move to when a Jupyter/Voila + ipywidgets front-end has gone as far as it can.
Recorded from a running instance — prices ticking, chart appending, values flashing on update.
pip install -r requirements.txt
uvicorn app.main:app --reload
# http://127.0.0.1:8000No API keys, no database, no build step. It runs immediately against a synthetic feed.
- Live streaming over a WebSocket, ~2 updates/sec — equity, P&L, prices, positions and strategy state
- Candlestick chart (Plotly) that appends bars in place rather than redrawing history on every tick
- Instrument switching without reconnecting — the client sends
{"symbol": "ETH-USD"}on the open socket - Dark and light themes, switched by one
data-themeattribute - Positions table with per-position P&L and P&L %
- Control panel — sliders, a toggle, an action button (UI-wired only)
- Auto-reconnect with capped exponential backoff
The strategy layer only produces a Snapshot. Nothing in the transport or
the templates knows where the numbers came from, so replacing app/feed.py
with a live broker connection touches no other file. That boundary is the whole
point of moving off a notebook — in Voila the UI and the strategy are the same
process, and you cannot change one without risking the other.
Updates are surgical, not re-renders. At two pushes a second, re-rendering
the table or redrawing the full chart drops frames on a laptop. New bars are
appended via Plotly.react; KPI values are direct textContent writes.
Numbers use tabular figures. font-variant-numeric: tabular-nums keeps
columns from jittering as digits change — with a price updating twice a second,
proportional digits make the whole layout shiver.
The update flash is deliberately restrained. A brief tint that hugs the digits, not a full-card highlight. Something you can sit in front of for eight hours.
Theming is a token swap. Every colour is a CSS custom property; the light theme redefines the tokens and changes nothing else. Plotly is the exception — its colours aren't CSS-driven, so the chart is redrawn on theme change.
app/
main.py FastAPI app — routes, WebSocket, lifecycle
feed.py synthetic market data (swap this for a real feed)
templates/
base.html shell, top bar, theme toggle
dashboard.html KPIs, chart, rail, positions table
static/
css/style.css design tokens + components
js/dashboard.js WebSocket client, chart, DOM updates
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
dashboard |
GET |
/api/candles/{symbol} |
OHLC history for first paint |
GET |
/api/snapshot/{symbol} |
single snapshot (polling fallback) |
WS |
/ws/{symbol} |
live stream; accepts {"symbol": "..."} to switch |
GET |
/healthz |
liveness |
GET |
/api/docs |
generated OpenAPI docs |
Replace Feed.next_snapshot() in app/feed.py so it returns a Snapshot
built from your own data. The dataclass is the contract:
Snapshot(
ts, symbol, price, change_pct,
equity, day_pnl, win_rate, open_positions, exposure,
positions=[...], # dicts: symbol, side, qty, entry, mark, pnl, pnl_pct
signals=[...], # dicts: name, value, tone ∈ {pos, neg, flat}
)Nothing else needs to change.
Controls are wired to the interface only — no orders are placed and no strategy runs. This is a front-end and transport demonstration, not a trading system.
MIT


