Skip to content

Engine internals

The pure-TypeScript trading engine — module map, BacktestConfig fields, Web Worker execution, and the Python↔TS parity test that keeps the port honest.

TradePilot’s engine is a pure-TypeScript port of the original Python library (tradepilot/). It has no external dependencies, operates on plain number arrays, and runs entirely in the browser. Source lives under web/src/lib/engine/.

Module map

ModuleResponsibility
metrics.tsreturns, annualization, Sharpe/Sortino, drawdown, VaR/CVaR, skew/kurtosis, covariance
strategies.tsmomentumStrategy, meanReversionStrategy, smartBetaStrategy
optimization.tsmsr, gmv, equalWeight, efficientFrontier (projected-gradient solver)
analytics.tswinRate, profitFactor, calmarRatio, avgWinLoss, monthlyReturns, topDrawdowns
simulator.tsthe run loop: rebalance → select → optimize → daily valuation → costs → benchmark
runBacktest.tsrunBacktestInWorker() (off-thread) and runBacktest() (sync fallback)
worker.tsWeb Worker entry that runs a BacktestConfig and posts progress/result
types.tsall shared types

BacktestConfig

The single input object every run and Lab mode is built from:

FieldMeaning
symbolstickers in the universe
strategy'momentum' | 'meanReversion' | 'smartBeta'
optimizer'MSR' | 'GMV' | 'EW'
startDate / endDateISO dates bounding the run
initialCapitalstarting portfolio value (USD)
rebalanceFreqtrading days between rebalances
topNnumber of top-ranked assets held
riskFreeRateannual risk-free rate (decimal, e.g. 0.04)
windowlookback window (trading days) for ranking
tmomentum / ranking lookback parameter
minWeight / maxWeightper-asset weight bounds (default 0.01 / 0.95)
costs?{ costBps, slippageBps } — omitted ⇒ frictionless
benchmarkSymbol?buy-and-hold benchmark (default SPY)

A run returns BacktestResultV2: dates, portfolioValues, weights, trades, metrics (ExtendedMetrics), monthlyReturns, topDrawdowns, and benchmarkValues / benchmarkDates (or null). It’s JSON-serializable for storage.

Worker execution

Backtests run off the main thread. runBacktestInWorker() spins up a module Web Worker, posts a WorkerRunMessage ({ config, priceMap }), and receives a stream of responses: { type: 'progress', fraction }, then either { type: 'result', result } or { type: 'error', message }. The priceMap is a plain record (structured-clone friendly); the worker rebuilds a Map internally. runBacktest() is a synchronous fallback for Node/tests where no Worker exists. The Lab parallelizes many runs across a worker pool.

Python parity

The engine is verified against the Python original by a parity test (python-parity.test.ts). A few functions preserve Python-specific numerical choices on purpose so results match bit-for-bit:

  • std ddof — sample (ddof=1) vs. population (ddof=0) is chosen per function to mirror pandas/scipy (e.g. varGaussian uses population std; skewness/kurtosis mirror pandas’ std() with a population moment).
  • Legacy valuationsimulator.ts keeps a runLegacy() with rebalance-date-only valuation and periodsPerYear = 52, which is what the parity test pins; the app itself uses daily valuation (252).

If you change a metric, keep the parity test green — it’s the contract that the browser engine equals the reference implementation.