Backtest Kit is designed to run many symbols concurrently in a single Node.js process. AllDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-kit-docs/llms.txt
Use this file to discover all available pages before exploring further.
Backtest.background() contexts share one event loop, one MongoDB connection pool, and one Redis pool. There is no inter-process communication, no subprocess fork overhead, and no per-symbol OS thread. The cooperative event loop and Redis O(1) lookup cache allow parallel replays to advance nearly as fast as individual ones — with measured throughput of ~6,300× real-time aggregate speed across 9 symbols on commodity developer hardware.
Measured Performance
The numbers below were captured frombacktest-monorepo-parallel running the apr_2026 DCA ladder strategy across 9 symbols on a mid-range laptop (HP Victus 15, i5-13420H, 16 GB DDR4-3200, NVMe SSD, Mongo + Redis on localhost).
| Metric | Value |
|---|---|
| Symbols in parallel | 9 (BTC, POL, ZEC, HYPE, XAUT, DOGE, SOL, PENGU, HBAR) |
| Historical window | 27 days of 1m candles per symbol |
| Wall-clock duration | ~2.9 seconds for a 34-minute slice |
| Per-symbol replay speed | ~703× real-time |
| Aggregate replay speed | ~6,326× real-time (9 × 703) |
| Event throughput | ~103 events/sec (one Node process) |
| Total candle ticks | ~350,000 (38,880 × 9 symbols) |
Why it’s fast
Speed factors explained
Speed factors explained
- Single-process concurrency. All 9
Backtest.background()contexts share one event loop, one Mongo pool, and one Redis pool. No IPC, no fork overhead. - Redis O(1) cache. Every
findByContext(...)is a RedisGETbefore falling back to Mongo. Cold miss fills Redis; all subsequent ticks hit the cache directly. - Atomic upserts. Every write is a single
findOneAndUpdatewithupsert: true— no read-modify-write, no application-side locks, no E11000 retry loop under concurrent writes. - Pre-warmed candle cache. The
--cacheflag pre-fetches all OHLCV data into Mongo before the runners start, so the inner loop never blocks on CCXT HTTP. - JIT-friendly hot path. The per-tick body is synchronous arithmetic plus a few awaited helpers — V8 inlines aggressively and the tight loop reaches full JIT speed quickly.
Fan-Out: Running Multiple Symbols
CallBacktest.background() in a loop. Each call returns independently; all symbols advance in parallel through the shared event loop.
listenDoneBacktest fires once per symbol as each replay completes. Use it to generate per-symbol reports independently without waiting for the whole fan-out to finish.
State Isolation
Despite sharing all infrastructure, every symbol’s position state is completely independent. The framework keys every persistence operation on(symbol, strategyName, exchangeName) — a signal open for BTCUSDT cannot interfere with ETHUSDT’s state in any way.
Cron Coordination Across Parallel Backtests
The built-inCron scheduler is aware of parallel replays. A global job (no symbols argument) fires exactly once per virtual boundary across all running symbols — the first symbol to reach the boundary opens the slot; all others await the same promise and release together. This prevents double-fires of expensive operations like news fetches or model warm-ups.
CLI Fan-Out with --entry
For power-user setups, the --entry flag activates a parallel runner mode in @backtest-kit/cli that reads a CC_SYMBOL_LIST environment variable and calls Backtest.background() for every symbol automatically:
--entry, the CLI runs a standard single-strategy backtest using the exchange and frame registered inside the strategy file.
A/B Testing Strategy Variants in Parallel
Running multiple strategy variants side by side is identical to running multiple symbols — use distinctstrategyName values:
(symbol, strategyName) pair is fully isolated. Reports are written to ./dump/backtest/{strategyName}.md and can be compared programmatically with Backtest.getData(strategyName).
Portfolio Heatmap and Pooled Metrics
When multiple symbols complete,Backtest.getData() and Backtest.getReport() aggregate cross-symbol metrics:
| Metric | Description |
|---|---|
| Pooled Sharpe | Sharpe computed over the combined PNL distribution of all symbols |
| Sortino Ratio | Downside-risk-adjusted return across the portfolio |
| Calmar Ratio | Annualised return divided by portfolio maximum drawdown |
| Recovery Factor | Total portfolio PNL divided by maximum drawdown |
| Expectancy | Expected value per trade averaged across all symbols |
Community: backtest-monorepo-parallel
The backtest-monorepo-parallel
community project is a TypeScript monorepo template that runs 9 symbols in
parallel with shared Mongo + Redis infrastructure and a self-enforcement
runtime that exposes the workspace DI container to strategy files without
imports, bundler hooks, or strategy-author changes.It is the reference implementation for:
- Per-subsystem persistence mode switching (Mongo for live, memory for backtest)
- DI container pattern for shared services between strategy files
--entryflag usage withCC_SYMBOL_LISTfan-out- Mode A (parallel runner) vs Mode B (single CLI) entry point separation
Resource Guidance
RAM
Budget ~50–100 MB per symbol for candle cache and signal state. 9 symbols on 16 GB RAM leaves ample headroom for Mongo and Redis.
Mongo + Redis
Run both on the same machine as the Node process for lowest latency. Use docker-compose for a one-command local setup during development.
Candle pre-warming
Use
--cache to pre-fetch all OHLCV data before the runners start. Without pre-warming, the first pass blocks on CCXT HTTP and is 10–50× slower.Symbol count
9–15 symbols is a practical upper bound for a single Node process on commodity hardware. Above that, split into multiple processes with separate
CC_SYMBOL_LIST slices.