Skip to main content

Backtest Fixes: Performance, Real-time, Equity Curve, Timeline & Replay

Date: 2026-05-06
Commit: 2099632


Summary​

This update resolves four major backtest page issues:

  1. Backtest too slow — QuickJS strategy runtime rebuilt INTL shim + user code on every tick
  2. Running backtest showed static spinner — no real-time updates while running
  3. Asset Value Over Time chart failed — depended on ClickHouse candle data (often unavailable)
  4. Timeline crappy / Replay broken — Dukascopy unreliable; scichart TimelineChart had 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 as globalThis.__qStrategy
  • buildTickScript(inputLiteral) — run PER TICK: ~4 lines calling globalThis.__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 (not setInterruptHandler(null))
  • Interrupt handler returns boolean (not 1 | 0)
  • ctx.unwrapResult(result) throws on failure, returns QuickJSHandle on 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:

  • DukascopyClient fetched candle data from unreliable external proxy → slow/failing
  • TimelineChart (scichart) had no replay support — currentTime prop 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 data
  • LiveCandleChart (lightweight-charts) via ref={chartRef}
  • Proper replay via replayIndex state + chartRef.current.setMarkers() filtering trades slice(0, replayIndex)

Replay flow:

  1. User clicks "Replay" → replayIndex = 0
  2. useEffect on isPlaying ticks replayIndex every 600ms via setTimeout
  3. Each tick update triggers marker update via chartRef.current.setMarkers(buildMarkers(replayIndex))
  4. "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-app via scripts/deploy-frontend.sh
  • Git: pushed to main + develop

Files Changed​

FileChange
services/backend/src/lib/custom-strategy-runtime.tsQuickJS persistent context optimization
services/frontend/src/routes/backtests/BacktestDetailPage.tsxReal-time polling, equity curve chart, removed duplicate component
services/frontend/src/routes/backtests/BacktestTimelinePage.tsxFull rewrite: ClickHouse candles, LiveCandleChart, working replay