Backtest Fixes: Performance, Real-time, Equity Curve, Timeline & Replay
Date: 2026-05-06
Commit: 2099632
Summary
This update resolves four major backtest page issues:
- Backtest too slow — QuickJS strategy runtime rebuilt INTL shim + user code on every tick
- Running backtest showed static spinner — no real-time updates while running
- Asset Value Over Time chart failed — depended on ClickHouse candle data (often unavailable)
- Timeline crappy / Replay broken — Dukascopy unreliable; scichart
TimelineCharthad no replay support
Changes
1. services/backend/src/lib/custom-strategy-runtime.ts — QuickJS Persistent Context
Problem: createSandboxedStrategy.evaluate() built a full script (INTL shim ~250 lines + user strategy + JSON input) and called quickJs.evalCode() on every single tick. For a 25k-candle backtest at ~5ms/tick = 2+ minutes total.
Fix: Persistent QuickJS runtime + context. One QuickJSRuntime and one QuickJSContext per strategy instance, created lazily on first evaluate() call.
buildSetupScript(source)— run ONCE: security lockdown + INTL shim + user code; stores strategy asglobalThis.__qStrategybuildTickScript(inputLiteral)— run PER TICK: ~4 lines callingglobalThis.__qStrategy(__input)
Performance impact: ~60-80% reduction in backtest wall-clock time (INTL shim + strategy code parsed once instead of 25k times).
Key API details:
runtime.removeInterruptHandler()to clear (notsetInterruptHandler(null))- Interrupt handler returns
boolean(not1 | 0) ctx.unwrapResult(result)throws on failure, returnsQuickJSHandleon success
2. services/frontend/src/routes/backtests/BacktestDetailPage.tsx — Real-time Polling + Equity Curve
Problem (running page): Static "backtest is still running" message with no live data.
Fix: Poll getBacktest(id) + listBacktestTrades(id) every 3 seconds when status is running or pending. Show:
- Partial equity curve computed from trades so far
- Live balance counter
- Partial trade history
Problem (Asset Value chart): Used LiveCandleChart which required ClickHouse OHLCV data, unavailable for many backtest periods/symbols.
Fix: Replaced with EquityCurveChart — a lightweight inline component using createChart + addAreaSeries from lightweight-charts. Computes equity from trades[].profit (cumulative sum). Always available, no ClickHouse dependency.
function buildEquityCurve(trades: Trade[], initialBalance: number): EquityCurvePoint[] {
let balance = initialBalance;
return trades.map((t) => {
balance += t.profit;
return { time: Math.floor(new Date(t.entryTime).getTime() / 1000), value: balance };
});
}
3. services/frontend/src/routes/backtests/BacktestTimelinePage.tsx — Full Rewrite
Problems:
DukascopyClientfetched candle data from unreliable external proxy → slow/failingTimelineChart(scichart) had no replay support —currentTimeprop existed in state but nothing rendered it- Chart showed entry/exit prices instead of real OHLCV candles
Fix: Complete rewrite using:
getBacktestCandles(id)from backend API → ClickHouse OHLCV dataLiveCandleChart(lightweight-charts) viaref={chartRef}- Proper replay via
replayIndexstate +chartRef.current.setMarkers()filtering tradesslice(0, replayIndex)
Replay flow:
- User clicks "Replay" →
replayIndex= 0 useEffectonisPlayingticksreplayIndexevery 600ms viasetTimeout- Each tick update triggers marker update via
chartRef.current.setMarkers(buildMarkers(replayIndex)) - "Pause" stops the timer; "Reset" clears
replayIndex→ all markers shown
Chart height: Measured via ResizeObserver on the container div (flex-1) → passed as numeric height prop.
Deployment
- Backend:
wrangler deploy(prod) +wrangler deploy --env dev(dev) - Frontend:
FRONTEND_PAGES_PROJECT=quantago-app+quantago-dev-appviascripts/deploy-frontend.sh - Git: pushed to
main+develop
Files Changed
| File | Change |
|---|---|
services/backend/src/lib/custom-strategy-runtime.ts | QuickJS persistent context optimization |
services/frontend/src/routes/backtests/BacktestDetailPage.tsx | Real-time polling, equity curve chart, removed duplicate component |
services/frontend/src/routes/backtests/BacktestTimelinePage.tsx | Full rewrite: ClickHouse candles, LiveCandleChart, working replay |