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
| Module | Responsibility |
|---|---|
metrics.ts | returns, annualization, Sharpe/Sortino, drawdown, VaR/CVaR, skew/kurtosis, covariance |
strategies.ts | momentumStrategy, meanReversionStrategy, smartBetaStrategy |
optimization.ts | msr, gmv, equalWeight, efficientFrontier (projected-gradient solver) |
analytics.ts | winRate, profitFactor, calmarRatio, avgWinLoss, monthlyReturns, topDrawdowns |
simulator.ts | the run loop: rebalance → select → optimize → daily valuation → costs → benchmark |
runBacktest.ts | runBacktestInWorker() (off-thread) and runBacktest() (sync fallback) |
worker.ts | Web Worker entry that runs a BacktestConfig and posts progress/result |
types.ts | all shared types |
BacktestConfig
The single input object every run and Lab mode is built from:
| Field | Meaning |
|---|---|
symbols | tickers in the universe |
strategy | 'momentum' | 'meanReversion' | 'smartBeta' |
optimizer | 'MSR' | 'GMV' | 'EW' |
startDate / endDate | ISO dates bounding the run |
initialCapital | starting portfolio value (USD) |
rebalanceFreq | trading days between rebalances |
topN | number of top-ranked assets held |
riskFreeRate | annual risk-free rate (decimal, e.g. 0.04) |
window | lookback window (trading days) for ranking |
t | momentum / ranking lookback parameter |
minWeight / maxWeight | per-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.varGaussianuses population std;skewness/kurtosismirror pandas’std()with a population moment). - Legacy valuation —
simulator.tskeeps arunLegacy()with rebalance-date-only valuation andperiodsPerYear = 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.