This page provides a comprehensive technical reference of backtest-kit's core capabilities. These features enable production-grade backtesting and live trading with enterprise-level reliability, performance, and extensibility.
For installation and setup instructions, see Installation and Setup. For hands-on examples, see Quick Start Guide. For detailed architecture documentation, see Architecture.
The framework supports three distinct execution modes that share identical strategy code through context propagation:
Backtest Mode processes historical data deterministically using async generators. It fast-forwards through active signals for performance optimization, yielding only closed/cancelled results.
Live Mode operates in real-time with crash recovery, polling at 1-minute intervals (TICK_TTL). It persists state atomically to disk after every signal change.
Walker Mode orchestrates sequential backtests for multiple strategies, enabling A/B testing and metric-based ranking.
Implementation:
All execution modes use async generators (async function*) to stream results without memory accumulation:
| Mode | Generator Type | Memory Profile |
|---|---|---|
| Backtest | AsyncGenerator<IStrategyBacktestResult> |
O(1) per timeframe |
| Live | AsyncGenerator<IStrategyTickResult> |
O(1) per tick |
| Walker | AsyncGenerator<WalkerContract> |
O(n) where n = strategies |
This architecture enables processing years of historical data without loading everything into memory, and allows early termination via break statements in consumer code.
All execution modes support programmatic stopping with graceful cleanup:
// Stop methods wait for active signals to complete
await Backtest.stop("BTCUSDT", "strategy-name");
await Live.stop("BTCUSDT", "strategy-name");
await Walker.stop("BTCUSDT", "walker-name");
Shutdown behavior:
listenDoneBacktest, listenDoneLive) fire when completeLive trading mode uses atomic file writes with automatic recovery to ensure no duplicate signals or lost state after process crashes:
The persistence layer uses a base class (PersistBase) that can be extended for custom backends:
| Default | Custom Options |
|---|---|
| File-based atomic writes | Redis (high-performance distributed) |
| JSON serialization | MongoDB (complex queries, analytics) |
./logs/data/ directory |
PostgreSQL (relational, ACID) |
| No external dependencies | Any storage implementing PersistBase interface |
Implementation details:
The framework allows replacing default file-based persistence with custom backends:
// Register custom adapter BEFORE running strategies
PersistSignalAdapter.usePersistSignalAdapter(RedisPersist);
PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
Custom adapters must implement the PersistBase interface:
| Method | Purpose |
|---|---|
waitForInit(initial: boolean) |
Initialize connection/storage |
readValue<T>(entityId) |
Read entity by ID |
hasValue(entityId) |
Check entity existence |
writeValue<T>(entityId, entity) |
Write/update entity |
removeValue(entityId) |
Delete entity |
removeAll() |
Clear all entities |
values<T>() |
Async iterator over all values |
keys() |
Async iterator over all IDs |
Signals are validated before execution to prevent invalid trades:
Validation implementation in src/client/Strategy.client.ts uses the VALIDATE_SIGNAL_FN constant with configurable parameters from GLOBAL_CONFIG.
The signal lifecycle is implemented as a discriminated union with compile-time type safety:
TypeScript discriminated union types ensure type safety at compile time:
type IStrategyTickResult =
| IStrategyTickResultIdle
| IStrategyTickResultScheduled
| IStrategyTickResultOpened
| IStrategyTickResultActive
| IStrategyTickResultClosed
| IStrategyTickResultCancelled;
Each variant has a unique action field used for type narrowing in consumer code.
Implementation: src/interfaces/StrategyTickResult.interface.ts
Strategies define a minimum interval between getSignal() calls to prevent signal spam:
| Interval | Minutes | Use Case |
|---|---|---|
"1m" |
1 | High-frequency scalping |
"3m" |
3 | Short-term signals |
"5m" |
5 | Standard intraday |
"15m" |
15 | Medium-term swing |
"30m" |
30 | Position trading |
"1h" |
60 | Long-term strategies |
The throttling is enforced by comparing the last signal timestamp with the configured interval. Implementation in src/client/Strategy.client.ts.
Signals with priceOpen defined become scheduled (limit orders) waiting for price activation:
Scheduled signal behavior:
priceOpenpriceStopLoss hit before activationCC_SCHEDULE_AWAIT_MINUTES, default 120 minutes)Edge case handling:
priceOpen and priceStopLoss on same candle, activation takes priority (signal opens then immediately closes by SL)All entry/exit decisions use Volume-Weighted Average Price from the last 5 one-minute candles for realistic simulation:
The VWAP calculation ensures backtest results match live execution behavior. Configured via:
CC_AVERAGE_PRICE_CANDLE_COUNT - Number of candles (default: 5)CC_AVERAGE_PRICE_CANDLE_INTERVAL - Candle interval (default: "1m")Implementation: src/helpers/getAveragePrice.ts
The async generator architecture processes data without accumulation:
| Operation | Memory Usage | Scaling |
|---|---|---|
| Backtest 1 year of 1m data | O(1) | Constant per timeframe |
| Live trading 24/7 | O(1) | Constant per tick |
| Walker with 100 strategies | O(n) | Linear in strategy count |
| Heatmap across 50 symbols | O(n) | Linear in symbol count |
Backtest fast-forward optimization: When a signal opens, the backtest skips ahead to the estimated close time rather than processing every timeframe. This reduces execution time by ~10-100x for strategies with long signal durations.
The dependency injection system memoizes service instances by configuration key:
This ensures one instance per unique combination of:
Implementation: src/services/connection/
The LiveMarkdownService uses a bounded queue (MAX_EVENTS = 25) to prevent memory leaks during long-running live sessions:
Queue behavior:
The framework calculates extensive performance metrics for backtests and live trading:
| Metric | Formula | Interpretation |
|---|---|---|
| Win Rate | (wins / total) × 100 |
Percentage of profitable trades |
| Average PNL | Σ(pnl) / count |
Expected return per trade |
| Total PNL | Σ(pnl) |
Cumulative return |
| Standard Deviation | √(Σ(pnl - avg)² / (n-1)) |
Volatility measure |
| Sharpe Ratio | avgPnl / stdDev |
Risk-adjusted return |
| Annualized Sharpe | sharpe × √365 |
Yearly risk-adjusted return |
| Certainty Ratio | `avgWin / | avgLoss |
| Expected Yearly Returns | Based on avg duration & PNL | Projected annual performance |
Safe math: All calculations return null for invalid results (NaN, Infinity) rather than propagating unsafe values.
Implementation: src/services/markdown/
Nine types of markdown reports are automatically generated:
Reports include:
Access via:
Backtest.dump(strategyName) - README.md:188Live.dump(strategyName) - README.md:413Walker.dump(symbol, walkerName) - README.md:467Heat.dump(strategyName) - README.md:559Multi-symbol performance analysis with extended metrics:
interface IHeatmapRow {
symbol: string;
totalPnl: number | null;
sharpeRatio: number | null;
profitFactor: number | null; // wins / losses ratio
expectancy: number | null; // (winRate × avgWin) - (lossRate × avgLoss)
winRate: number | null;
avgWin: number | null;
avgLoss: number | null;
maxDrawdown: number | null;
maxWinStreak: number; // consecutive wins
maxLossStreak: number; // consecutive losses
totalTrades: number;
}
Sorting: Symbols sorted by Sharpe Ratio descending (best performers first)
Portfolio metrics: Aggregated statistics across all symbols
The framework tracks milestone events as price moves toward TP or SL:
| Milestone Type | Thresholds |
|---|---|
| Partial Profit | 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90% toward TP |
| Partial Loss | -40%, -80% toward SL |
These events fire callbacks and emit to listeners, enabling:
Built-in execution time tracking with aggregated statistics:
| Statistic | Description |
|---|---|
| Average | Mean execution time |
| Minimum | Fastest execution |
| Maximum | Slowest execution |
| StdDev | Execution time variance |
| P95 | 95th percentile |
| P99 | 99th percentile |
Helps identify bottlenecks in:
The risk management system coordinates across strategies and symbols:
Validation context provided to each function:
| Field | Type | Description |
|---|---|---|
symbol |
string | Trading pair |
strategyName |
string | Strategy identifier |
exchangeName |
string | Exchange identifier |
currentPrice |
number | Current market price |
timestamp |
number | Current timestamp |
activePositionCount |
number | Total open positions |
activePositions |
IActivePosition[] | Array of all active positions |
Fail-fast pattern: Validations execute sequentially; first failure stops execution and rejects signal.
Implementation: src/client/Risk.client.ts
Three position sizing methods with configurable constraints:
When to use each method:
Fixed Percentage - Simple, consistent risk per trade. Best for beginners and conservative strategies.
Kelly Criterion - Optimal sizing based on edge (win rate × win/loss ratio). Best for strategies with proven statistical advantage. Use fractional Kelly (0.25-0.5) to reduce volatility.
ATR-Based - Volatility-adjusted sizing. Position size scales inversely with market volatility. Best for swing trading and volatile markets.
Implementation: src/utils/PositionSize.utils.ts
Risk profiles can inspect all active positions for cross-strategy coordination:
addRisk({
riskName: "coordinator",
validations: [
({ activePositions, strategyName, symbol }) => {
// Count positions for specific strategy
const strategyPositions = activePositions.filter(
pos => pos.strategyName === strategyName
);
// Check for symbol conflicts
const symbolPositions = activePositions.filter(
pos => pos.symbol === symbol
);
// Custom validation logic
if (strategyPositions.length >= 2) {
throw new Error("Max 2 positions per strategy");
}
}
]
});
Exchange adapters provide market data without requiring pre-downloaded datasets:
| Method | Purpose | Return Type |
|---|---|---|
getCandles() |
Fetch OHLCV data | Promise<ICandle[]> |
formatPrice() |
Format price for exchange | Promise<string> |
formatQuantity() |
Format quantity for exchange | Promise<string> |
Zero data download: Unlike Freqtrade and similar frameworks, backtest-kit doesn't require downloading gigabytes of historical data. You can plug any data source: CCXT for live data, databases for fast backtesting, or custom APIs.
Example integrations:
Implementation: src/client/Exchange.client.ts
Components are registered by name and lazily instantiated at runtime:
Benefits:
Schema reflection: Use listExchanges(), listStrategies(), listFrames() for runtime introspection.
Async context propagation eliminates need for explicit parameter passing:
This "time-travel context" enables the same strategy code to:
when timestampnew Date() timestampImplementation:
Based on di-scoped package with async_hooks-style scoping.
The framework includes extensive test coverage across multiple dimensions:
| Test Category | File | Coverage |
|---|---|---|
| Exchange Functions | test/spec/exchange.test.mjs | VWAP calculation, candle fetching, price formatting |
| Event System | test/spec/event.test.mjs | Listener coordination, async event handling |
| Signal Validation | test/spec/validation.test.mjs | Price logic, timestamp validation, position validation |
| PNL Calculation | test/spec/pnl.test.mjs | Fees, slippage, realistic simulations |
| Backtest Mode | test/spec/backtest.test.mjs | Lifecycle, early termination, close reasons |
| Callbacks | test/spec/callbacks.test.mjs | Parameter passing, backtest flag verification |
| Reports | test/spec/report.test.mjs | Statistics, markdown formatting |
| Live Mode | test/spec/live.test.mjs | Real-time execution, crash recovery |
| Scheduled Signals | test/spec/scheduled.test.mjs | Activation, cancellation, timeout |
| Risk Management | test/spec/risk.test.mjs | Validation chain, position tracking |
| Position Sizing | test/spec/sizing.test.mjs | Fixed %, Kelly, ATR methods |
| Walker Mode | test/spec/walker.test.mjs | Strategy comparison, ranking |
| Heatmap | test/spec/heat.test.mjs | Portfolio metrics, cross-symbol analysis |
| Performance | test/spec/performance.test.mjs | Execution timing, bottleneck detection |
| Optimizer | test/spec/optimizer.test.mjs | AI strategy generation, LLM integration |
Critical defensive tests ensure correct behavior in complex scenarios:
These tests prove that the framework handles edge cases correctly and prevents financial loss from logic bugs.
All statistical calculations are protected against unsafe numeric values:
Unsafe value detection:
NaN (invalid calculations)Infinity / -Infinity (division by zero)Handling: Return null instead of propagating unsafe values through calculations.
Benefits:
Implementation in statistics calculation functions: src/services/markdown/
LLM-powered strategy generation from historical data:
Optimizer workflow:
Output structure:
Debug artifacts: JSON conversation history saved to ./dump/strategy/ for analysis.
Implementation: src/client/Optimizer.client.ts, src/services/template/OptimizerTemplateService.ts
| Feature | Key Component | Wiki Reference |
|---|---|---|
| Multi-mode execution | BacktestLogicPrivateService, LiveLogicPrivateService, WalkerLogicPrivateService | Execution Modes |
| Crash-safe persistence | PersistSignalAdapter, PersistBase | Signal Persistence |
| Signal validation | VALIDATE_SIGNAL_FN, Risk validation chain | Signal Generation and Validation |
| State machine | IStrategyTickResult discriminated union | Signal States |
| VWAP pricing | getAveragePrice(), CC_AVERAGE_PRICE_* config | Backtesting |
| Interval throttling | Strategy.interval, last signal timestamp check | Signal Lifecycle Overview |
| PNL calculation | calculatePnl(), CC_TRADE_FEE, CC_SLIPPAGE | PnL Calculation |
| Context propagation | ExecutionContextService, MethodContextService | Context Propagation |
| Markdown reports | BacktestMarkdownService, LiveMarkdownService, etc. | Markdown Report Generation |
| Performance profiling | PerformanceMarkdownService, PerformanceEmitter | Performance Metrics |
| Strategy comparison | WalkerLogicPrivateService | Walker Mode |
| Portfolio heatmap | HeatMarkdownService | Heatmap Analytics |
| Position sizing | PositionSize utils (fixed/kelly/atr) | Component Types |
| Risk management | ClientRisk, IRiskSchema validations | Risk Management |
| Pluggable exchanges | ClientExchange, IExchangeSchema | Exchange Schemas |
| Pluggable persistence | PersistBase, custom adapters | Custom Persistence Backends |
| AI optimizer | ClientOptimizer, Ollama integration | AI-Powered Strategy Optimization |