Backtest Kit is a time execution engine, not a data-processing library. The engine treats time as a first-class stream — your strategy is evaluated step by step as virtual time advances through historical candles (backtest) or the real clock (live). Understanding this distinction makes everything else click into place.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit/llms.txt
Use this file to discover all available pages before exploring further.
The Execution Engine
The engine is built on two JavaScript primitives: async generators for streaming results andAsyncLocalStorage for implicit time context propagation.
Async Generators for Streaming
BothBacktest.run() and Live.run() return AsyncIterableIterator objects. Results are yielded one at a time — the engine never accumulates a large array of trade results in memory. This matters for long backtests spanning months or years.
break:
Virtual Time via AsyncLocalStorage
When your strategy callsgetCandles(symbol, '1h', 24), the function needs to know when it is so it can return the correct slice of history. Backtest Kit solves this without passing a when parameter everywhere — it uses Node.js AsyncLocalStorage to attach the current virtual timestamp to the entire async call chain.
Every ExecutionContextService.runInContext({ symbol, when, backtest }, ...) call sets the implicit clock for all descendants. This makes the identical getSignal function safe in both modes: in backtest, when is the candle timestamp; in live, when is new Date(). Look-ahead bias is structurally impossible.
Schema Registration Pattern
Before running anything, you register four types of schemas. Each schema is stored in aToolRegistry keyed by its name. All methods that need to route to the right implementation look up the name from the active MethodContextService.context.
| Schema | Function | What it configures |
|---|---|---|
| Exchange | addExchangeSchema | Candle data source, price/quantity formatting |
| Strategy | addStrategySchema | Signal generation logic, interval throttling, risk profile |
| Frame | addFrameSchema | Backtest date range and tick interval |
| Risk | addRiskSchema | Validation functions applied before signal acceptance |
Signal Lifecycle
Every trading signal moves through a strict sequence of states represented as a discriminated union:idle— no signal is active;getSignalis called if the interval has elapsedscheduled— signal with apriceOpentarget was just created; emitted once on creationwaiting— scheduled signal is still waiting for price to reachpriceOpen; emitted on subsequent monitoring ticksopened— signal just opened at current price (market entry) or atpriceOpen(limit activation)active— signal is monitoring for TP/SL conditions every minuteclosed— signal exited withcloseReason:take_profit,stop_loss,time_expired, orclosed(manual)cancelled— scheduled signal expired or was cancelled before price reachedpriceOpen
action discriminator field makes type-safe branching straightforward:
Two Execution Modes
The only difference between backtest and live is the class you call and whether you supply aframeName.
| Property | Backtest | Live |
|---|---|---|
| Class | Backtest | Live |
| Time source | ClientFrame.getTimeframe() → Date[] | new Date() |
| Frame required | Yes | No |
| Persistence | Skipped (memory only) | Atomic file writes |
| Loop | Iterates timeframes[] | while(true) with sleep() |
| Signal recovery | N/A | waitForInit() loads last state |
VWAP Pricing
All entry, exit, and TP/SL checks use VWAP (Volume Weighted Average Price) calculated from the last 5 one-minute candles (configurable viaCC_AVG_PRICE_CANDLES_COUNT):
backtest(candles[]) array drives one VWAP calculation. The engine starts at index 4 (needs at least 5 candles) and checks TP/SL on every subsequent candle until the signal closes or time expires.
Crash-Safe Persistence
In live mode, every signal state mutation goes throughsetPendingSignal(signal), which atomically writes the signal JSON to disk using writeFileAtomic. On restart, ClientStrategy.waitForInit() reads the last saved state and resumes monitoring without re-entering or duplicating the position.
PersistSignalAdapter.usePersistSignalAdapter(RedisPersist) to use MongoDB + Redis instead of files.
Deeper Guides
Schemas
Full interface reference for all four schema types with working code examples.
Backtesting
Event-driven vs async iterator execution, multi-symbol parallelism, and report generation.
Live Trading
Crash recovery,
PersistSignalAdapter, Broker.enable(), and graceful shutdown.Signal Lifecycle
All four states, discriminated union types, validation, and interval throttling.
Risk Management
Custom validation functions,
IRiskCheckArgs, position tracking, and listenRisk.