The Service Layer provides the orchestration infrastructure that coordinates business logic execution across the framework. It implements a layered architecture where ~60 services are organized into functional categories, each handling a specific aspect of system operation: connection management, schema registration, validation, context injection, execution logic, and reporting. Services communicate through dependency injection, with the backtest aggregation object providing a unified namespace for all service access.
For information about the core business logic classes (ClientStrategy, ClientExchange, etc.) that services orchestrate, see Core Business Logic. For dependency injection implementation details, see Dependency Injection System.
Services follow a matrix organization pattern where most component types (Strategy, Exchange, Frame, Risk, Sizing) have corresponding service variants across multiple functional categories. This creates a predictable structure where finding a service for a specific component and function is straightforward.
The service matrix spans two dimensions:
| Component Type | Connection | Schema | Global | Validation | Markdown |
|---|---|---|---|---|---|
| Strategy | StrategyConnectionService | StrategySchemaService | StrategyGlobalService | StrategyValidationService | - |
| Exchange | ExchangeConnectionService | ExchangeSchemaService | ExchangeGlobalService | ExchangeValidationService | - |
| Frame | FrameConnectionService | FrameSchemaService | FrameGlobalService | FrameValidationService | - |
| Risk | RiskConnectionService | RiskSchemaService | RiskGlobalService | RiskValidationService | - |
| Sizing | SizingConnectionService | SizingSchemaService | SizingGlobalService | SizingValidationService | - |
| Walker | - | WalkerSchemaService | - | WalkerValidationService | WalkerMarkdownService |
| Optimizer | OptimizerConnectionService | OptimizerSchemaService | OptimizerGlobalService | OptimizerValidationService | - |
| Partial | PartialConnectionService | - | PartialGlobalService | - | PartialMarkdownService |
Additional service categories exist for execution modes:
Connection Services manage memoized instances of Client* business logic classes. Each service caches instances by a key derived from execution parameters (e.g., symbol:strategyName for strategies). The getStrategy, getExchange, getRisk methods use memoize from functools-kit to ensure only one instance exists per unique parameter combination.
Key Methods:
tick(), backtest(), stop(), clear(), getPendingSignal()getCandles(), getNextCandles(), getAveragePrice(), formatPrice(), formatQuantity()getRisk() returns memoized ClientRisk interfacegetSizing() returns memoized ClientSizing interfaceThese services ensure consistent instance routing - multiple calls with the same parameters return the same cached instance, preserving internal state like signal history and initialization promises.
Schema Services maintain registries mapping component names to their configuration schemas. Each service exposes register(name, schema) to add entries and get(name) to retrieve them. The registries are Map-based stores that persist for the application lifetime.
Example:
// Internal storage
private schemaMap = new Map<StrategyName, IStrategySchema>()
// Registration
strategySchemaService.register("my-strategy", {
strategyName: "my-strategy",
interval: "5m",
getSignal: async (symbol) => { /* ... */ }
})
// Retrieval
const schema = strategySchemaService.get("my-strategy")
These services are queried by Connection Services during instance creation to retrieve configuration data.
Global Services wrap Connection Services with context injection via ExecutionContextService. They call ExecutionContextService.runInContext() to set the symbol, when, and backtest parameters before delegating to Connection Services. This pattern enables implicit parameter passing without requiring these values to be passed through every function call.
Flow:
StrategyGlobalService.tick(symbol, when, backtest)
→ ExecutionContextService.runInContext({ symbol, when, backtest })
→ StrategyConnectionService.tick(symbol, strategyName)
→ ClientStrategy.tick() [reads context implicitly]
Global Services also call Validation Services to verify component registration before operations.
Validation Services implement two validation layers:
addStrategy, addExchange, etc.): Called by add.ts functions to verify schema correctness before registrationvalidate): Called by Global Services or Command Services before operations to verify component existsStrategyValidationService implements 30+ signal validation rules checked during strategy execution:
Command Services provide the top-level entry points for execution modes. They perform validation, inject context, and delegate to Logic Services.
Responsibilities:
Logic Services implement execution mode algorithms through a two-tier architecture:
BacktestLogicPublicService, etc.): External contract that validates parameters and delegates to PrivateBacktestLogicPrivateService, etc.): Internal implementation with core execution logicThis separation enables Private Services to call other Private Services without re-validation while maintaining a clean public API.
Execution Patterns:
tick(), skips ahead on signal openwhile(true) loop with sleep(TICK_TTL), persistence recoveryFor detailed execution flows, see Logic Services.
Markdown Services accumulate events from execution and generate reports with statistics. They subscribe to event emitters (e.g., signalBacktestEmitter) and maintain internal arrays of events (limited to MAX_EVENTS = 5000).
Methods:
getData(symbol, strategyName): Returns statistics object (sharpe ratio, win rate, etc.)getReport(symbol, strategyName): Returns markdown formatted report stringdump(strategyName, path): Writes report to diskStatistics Calculation:
isUnsafe() checks for NaN/InfinityFor detailed metrics, see Performance Metrics.
Template Services provide code generation templates for AI optimization. OptimizerTemplateService contains 11 template methods that generate different sections of the output script:
getTopBanner(): Imports and constantsgetJsonDumpTemplate(): Helper functionsgetStrategyTemplate(): Strategy configuration with LLM integrationgetWalkerTemplate(): Walker comparison setupgetLauncherTemplate(): Execution code with event listenersFor details on AI optimization, see AI-Powered Strategy Optimization.
The registration process follows three phases:
Phase 1: Type Definition - src/lib/core/types.ts:1-96 defines Symbol constants for each service. Symbols prevent naming collisions and enable type-safe lookups.
Phase 2: Service Registration - src/lib/core/provide.ts:1-132 calls provide(type, factory) for each service. Factory functions instantiate services with their dependencies injected via inject<T>(type).
Phase 3: Aggregation - src/lib/index.ts:212-224 imports all services via inject() and aggregates them into the backtest object, creating a single-namespace API.
The backtest object exported from src/lib/index.ts:212-224 flattens the service hierarchy into a single namespace. This provides convenient access to any service without needing to understand the dependency graph:
// Internal organization
const baseServices = {
loggerService: inject<LoggerService>(TYPES.loggerService),
}
const connectionServices = {
strategyConnectionService: inject<StrategyConnectionService>(TYPES.strategyConnectionService),
exchangeConnectionService: inject<ExchangeConnectionService>(TYPES.exchangeConnectionService),
// ... 5 more
}
const schemaServices = {
strategySchemaService: inject<StrategySchemaService>(TYPES.strategySchemaService),
exchangeSchemaService: inject<ExchangeSchemaService>(TYPES.exchangeSchemaService),
// ... 5 more
}
// ... 7 more categories
export const backtest = {
...baseServices,
...contextServices,
...connectionServices,
...schemaServices,
...globalServices,
...commandServices,
...logicPrivateServices,
...logicPublicServices,
...markdownServices,
...validationServices,
...templateServices,
}
This enables direct service access:
import backtest from "./lib"
// Access any service directly
backtest.loggerService.info("message", data)
backtest.strategySchemaService.get("my-strategy")
backtest.backtestCommandService.run(symbol, context)
The Backtest, Live, and Walker utility classes (src/classes/Backtest.ts, src/classes/Live.ts, src/classes/Walker.ts) use this object internally to access services.
Services follow a strict layering pattern to prevent circular dependencies:
Layering Rules:
This layering ensures:
Example Dependency Chain:
User calls Backtest.run()
→ BacktestCommandService.run()
→ BacktestLogicPublicService.run()
→ BacktestLogicPrivateService.run()
→ StrategyGlobalService.tick()
→ StrategyConnectionService.tick()
→ ClientStrategy.tick()
→ ExchangeConnectionService.getCandles()
→ ClientExchange.getCandles()
Each layer adds value:
For detailed information about each service category: