backtest-kit is a production-ready TypeScript framework for backtesting and live trading algorithmic strategies. It provides crash-safe state persistence, comprehensive signal validation, memory-optimized async generators, and enterprise-grade reporting capabilities.
The framework enables:
Backtest.run() and Backtest.background()Live.run() and Live.background()Walker.run() and Walker.background()Heat.getData()addRisk()PositionSize classSchedule.getData()The framework implements a six-layer clean architecture with dependency injection:
| Layer | Purpose | Key Components | Location |
|---|---|---|---|
| User Interface | Public API exports and utility classes | Backtest, Live, Walker, Heat, Schedule, PositionSize |
src/classes/, src/function/ |
| Registry & Validation | Schema storage and validation logic | *SchemaService, *ValidationService |
src/lib/services/schema/, src/lib/services/validation/ |
| Execution Engine | Core orchestration and business logic | *LogicPrivateService, *GlobalService, *ConnectionService, Client* classes |
src/lib/services/logic/, src/lib/services/global/, src/lib/services/connection/, src/client/ |
| State Management | Crash-safe persistence and configuration | PersistSignalAdapter, PersistRiskAdapter, GLOBAL_CONFIG |
src/classes/Persist.ts, src/config/params.ts |
| Event System | Pub-sub for observability | signalEmitter, doneEmitter, errorEmitter, progressEmitter |
src/config/emitters.ts |
| Reporting & Analytics | Performance metrics and markdown generation | BacktestMarkdownService, LiveMarkdownService, etc. |
src/lib/services/markdown/ |
The framework provides three primary execution modes with distinct characteristics:
| Mode | Entry Point | Execution Pattern | Output | Use Case |
|---|---|---|---|---|
| Backtest | Backtest.run(symbol, {strategyName, exchangeName, frameName}) |
Finite loop over historical timeframes | IStrategyTickResultClosed only |
Strategy validation with historical data |
| Live | Live.run(symbol, {strategyName, exchangeName}) |
Infinite loop with 1-minute intervals | IStrategyTickResult (idle/opened/active/closed) |
Real-time trading with crash recovery |
| Walker | Walker.run(symbol, {walkerName}) |
Iterates strategies, runs Backtest for each | IWalkerResults with ranked strategies |
A/B testing and strategy selection |
Backtest Mode:
ClientStrategy.backtest(candles) for fast-forward simulationClientFrame.getTimeframe() based on IFrameSchemaLive Mode:
ClientStrategy.tick() with real-time VWAP from ClientExchange.getAveragePrice()PersistSignalAdapter.writeValue() at state transitionswaitForInit() pattern in src/client/ClientStrategy.ts_lastSignalTimestamp checksWalker Mode:
Backtest.run() executionssharpeRatio, winRate, avgPnl, totalPnl, certaintyRatio, expectedYearlyReturnsprogressEmitter in src/config/emitters.tsWalkerMarkdownServiceThe client layer contains pure business logic without dependency injection:
| Component | Responsibility | Key Methods |
|---|---|---|
ClientStrategy |
Signal lifecycle, validation, TP/SL monitoring | tick(), backtest(), getSignal() |
ClientExchange |
Candle data fetching, VWAP calculation | getCandles(), getAveragePrice() |
ClientFrame |
Timeframe generation for backtesting | getTimeframe() |
PersistSignalAdapter |
Crash-safe signal persistence | write(), read() |
The service layer handles dependency injection and routing:
Service Types:
Schema Services - Registry for user configurations
StrategySchemaService, ExchangeSchemaService, FrameSchemaServiceaddStrategy(), addExchange(), addFrame()Connection Services - Memoized client instance creators
StrategyConnectionService, ExchangeConnectionService, FrameConnectionServiceGlobal Services - Context-aware wrappers
StrategyGlobalService, ExchangeGlobalService, FrameGlobalServiceExecutionContextService into operationsLogic Services - Execution orchestration
BacktestLogicPrivateService, LiveLogicPrivateServiceFor detailed service layer documentation, see Service Layer.
Signals transition through six states in a type-safe discriminated union:
The framework defines signal states as a discriminated union in types.d.ts:654-770:
type IStrategyTickResult =
| IStrategyTickResultIdle // action: "idle"
| IStrategyTickResultScheduled // action: "scheduled" (new in v1.x)
| IStrategyTickResultOpened // action: "opened"
| IStrategyTickResultActive // action: "active"
| IStrategyTickResultClosed // action: "closed"
| IStrategyTickResultCancelled // action: "cancelled" (new in v1.x)
| State | When Yielded | Key Properties | Lifecycle Callbacks |
|---|---|---|---|
idle |
Live: always when no signal, Backtest: never | signal: null, currentPrice |
onIdle() |
scheduled |
Both: when priceOpen specified in ISignalDto |
signal: IScheduledSignalRow, currentPrice |
onSchedule() |
opened |
Both: when position activated | signal: ISignalRow, currentPrice |
onOpen() |
active |
Live: every tick while monitoring, Backtest: never | signal: ISignalRow, currentPrice |
onActive() |
closed |
Both: when TP/SL/time triggered | pnl: IStrategyPnL, closeReason, closeTimestamp |
onClose() |
cancelled |
Both: when scheduled signal times out or SL hit before entry | signal: IScheduledSignalRow, closeTimestamp |
onCancel() |
When getSignal() returns ISignalDto with priceOpen specified:
scheduled state with _isScheduled: true flagcurrentPrice against priceOpen each tickpriceOpen, signal transitions to openedCC_SCHEDULE_AWAIT_MINUTES) or priceStopLoss hit first, signal transitions to cancelledSchedule.getData() in src/classes/Schedule.tsThe framework uses a registration-then-execution pattern:
| Function | Purpose | Interface |
|---|---|---|
addStrategy() |
Register trading strategy | IStrategySchema |
addExchange() |
Register data source | IExchangeSchema |
addFrame() |
Register timeframe generator | IFrameSchema |
Key Characteristics:
MethodContextService provides schema names for lookupFor configuration details, see Configuration Functions.
The framework uses symbol-based dependency injection with scoped context propagation:
The framework uses di-scoped for implicit context passing:
{symbol, when, backtest} to operations{strategyName, exchangeName, frameName} for routingBenefits:
For DI system details, see Dependency Injection System.
| Package | Version | Purpose |
|---|---|---|
di-kit |
^1.0.18 | Symbol-based dependency injection container |
di-scoped |
^1.0.20 | Scoped context propagation for async operations |
functools-kit |
^1.0.93 | Functional utilities (singleshot, queued, memoize) |
get-moment-stamp |
^1.1.1 | Timestamp utilities for candle intervals |
build/index.cjs) and ESM (build/index.mjs)types.d.ts)The framework is optimized for production use:
| Feature | Technique | Benefit |
|---|---|---|
| Client instances | Memoization via ConnectionServices |
One instance per schema name |
| Backtest streaming | Async generator | Early termination possible |
| Context propagation | di-scoped |
No parameter threading overhead |
| Persistence | Atomic file writes | Crash safety without performance penalty |
The framework provides comprehensive validation and error handling:
Signals are validated automatically in ClientStrategy:
Validation Location: ClientStrategy.ts signal validation logic
Persistence Location: src/classes/Persist.ts
For error handling patterns, see Error Handling.
To use the framework:
npm install backtest-kit - see Installation and SetupaddStrategy(), addExchange(), addFrame() - see Configuration FunctionsBacktest.run() for historical testing - see Backtest APILive.run() for production deployment - see Live Trading APIgetReport() and dump() - see Reporting and Analytics