Header/Tabs: Hallmark Cobalt aesthetic
- Hairlines, cool paper bg, JetBrains Mono + Inter
- Electric cobalt accent on active tab
- No branding, no purple badges, no gradients
QuantReport: inline in strategy detail view
- Renders below trade history on every tab
- API maps strategy name -> file prefix
- Proper backtestId from historical data
Server: strategy-name-to-prefix lookup
ofi, avellaneda, iceberg, momentum, mean_rev,
funding_arb, kalman_pairs, pairs
Removed: FTDT Quant Lab branding, purple badges, green pulse dot,
shadcn Tabs dependency, backdrop blur noise
Replaced with: Hallmark Cobalt engineered aesthetic
- Hairline borders (#e0e4ec), cool paper (#f8f9fb)
- JetBrains Mono header labels, Inter tab buttons
- Electric cobalt (#0ea5e9) signal accent on active tab
- Flat text labels: Live · Paper · Historical
- Status dot + CONNECTED/OFFLINE subtle indicator
- No shadows, no gradients, no rounded cards
- API: fuzzy matcher resolves files by strategy name substring
- Frontend: backtestId now uses historical[name].name (the filename)
- Server restarted with quant_report endpoint
Backend: strategies/quant_report.py
- equityCurve: daily PnL from trade history
- monthlyReturns: heatmap matrix (years x months)
- yearlyReturns: bar chart data with mean
- monthlyReturnDistribution: histogram bins
- qqPlot: theoretical vs observed quantiles
- rollingStats: 6-month rolling return + volatility
API: /api/quant-report/{name}
Computes full report from any backtest JSON file
Frontend: QuantReport.tsx
- Strategy Performance chart (equity curve, blue line)
- Monthly Returns heatmap (blue saturation)
- Yearly Returns bar chart with mean line
- Distribution histogram
- Normal QQ plot with diagonal reference
- Rolling Statistics (6-month, dual line)
- QF-Lib header with logo and metadata
- Access via QF-Lib Report button in detail view
New: strategies/persistence.py
Tables: strategies_snap, trade_log, equity_history, fill_tracker
Auto-creates on first use, batches inserts per tick
Fix: seen_fills loads from PG (not 2000 API fills)
Before: every restart loaded all 2000 fills from API into
seen_fills, blocking new fills with matching TIDs for ~20min
After: only loads last 100 from API + full history from PG.
New fills saved to PG immediately - survives restarts.
Live node integration:
- write_metrics() → save_strategies() every tick
- On fill → save_trade() to trade_log
- On fill → TID saved to fill_tracker for cross-restart dedup
- 6 BTC strategies now have unique sizes (0.000200-0.000250)
- Fill attribution uses tighter tolerance (1e-6) for unambiguous matching
- Testnet meta API returns null -> fallback to mainnet for perp loading
- All strategies placing orders with correct isolation
Root cause: OrderBookDepthMap was only in non-OBI detail branch.
When user clicked Order Book Imbalance card, the OBIDetail component
replaced the entire detail view, and the tape was never mounted.
Fix: Added OrderBookDepthMap to OBIDetail component below trade history.
Now visible in ALL strategy detail views (both OBI and non-OBI).
Root cause: Kalman filter needs a cointegrated pair, but the
historical runner was feeding it synthetic noise (close vs SMA).
The Kalman filter found no mean-reverting spread, producing 0 signals.
Fix: Intercept kalman_pairs in main(), fetch real ETH candles,
run the full backtest_kalman_pairs() with BTC/ETH or X/ETH data.
Results (30-day, 720h candles, BTC/ETH pair):
BTC: 35 trades, -0.36% PnL
ETH: 34 trades, -0.01% PnL (ETH/BTC pair)
HYPE: 27 trades, -0.00% PnL (HYPE/BTC pair)
VVV: 33 trades, -0.01% PnL (VVV/BTC pair)
Total: 32 historical backtests (8 strategies x 4 coins)
Live node:
- Registered in STRATEGIES dict (8th strategy)
- Signal: KalmanPairsTrader.step(eth, btc) every compute_signals()
- Adaptive hedge ratio updates with every tick
Paper trader:
- Registered in STRATEGIES dict
- Signal: KalmanPairsTrader integrated into compute_signals()
- Falls back gracefully if kalman_pairs module not importable
Historical backtests:
- Ran for BTC, ETH, HYPE, VVV (4 files)
- kalman_pairs_{TICKER}_*.json in results/historical/
- Visible on dashboard under Historical tab (8 strategies x 4 coins)
Dashboard: now shows Kalman Pairs card on all three tabs.
Root cause analysis:
- Round-robin bottleneck: each strategy got attention every ~28s
- Orders cancelled immediately: POST-ONLY orders lived <=28s, near zero fill prob
- 5 strategies had over-tight thresholds (Iceberg 7/10, Momentum 2σ, etc.)
- No position management: no take-profit, no opposing signal close
Fixes applied:
1. ALL strategies execute every 4s (for name in names: parallel)
2. Orders rest 60s before refresh (was: cancelled every round)
3. Take-profit at 0.1% move + close on opposing signal
4. Aggressive 0.03% offset inside spread for higher fill probability
5. Iceberg: 7/10 -> 5/10 consecutive ticks
6. Momentum: 2σ -> 1.5σ Bollinger breakout
7. Mean Reversion: 1.5σ -> 1.0σ VWAP deviation
8. Funding Arb: uses real Hyperliquid API funding rate
9. OFI threshold kept at 0.04% (was 0.08%)
Verification:
Post-patch log shows all 7 strategies placing orders every 4 seconds.
Order Book Imbalance, Iceberg Detection, Funding Rate Arb, Pairs Trading
all confirmed active in tick 12680 output.
- Replaced single surface with dual synchronized 3D subplots:
Left: BID depth (green colorscale, -50 to 0 bps)
Right: ASK depth (red colorscale, 0 to +50 bps)
- Independent colorbars per side with proper labeling
- Camera sync via scene anchor mirroring
- Contour projection on both surfaces
- Live imbalance overlay centered between subplots
Data pipeline:
- l2SnapshotsToDualSurface() splits bid/ask into separate matrices
- L2RingBuffer unchanged (60 snapshots, O(1) append)
Removed:
- depth-map-three.tsx (Three.js alternative)
- Engine toggle buttons from OBI detail
- Three.js CDN loading
- Historical cards now deduplicated by strategy+ticker (28 entries: 7×4)
- Ticker filter bar: ALL | BTC | ETH | HYPE | VVV
- Coin badge on each card
- BacktestSummary.coin now required string field
- fetchHistorical groups by strategy · coin composite key
- Added HYPE and VVV to --coin choices
- Fixed coin field to store ticker name instead of first candle timestamp
- Added coin_name parameter to simulate_strategy_on_candles
- Next.js 16 + React + TypeScript static export
- shadcn/ui components: Card, Tabs, Badge, Sheet, Collapsible, Table
- Claude Blu 2 dark theme via oklch CSS variables
- lightweight-charts v4 for equity curve rendering
- Framer Motion for layout animations
- 3 tabs: Live Testnet, Paper Mainnet (00K), Historical
- Full-page strategy detail with equity chart + trade history
- Fee tier selector (7 official Hyperliquid tiers + staking)
- API routes prefixed with /api/ for clean Caddy proxying
- _next/ mount for Next.js static assets
- WebSocket data flowing for live metrics and paper trader
- Live node: testnet-only API for prices/orderbook/instruments
(removed mainnet fallback, added resilience wrappers)
- Paper trader: mainnet-only API — simulates with real Hyperliquid data
$120K paper capital, 12 strategies, mainnet mark prices
- Historical backtests: mainnet candle API (unchanged, already correct)
- All three tiers: strategy_equity tracking, dynamic perp lookup,
win_rate fix (pnl_net/pnl_gross), CSS contrast improvement
Renamed all "VIP N" to "Tier N" in config, server, and HTML dropdowns.
Added recalc_equity_curve() that rebuilds the equity curve with new
fee rates. Previously the equity curve was passed through unchanged
when switching tiers, so the chart visually stayed identical even
though PnL numbers changed. Now each tier produces its own curve.
Example OBI historical: Tier 0 equity ends at 141.5, Tier 6 at 149.7 —
the chart visibly shifts up as fees drop from 4.5bp to 2.4bp taker.
Server: recalc endpoint now checks HISTORICAL_DIR as fallback
when file not found in BACKTEST_DIR. Previously historical backtests
returned "not found" on recalc.
Frontend: renderBTDetail now accepts pnl_net/pnl_net_pct from
recalc response (the endpoint returns pnl_net not pnl).
Verified: VIP 0 → VIP 6 on OBI historical backtest changes
net PnL from 54.31% to 66.57% with fees dropping $18.10 → $5.85.
The historical runner stores equity curve times as ISO strings like
"2026-07-05T07:00:00" but LightweightCharts only accepts Unix timestamps.
Chart was loading 721 data points but rendering blank because time values
were silently rejected.
Changes:
- renderBTDetail: convert string times to Unix timestamps before setData()
- openDetail: same conversion for live/paper detail charts
- pushEquity: same conversion for main area equity charts
All chart codepaths now handle both string ISO and numeric timestamps.
Root cause: openDetail() never set equity/trades for live tab because
the live node doesn't send per-strategy equity or per-strategy trades.
The live WS sends overall equity_history[] and trades[] array.
Changes:
- Live tab: uses overall equity_history for chart, filters trades[]
by strategy name
- Paper tab: uses per-strategy strategy_equity[name]
- Chart data: handles both array (live) and dict (paper) equity formats
- Backtest: unchanged, already works (720 pts)
- Historical: unchanged, already works (721 pts)
All four detail charts now render:
Live: 600 equity pts + filtered trade rows
Paper: per-strategy equity + trades
Backtest: 720 equity pts + 100 trades
Historical: 721 equity pts from mainnet candles
Two LightweightCharts area-series charts added below strategy cards
in Live and Paper tabs. Each chart renders equity_history from the
WebSocket data stream, updating on every tick.
Changes:
- chart-live and chart-paper containers with 220px height
- initMainCharts() creates chart instances + area series
- pushEquity() converts equity points to chart data, auto-fits view
- renLive() and renPaper() push equity_history to respective charts
- .main-chart CSS for dark-theme background
- init chain calls initMainCharts() after initDetChart()
Live chart: 600 data points rendering on first load
Paper chart: loads on tab activation, 600 points
Root cause: switchTab() didn't show the historical panel (pnl-historical).
Added panel visibility toggle and tab highlight for 'historical' tab.
Also fixed broken JS quote escaping in loadHistBT function — ''+s+''
was missing backslash-escaped quotes, causing "Unexpected string" syntax
error that prevented the entire script from executing.
Historical tab now shows 7 real-data backtest cards from Hyperliquid
mainnet candles.
Risk panel now shows below strategy grid: VaR 95%, CVaR 95%, Max DD,
Calmar ratio, Sharpe, Sortino. Strategy correlation summary with
color-coded ρ values (red=high >0.7, amber=medium). Auto-refreshes
when paper data updates (throttled 30s). Collapsible with ▶ toggle.