This document explains the service layer architecture in backtest-kit, which sits between the Public API and Client classes. The service layer provides dependency injection, context management, validation, and orchestration logic. This page covers the six service type categories and their relationships. For implementation details of specific service types, see Connection Services, Schema Services, Validation Services, Global Services, Logic Services, and Markdown Services.
The service layer implements a 6-layer architecture where each layer has a specific responsibility in the framework's execution pipeline. Services use dependency injection via di-kit with Symbol-based tokens for type-safe resolution. All services are registered in src/lib/core/provide.ts and injected in src/lib/index.ts.
The service layer isolates business logic (Client classes) from framework concerns (validation, context propagation, persistence, reporting). Client classes have no DI dependencies and receive all parameters explicitly, while services handle cross-cutting concerns through dependency injection.
The framework organizes services into six categories, each with a distinct purpose in the execution flow:
Services are organized into nine groups in the codebase, with each group containing related service instances:
| Group | Symbol Prefix | File Location | Count |
|---|---|---|---|
| Base Services | loggerService |
src/lib/services/base/ | 1 |
| Context Services | *ContextService |
src/lib/services/context/ | 2 |
| Connection Services | *ConnectionService |
src/lib/services/connection/ | 5 |
| Schema Services | *SchemaService |
src/lib/services/schema/ | 6 |
| Global Services | *GlobalService |
src/lib/services/global/ | 8 |
| Logic Private Services | *LogicPrivateService |
src/lib/services/logic/private/ | 3 |
| Logic Public Services | *LogicPublicService |
src/lib/services/logic/public/ | 3 |
| Markdown Services | *MarkdownService |
src/lib/services/markdown/ | 6 |
| Validation Services | *ValidationService |
src/lib/services/validation/ | 6 |
The dependency injection container is initialized through three files:
provide()inject<T>() and exports the unified backtest objectServices use Symbol-based dependency injection for type-safe resolution. Each service type has a unique Symbol defined in src/lib/core/types.ts:
Services receive dependencies through constructor injection. The DI container resolves the dependency graph at initialization time via init() called in src/lib/index.ts:164.
Each service type has a specific role in the framework's execution flow:
Store component configurations using the ToolRegistry pattern from functools-kit. Schema services provide a typed registry for strategies, exchanges, frames, walkers, sizing, and risk profiles. They perform shallow validation during registration and allow runtime retrieval by name.
Key Methods: register(), get(), override(), validateShallow()
Implementations: StrategySchemaService, ExchangeSchemaService, FrameSchemaService, WalkerSchemaService, SizingSchemaService, RiskSchemaService
Perform runtime existence checks and cross-component validation. Validation services use memoization to cache validation results and prevent redundant checks. They verify that referenced components exist before execution begins.
Key Methods: addStrategy(), addExchange(), addFrame(), validate(), list()
Implementations: StrategyValidationService, ExchangeValidationService, FrameValidationService, WalkerValidationService, SizingValidationService, RiskValidationService
Create and memoize Client class instances. Connection services act as factories for ClientStrategy, ClientExchange, ClientFrame, ClientRisk, and ClientSizing. They use functools-kit's memoize() to ensure one instance per component name.
Key Methods: get() (memoized)
Implementations: StrategyConnectionService, ExchangeConnectionService, FrameConnectionService, SizingConnectionService, RiskConnectionService
Provide public API entry points with validation and context setup. Global services wrap lower-level logic services and ensure all prerequisites are met before execution. They orchestrate validation, context propagation, and delegation to logic services.
Key Methods: tick(), backtest(), getTimeframe(), checkSignal()
Implementations: StrategyGlobalService, ExchangeGlobalService, FrameGlobalService, BacktestGlobalService, LiveGlobalService, WalkerGlobalService, SizingGlobalService, RiskGlobalService
Orchestrate execution loops and async generator flows. Logic services are split into Public (context management) and Private (core logic) layers. Public services wrap generators with MethodContextService.runAsyncIterator(), while Private services contain the actual backtest/live/walker execution logic.
Key Methods: run() (async generator)
Implementations: BacktestLogicPublicService, BacktestLogicPrivateService, LiveLogicPublicService, LiveLogicPrivateService, WalkerLogicPublicService, WalkerLogicPrivateService
Subscribe to event emitters and generate performance reports. Markdown services accumulate signal events, calculate statistics, and format results as markdown tables. They use memoization per strategy/exchange/frame to maintain separate report storage.
Key Methods: getData(), getReport(), dump()
Implementations: BacktestMarkdownService, LiveMarkdownService, ScheduleMarkdownService, PerformanceMarkdownService, WalkerMarkdownService, HeatMarkdownService
Services form a dependency chain from public API to Client classes:
Services are organized around six component types (Strategy, Exchange, Frame, Walker, Sizing, Risk). Each component has a complete service stack:
The pattern is consistent across all components: Global → Validation/Logic → Connection → Schema → Client. This uniformity makes the codebase predictable and maintainable.
Services use MethodContextService and ExecutionContextService from di-scoped to propagate context without explicit parameters. The context flows through the service stack:
Services at any depth can resolve MethodContextService or ExecutionContextService via DI to access context without it being passed as parameters. This enables clean APIs where strategy authors call getCandles(symbol, interval, limit) instead of getCandles(symbol, interval, limit, context).