1. Live dashboard now shows real open orders (87) and positions (2)
from Hyperliquid API, cached every 5s to avoid 429 rate limit.
2. A-S gamma scaling: gamma*500K gives ~0 skew at max inventory
(was bash.003, functionally identical to naive dual-quote).
3. Mean Reversion: 60-tick window with 0.5σ threshold
(20s of 1s ticks was noise, not mean-reverting).
4. Sizes reduced for margin safety (wallet 86, 9 concurrent orders).
5. Kalman win_rate bug fixed: added net_pnl/gross_pnl field support.
1. A-S reservation price now uses gamma*500000 scaling.
Before: bash.003 skew on 4K BTC (invisible, same as naive dual-quote)
After: ~0 skew at max inventory (0.05% of mid — enough to suppress one side)
2. Mean Reversion: 20-tick → 60-tick window, threshold 1.0σ → 0.5σ.
20 seconds of 1s ticks is noise, not mean-reverting.
60 seconds captures real short-term reversion dynamics.
Fill attribution verified: BTC sizes differ by 50 μBTC, ETH by 0.0025 — all above matching tolerance.
Orderbook null guards present — no crash on failed fetch.
Bug: win_rate() only checked pnl_net/pnl_gross/pnl fields,
but Kalman backtests save trades with net_pnl/gross_pnl (underscore-first).
Result: all 4 Kalman assets showed 0% win on 27-35 trades.
After fix:
BTC: 0% → 45% (16/35)
ETH: 0% → 47% (16/34)
HYPE: 0% → 51% (14/27)
VVV: 0% → 57% (19/33)
Also corrected paper trader coin assignments for Mean Reversion
and Momentum Breakout (was BTC, should be ETH).
The AS optimal spread formula gives absurd spreads at crypto scale.
Real market makers quote at the MARKET spread (best bid/ask) and use
AS to decide WHEN to quote based on inventory-adjusted fair value:
r = s - q * gamma * sigma^2 * tau
If r < best_bid (long-biased) → stop quoting bid
If r > best_ask (short-biased) → stop quoting ask
If circuit breaker active → pause both sides
Decoupled: spread is market-driven, inventory skew is AS-driven.
Bug: /api/backtest/{name} only looked in backtests/results/,
but all historical backtests are saved in backtests/results/historical/.
Fix: check HISTORICAL_DIR first, then fall back to BACKTEST_DIR.
This fixes SPX backtest detail showing zero prices/fees.
Root cause: VWAP weighted the current price highest so dev≈0 always.
- Use prior 19 prices (exclude current) for mean/std calculation
- Compare current price vs prior mean, normalized by prior std
- Paper trader: was using BTC prices instead of ETH (wrong coin)
- Threshold unified: 1.0σ (was 1.5σ in paper, 1.0σ in live)
Backtests show BTC Mean Reversion: +76.42% PnL, 91% win, 22 trades.
Live node:
- Added Hurst VPIN to STRATEGIES (BTC, 0.00024 size, 00)
- Feed BTC price into dollar-bar Hurst/VPIN every 5 ticks
- Signal: BUY/SELL when H>0.55 + VPIN>0.25 + direction bias
Paper trader:
- Added Kalman Pairs, Avellaneda-Stoikov, Hurst VPIN strategies
- All 00 allocation, matching live node asset distribution
- Hurst/VPIN signal from BTC mid-price dollar bars
Strategy file: hurst_vpin_live.py (lightweight price-tick mode)
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