Core Concepts

This page introduces the fundamental architectural patterns and execution models of backtest-kit. It covers the three execution modes (Backtest, Live, Walker), the signal lifecycle state machine, and the component-based registration system. For detailed API documentation, see Public API Reference. For implementation details of specific components, see Component Types.

backtest-kit provides three execution modes that share the same strategy and exchange components but operate under different timing and data models:

Mermaid Diagram

Mode Time Progression Data Source Use Case Completion
Backtest Historical timestamps from IFrameSchema Frame.getTimeframe() array Strategy validation on past data Finite (when timeframe exhausted)
Live Real-time via Date.now() Live exchange API calls Production trading Infinite (until stopped)
Walker Orchestrates multiple Backtests Shared IFrameSchema across strategies A/B testing and optimization Finite (after all strategies tested)

The execution modes are accessed through singleton utility classes:

Each mode implements a similar async generator pattern but with different orchestration logic. Backtest and Live both call strategy.tick() or strategy.backtest(), while Walker iterates through multiple strategies and compares their results by a configurable metric (WalkerMetric).

Signals progress through a state machine implemented as a discriminated union of result types. The action field serves as the discriminator for type-safe state handling.

Mermaid Diagram

The framework uses TypeScript discriminated unions for type-safe signal state handling:

type IStrategyTickResult = 
| IStrategyTickResultIdle
| IStrategyTickResultScheduled
| IStrategyTickResultOpened
| IStrategyTickResultActive
| IStrategyTickResultClosed
| IStrategyTickResultCancelled;

Each state type has a unique action discriminator:

Signals track two critical timestamps for accurate duration calculation:

The minuteEstimatedTime countdown uses pendingAt, not scheduledAt, ensuring scheduled signals don't count waiting time toward expiration - src/client/ClientStrategy.ts:681-683.

backtest-kit uses a registration-based architecture where components are defined as schemas and instantiated on-demand via dependency injection.

Mermaid Diagram

The framework provides six registrable component types, each with a dedicated schema interface:

Component Schema Interface Purpose Registration Function
Strategy IStrategySchema Signal generation logic via getSignal() addStrategy()
Exchange IExchangeSchema Market data via getCandles(), price formatting addExchange()
Frame IFrameSchema Backtest timeframe generation via getTimeframe() addFrame()
Risk IRiskSchema Portfolio-level position limits and validations addRisk()
Sizing ISizingSchema Position size calculation (fixed, Kelly, ATR) addSizing()
Walker IWalkerSchema Multi-strategy comparison configuration addWalker()

Each schema is stored in a corresponding *SchemaService using the ToolRegistry pattern - src/lib/services/schema/StrategySchemaService.ts, src/lib/services/schema/ExchangeSchemaService.ts, etc.

The framework uses memoized Connection Services to lazily instantiate Client classes:

Mermaid Diagram

This two-tier architecture (Schema Services + Connection Services) enables:

  1. Validation at registration time: Schema structure is validated immediately via *ValidationService.validate() - src/lib/services/validation/StrategyValidationService.ts
  2. Lazy instantiation: Client instances are only created when first used, reducing memory overhead
  3. Instance reuse: Memoization ensures one Client per schema name, preventing state duplication
  4. Crash recovery: Live mode can restore persisted state via waitForInit() before first operation - src/client/ClientStrategy.ts:298-330

The framework uses di-scoped to propagate execution context without explicit parameter passing. Two context types flow through the system:

ExecutionContext (IExecutionContext):

  • symbol: Trading pair (e.g., "BTCUSDT")
  • when: Current timestamp (historical for backtest, Date.now() for live)
  • backtest: Boolean flag indicating execution mode

MethodContext (IMethodContext):

  • strategyName: Which strategy schema to use
  • exchangeName: Which exchange schema to use
  • frameName: Which frame schema to use (empty string for live mode)

Mermaid Diagram

This pattern enables clean strategy code without framework boilerplate:

// Strategy author writes:
const candles = await getCandles(symbol, interval, limit);

// Framework automatically injects:
// - executionContext.when (which timestamp to query)
// - methodContext.exchangeName (which exchange to use)
// - executionContext.backtest (historical vs real-time data)

For detailed context propagation mechanics, see Context Propagation.