Schema Services provide centralized in-memory storage for component configurations registered via the add* functions in the public API. Each schema service implements the ToolRegistry pattern to store, retrieve, and validate registered schemas before they are used by connection services to instantiate client implementations.
For information about how schemas are validated before registration, see Validation Services. For information about how registered schemas are used to create client instances, see Connection Services.
Schema Services serve as the configuration registry layer in the service architecture. Their responsibilities include:
Schema Services do not perform validation logic (delegated to Validation Services) or instantiate clients (delegated to Connection Services). They are pure storage and retrieval mechanisms.
The framework provides seven schema services, one for each registrable component type:
| Schema Service | Schema Interface | Name Type | Registration Function | DI Symbol |
|---|---|---|---|---|
ExchangeSchemaService |
IExchangeSchema |
ExchangeName |
addExchange() |
TYPES.exchangeSchemaService |
StrategySchemaService |
IStrategySchema |
StrategyName |
addStrategy() |
TYPES.strategySchemaService |
FrameSchemaService |
IFrameSchema |
FrameName |
addFrame() |
TYPES.frameSchemaService |
WalkerSchemaService |
IWalkerSchema |
WalkerName |
addWalker() |
TYPES.walkerSchemaService |
SizingSchemaService |
ISizingSchema |
SizingName |
addSizing() |
TYPES.sizingSchemaService |
RiskSchemaService |
IRiskSchema |
RiskName |
addRisk() |
TYPES.riskSchemaService |
OptimizerSchemaService |
IOptimizerSchema |
OptimizerName |
addOptimizer() |
TYPES.optimizerSchemaService |
All schema services follow identical structural patterns but store different schema types.
Diagram: Schema Services in the Service Layer Hierarchy
This diagram shows how schema services sit between validation and connection layers. The public API functions call validation services for checks, then schema services for storage. Later, connection services retrieve schemas to instantiate clients.
Each schema service implements the ToolRegistry pattern for in-memory storage. This pattern provides:
Map<string, TSchema> where key is the unique nameDiagram: ToolRegistry Storage Operations
The ToolRegistry pattern provides a simple but consistent API across all schema services. Each operation interacts with the underlying Map storage.
The registration flow is consistent across all component types. Here is the detailed flow for strategy registration:
Diagram: Strategy Registration Sequence
The registration flow shows the orchestration between validation and storage. Validation occurs first (throwing errors if schema is invalid), followed by storage in the schema service's ToolRegistry.
Connection services are the primary consumers of schema services. They retrieve stored schemas to create memoized client instances:
Diagram: Schema Retrieval and Client Instantiation Flow
Connection services use MethodContextService to determine which schema to retrieve, then fetch it from the appropriate schema service. The retrieved schema is combined with runtime dependencies (logger, context services) to instantiate a memoized client.
Stores: IStrategySchema configurations with signal generation logic
Key Fields:
strategyName: Unique identifierinterval: Signal generation throttle interval ("1m" | "3m" | "5m" | "15m" | "30m" | "1h")getSignal: Async function returning ISignalDto | nullcallbacks: Optional lifecycle hooks (onOpen, onClose, onActive, etc.)riskName / riskList: Optional risk profile referencesUsed By: StrategyConnectionService to create ClientStrategy instances
Stores: IExchangeSchema configurations for market data access
Key Fields:
exchangeName: Unique identifiergetCandles: Async function fetching OHLCV dataformatPrice: Price formatting for exchange precisionformatQuantity: Quantity formatting for exchange precisioncallbacks: Optional hooks (onCandleData)Used By: ExchangeConnectionService to create ClientExchange instances
Stores: IFrameSchema configurations for backtest timeframe generation
Key Fields:
frameName: Unique identifierinterval: Timeframe granularity ("1m" | "3m" | "5m" | ... | "1d" | "3d")startDate: Backtest period startendDate: Backtest period endcallbacks: Optional hooks (onTimeframe)Used By: FrameConnectionService to create ClientFrame instances
Stores: IRiskSchema configurations for portfolio risk management
Key Fields:
riskName: Unique identifiervalidations: Array of IRiskValidation functionscallbacks: Optional hooks (onRejected, onAllowed)Used By: RiskConnectionService to create ClientRisk instances shared across strategies
Stores: ISizingSchema configurations for position sizing calculations
Key Fields:
sizingName: Unique identifiermethod: Sizing algorithm ("fixed-percentage" | "kelly-criterion" | "atr-based")callbacks: Optional hooks (onCalculate)Used By: SizingConnectionService to create ClientSizing instances
Stores: IWalkerSchema configurations for strategy comparison
Key Fields:
walkerName: Unique identifierstrategies: Array of strategy names to compareexchangeName: Exchange to use for all backtestsframeName: Timeframe to use for all backtestsmetric: Optimization metric (default: "sharpeRatio")callbacks: Optional hooks (onStrategyComplete, onComplete)Used By: WalkerCommandService for orchestrating multi-strategy backtests
Stores: IOptimizerSchema configurations for LLM-based strategy generation
Key Fields:
optimizerName: Unique identifierrangeTrain: Array of training periods (generates strategy variants)rangeTest: Testing period for validationsource: Array of data sources for LLM conversation buildinggetPrompt: Function generating strategy prompts from conversation historytemplate: Optional code generation template overridescallbacks: Optional hooks (onData, onCode, onDump, onSourceData)Used By: OptimizerConnectionService to create ClientOptimizer instances
Schema services are registered in the DI container as singleton instances:
Diagram: Schema Services Dependency Injection Flow
Schema services are registered once at application startup via provide() calls, creating singleton instances. The inject() function retrieves these instances using TYPES symbols, making them available throughout the application.
Schema services support schema replacement through re-registration. When register(name, schema) is called with an existing name, the new schema overwrites the old one:
Diagram: Schema Override and Cache Invalidation
When a schema is re-registered, the ToolRegistry overwrites the previous version. Connection services that memoize client instances must invalidate their caches to ensure new instances use the updated schema.
Schema services throw errors in two primary scenarios:
// Attempting to retrieve non-existent schema
const schema = schemaService.get("non-existent-name");
// Throws: Error("Schema 'non-existent-name' not found in StrategySchemaService")
This error occurs when connection services attempt to retrieve a schema that was never registered. The error message includes the schema name and service type for debugging.
Validation errors occur before schema registration, in the validation service layer:
addStrategy({
strategyName: "invalid",
interval: "INVALID_INTERVAL", // Type error: not a valid SignalInterval
getSignal: async () => null,
});
// Throws in StrategyValidationService.addStrategy() before reaching schema service
Schema services themselves do not perform validation—they assume incoming schemas have already been validated.
Each schema service provides a method to list all registered names:
// List all registered strategies
const strategies: StrategyName[] = backtest.strategySchemaService.list();
// Returns: ["strategy-v1", "strategy-v2", "strategy-v3"]
// List all registered exchanges
const exchanges: ExchangeName[] = backtest.exchangeSchemaService.list();
// Returns: ["binance", "coinbase", "kraken"]
This is useful for:
The framework also exposes public list* functions that wrap schema service list methods:
| Public Function | Schema Service Method | Return Type |
|---|---|---|
listStrategies() |
strategySchemaService.list() |
StrategyName[] |
listExchanges() |
exchangeSchemaService.list() |
ExchangeName[] |
listFrames() |
frameSchemaService.list() |
FrameName[] |
listWalkers() |
walkerSchemaService.list() |
WalkerName[] |
listSizings() |
sizingSchemaService.list() |
SizingName[] |
listRisks() |
riskSchemaService.list() |
RiskName[] |
listOptimizers() |
optimizerSchemaService.list() |
OptimizerName[] |
Schema services are designed for high-performance retrieval:
| Operation | Time Complexity | Space Complexity |
|---|---|---|
register(name, schema) |
O(1) | O(1) per schema |
get(name) |
O(1) | O(1) |
list() |
O(n) where n = schema count | O(n) |
has(name) |
O(1) | O(1) |
Schema services are singleton instances created once at application startup. All add* calls modify the same service instance, ensuring:
Schema services store configurations only in memory. Schemas are lost on process restart. This design is intentional because:
getSignal, getCandles functions)import { addStrategy } from "backtest-kit";
addStrategy({
strategyName: "momentum-long",
interval: "5m",
getSignal: async (symbol, when) => {
// Strategy logic here
return {
position: "long",
priceTakeProfit: 51000,
priceStopLoss: 49000,
minuteEstimatedTime: 60,
};
},
});
// Initial registration
addStrategy({
strategyName: "v1",
interval: "5m",
getSignal: async () => ({ /* v1 logic */ }),
});
// Later: Update strategy logic
addStrategy({
strategyName: "v1", // Same name = override
interval: "3m", // Can change any field
getSignal: async () => ({ /* v2 logic */ }),
});
// All future executions use updated schema
const strategies = [
{ name: "conservative", risk: 1 },
{ name: "aggressive", risk: 5 },
];
strategies.forEach(({ name, risk }) => {
addStrategy({
strategyName: name,
interval: "5m",
riskName: risk > 2 ? "high-risk" : "low-risk",
getSignal: async () => ({ /* ... */ }),
});
});
Schema Services interact with multiple service layers:
| Service Layer | Relationship | Direction |
|---|---|---|
| Validation Services | Schemas are validated before storage | Validation → Schema |
| Connection Services | Schemas are retrieved to create clients | Schema → Connection |
| Global Services | Global services orchestrate validation + storage | Public API → Global → Schema |
| Core Services | Core services use connection services (which use schemas) | Schema → Connection → Core |
| Command Services | Commands retrieve schemas via connection services | Schema → Connection → Command |
Schema Services are pure storage with no business logic. They do not:
Schema Services implement the ToolRegistry pattern to provide centralized, in-memory storage for component configurations. They offer:
register(name, schema) and get(name) patternSchema Services serve as the configuration registry that bridges the public API (add* functions) with the internal service architecture (connection/core/command layers).