This page explains how to register components in backtest-kit using the add* family of functions. Component registration is the first step in using the frameworkâyou define strategies, exchanges, frames, risk profiles, sizing configurations, and walkers before running backtests or live trading.
For information about the structure and properties of each component type, see Component Types. For details on how registered components are instantiated during execution, see Connection Services.
The framework supports six types of components that can be registered:
| Component Type | Add Function | Purpose | Required For |
|---|---|---|---|
| Strategy | addStrategy() |
Signal generation logic and lifecycle callbacks | Backtest, Live |
| Exchange | addExchange() |
Market data source and price/quantity formatting | Backtest, Live, Walker |
| Frame | addFrame() |
Backtest timeframe generation (start/end dates, interval) | Backtest, Walker |
| Risk | addRisk() |
Portfolio-level risk management and custom validations | Optional (strategy-level) |
| Sizing | addSizing() |
Position size calculation methods | Optional (strategy-level) |
| Walker | addWalker() |
Multi-strategy comparison configuration | Walker mode only |
Each component is identified by a unique name (strategyName, exchangeName, etc.) and stored in a corresponding schema service.
All registration functions follow the same pattern: accept a schema object and store it in the framework's internal registry.
Diagram: Registration Function Flow
Registers a trading strategy with signal generation logic and lifecycle callbacks.
Parameters:
strategyName: Unique identifier (string)interval: Signal generation throttle interval (SignalInterval)getSignal: Async function returning ISignalDto | nullcallbacks: Optional lifecycle hooks (onTick, onOpen, onClose, onSchedule, onCancel)riskName: Optional risk profile name to usesizingName: Optional sizing configuration name to useExample:
addStrategy({
strategyName: "momentum-breakout",
interval: "5m",
getSignal: async (symbol) => ({
position: "long",
priceOpen: 50000,
priceTakeProfit: 51000,
priceStopLoss: 49000,
minuteEstimatedTime: 60,
}),
riskName: "conservative",
callbacks: {
onOpen: (symbol, signal, price, backtest) => {
console.log(`[${symbol}] Signal opened at ${price}`);
},
},
});
Registers a market data source with candle fetching and formatting functions.
Parameters:
exchangeName: Unique identifier (string)getCandles: Async function fetching ICandleData[]formatPrice: Async function formatting prices for exchange precisionformatQuantity: Async function formatting quantities for exchange precisioncallbacks: Optional onCandleData callbackExample:
addExchange({
exchangeName: "binance",
getCandles: async (symbol, interval, since, limit) => {
// Fetch from API or database
return [...];
},
formatPrice: async (symbol, price) => price.toFixed(2),
formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
});
Registers a backtest timeframe with start/end dates and interval.
Parameters:
frameName: Unique identifier (string)interval: Timeframe granularity (FrameInterval)startDate: Backtest period start (Date)endDate: Backtest period end (Date)callbacks: Optional onTimeframe callbackExample:
addFrame({
frameName: "2024-q1",
interval: "1m",
startDate: new Date("2024-01-01T00:00:00Z"),
endDate: new Date("2024-03-31T23:59:59Z"),
});
Registers a risk management profile with custom validations.
Parameters:
riskName: Unique identifier (string)validations: Array of IRiskValidation or IRiskValidationFncallbacks: Optional onRejected and onAllowed callbacksExample:
addRisk({
riskName: "conservative",
validations: [
{
validate: async ({ activePositionCount }) => {
if (activePositionCount >= 5) {
throw new Error("Max 5 concurrent positions");
}
},
note: "Portfolio-level position limit",
},
],
});
Registers a position sizing configuration (fixed-percentage, kelly-criterion, or atr-based).
Parameters:
sizingName: Unique identifier (string)method: Sizing method discriminatorExample:
addSizing({
sizingName: "fixed-1pct",
method: "fixed-percentage",
riskPercentage: 1,
maxPositionPercentage: 10,
});
Registers a walker for multi-strategy comparison.
Parameters:
walkerName: Unique identifier (string)exchangeName: Exchange to use for all backtestsframeName: Frame to use for all backtestsstrategies: Array of strategy names to comparemetric: Optimization metric (WalkerMetric)Example:
addWalker({
walkerName: "strategy-optimizer",
exchangeName: "binance",
frameName: "2024-q1",
strategies: ["momentum-v1", "momentum-v2", "momentum-v3"],
metric: "sharpeRatio",
});
When a component is registered via an add* function, the framework performs two operations:
Diagram: Registration Sequence
Each add* function follows this pattern in src/function/add.ts:50-341:
export function addStrategy(strategySchema: IStrategySchema) {
// 1. Log registration
backtest.loggerService.info(ADD_STRATEGY_METHOD_NAME, {
strategySchema,
});
// 2. Validate schema
backtest.strategyValidationService.addStrategy(
strategySchema.strategyName,
strategySchema
);
// 3. Store in registry
backtest.strategySchemaService.register(
strategySchema.strategyName,
strategySchema
);
}
Registered schemas are stored in schema services that follow the ToolRegistry pattern. Each component type has a dedicated schema service:
Diagram: Schema Service Architecture
The dependency injection configuration is defined in:
Schema services use the ToolRegistry pattern for name-based storage and retrieval:
| Method | Purpose |
|---|---|
register(name, schema) |
Store schema by unique name |
get(name) |
Retrieve schema by name (throws if not found) |
has(name) |
Check if schema exists |
list() |
Get all registered schemas |
This pattern enables:
Each component type has a corresponding validation service that performs schema validation during registration:
Diagram: Validation Layer
Validation services are bound in the DI container:
Validation services use memoization to cache validation results per component name. This ensures validation only runs once per component, even if the schema is retrieved multiple times during execution.
The framework provides list* functions for runtime introspection of registered components:
| Function | Returns | Purpose |
|---|---|---|
listStrategies() |
Promise<IStrategySchema[]> |
All registered strategies |
listExchanges() |
Promise<IExchangeSchema[]> |
All registered exchanges |
listFrames() |
Promise<IFrameSchema[]> |
All registered frames |
listRisks() |
Promise<IRiskSchema[]> |
All registered risk profiles |
listSizings() |
Promise<ISizingSchema[]> |
All registered sizing configs |
listWalkers() |
Promise<IWalkerSchema[]> |
All registered walkers |
Example:
import { addStrategy, listStrategies } from "backtest-kit";
addStrategy({
strategyName: "momentum",
interval: "5m",
getSignal: async (symbol) => ({ /* ... */ }),
});
const strategies = await listStrategies();
console.log(strategies);
// [{ strategyName: "momentum", interval: "5m", ... }]
These functions delegate to the validation services' list() method, which returns all schemas stored in the registry.
The relationship between registration and execution follows this sequence:
Diagram: Registration to Execution Lifecycle
Key points:
add*): Schemas validated and storedBacktest.run or Live.run): Connection services retrieve schemas and create memoized client instancesFor details on client instantiation, see Connection Services. For execution orchestration, see Execution Modes.
All schema services and validation services are bound using Symbol-based tokens in the DI container. This prevents naming collisions and provides type safety:
Diagram: Symbol-Based DI Token Flow
The complete DI setup is defined across three files: