This page documents the IStrategySchema interface, which defines the configuration structure for trading strategies in backtest-kit. Strategy schemas specify signal generation logic, throttling intervals, lifecycle callbacks, and optional risk management integration.
For information about risk management configuration, see Risk Schemas. For exchange data source configuration, see Exchange Schemas. For strategy execution patterns, see ClientStrategy.
The IStrategySchema interface defines the complete configuration for a trading strategy. When registered via addStrategy(), the schema is stored in StrategySchemaService and validated by StrategyValidationService.
Type: StrategyName (string)
Unique identifier for the strategy. Used throughout the framework for:
StrategySchemaServiceMethodContextServicesignal-{strategyName}-{symbol}.json)strategyName:symbol)The strategy name must be unique across all registered strategies. Multiple calls to addStrategy() with the same strategyName will overwrite the previous registration.
Type: SignalInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h"
Minimum time interval between getSignal() invocations. Implements throttling to prevent signal spam and reduce computational overhead.
The framework enforces this interval using _lastSignalTimestamp tracking in ClientStrategy. When tick() is called, the framework checks elapsed time since last signal generation:
Interval Mappings (INTERVAL_MINUTES):
| Interval | Minutes | Use Case |
|---|---|---|
1m |
1 | High-frequency strategies, live monitoring |
3m |
3 | Short-term scalping strategies |
5m |
5 | Standard intraday strategies |
15m |
15 | Medium-term swing strategies |
30m |
30 | Longer-term position strategies |
1h |
60 | Low-frequency strategies |
Type: (symbol: string) => Promise<ISignalDto | null>
Async function that generates trading signals. Called by ClientStrategy.tick() when:
stop()Return Values:
ISignalDto object: Creates new signal (opened or scheduled)null: No signal, remains in idle stateFunction Signature:
async getSignal(symbol: string): Promise<ISignalDto | null> {
// Access to:
// - symbol: trading pair (e.g., "BTCUSDT")
// - Implicit context via ExecutionContextService (when, backtest)
// - Exchange functions: getCandles(), getAveragePrice(), etc.
// Return signal or null
return {
position: "long",
priceOpen: 50000, // Optional: scheduled signal
priceTakeProfit: 51000, // Required
priceStopLoss: 49000, // Required
minuteEstimatedTime: 60, // Required
note: "MA crossover", // Optional
};
}
Context Availability:
The getSignal function executes within ExecutionContextService.runInContext(), providing implicit access to:
executionContext.whenexecutionContext.backtestgetCandles(symbol, interval, limit)formatPrice(symbol, price)The ISignalDto interface defines the structure returned by getSignal(). It is validated by VALIDATE_SIGNAL_FN before conversion to ISignalRow.
Field Details:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
No | UUID v4, auto-generated if not provided |
position |
"long" | "short" |
Yes | Trade direction |
note |
string |
No | Human-readable signal description |
priceOpen |
number |
No | Entry price. If provided, creates scheduled signal |
priceTakeProfit |
number |
Yes | Exit price for profit target |
priceStopLoss |
number |
Yes | Exit price for stop loss |
minuteEstimatedTime |
number |
Yes | Expected signal lifetime in minutes |
Scheduled vs Immediate Entry:
priceOpen provided: Signal enters scheduled state, waits for price activationpriceOpen omitted: Signal enters opened state immediately at current VWAPType: Partial<IStrategyCallbacks>
Lifecycle event handlers called during signal state transitions. All callbacks are optional.
Callback Signatures:
interface IStrategyCallbacks {
// Called on every tick with unified result
onTick: (symbol: string, result: IStrategyTickResult, backtest: boolean) => void;
// Called when signal opens (immediate entry)
onOpen: (symbol: string, data: ISignalRow, currentPrice: number, backtest: boolean) => void;
// Called when signal is monitoring TP/SL
onActive: (symbol: string, data: ISignalRow, currentPrice: number, backtest: boolean) => void;
// Called when no active signal exists
onIdle: (symbol: string, currentPrice: number, backtest: boolean) => void;
// Called when signal closes (TP/SL/time)
onClose: (symbol: string, data: ISignalRow, priceClose: number, backtest: boolean) => void;
// Called when scheduled signal is created
onSchedule: (symbol: string, data: IScheduledSignalRow, currentPrice: number, backtest: boolean) => void;
// Called when scheduled signal cancelled
onCancel: (symbol: string, data: IScheduledSignalRow, currentPrice: number, backtest: boolean) => void;
}
Execution Order:
onIdle, onOpen, onActive, onClose, onSchedule, onCancel)onTick callback (receives discriminated union result)Common Use Cases:
Type: RiskName (string, optional)
Reference to a registered risk profile via addRisk(). When specified, the strategy's signals are validated against the risk profile's constraints before opening positions.
Risk Integration Flow:
Cross-Strategy Position Tracking:
Multiple strategies sharing the same riskName share a single ClientRisk instance, enabling portfolio-level risk management:
addSignal() / removeSignal()Example:
// Register risk profile
addRisk({
riskName: "conservative",
validations: [
({ activePositionCount }) => {
if (activePositionCount >= 5) {
throw new Error("Max 5 concurrent positions");
}
}
]
});
// Register strategy with risk profile
addStrategy({
strategyName: "my-strategy",
riskName: "conservative", // Links to risk profile
// ... other fields
});
For detailed risk management configuration, see Risk Schemas.
Type: string (optional)
Developer documentation string. Used for:
Not used by framework logic, purely informational.
Strategies are registered via the addStrategy() function, which performs validation and storage in two services.
Registration Code Path:
addStrategy(schema) src/function/add.ts:50LoggerService.info() logs registration src/function/add.ts:51-53StrategyValidationService.addStrategy() stores for validation src/function/add.ts:54-57StrategySchemaService.register() stores in registry src/function/add.ts:58-61Connection Service Instantiation:
When strategy is first used (during Backtest.run() or Live.run()):
StrategyConnectionService.get(strategyName) resolves schemaClientStrategy instance with injected dependencies:
IStrategySchema (schema from registry)LoggerService (for debug output)ExecutionContextService (symbol, when, backtest)TExchangeGlobalService (exchange operations)TRiskGlobalService (risk checking, if riskName specified)The getSignal function has implicit access to exchange operations via context propagation. No explicit parameters needed.
Risk Check Flow:
ISignalDto from getSignal()riskName specified, ClientRisk.checkSignal() invokedactivePositionCount: Current open positionsactivePositions: Array of all active signalsparams: Signal details (symbol, price, timestamp)addSignal() calledremoveSignal() called to update portfolio stateimport { addStrategy, getCandles, getAveragePrice } from "backtest-kit";
addStrategy({
// Required: Unique identifier
strategyName: "sma-crossover",
// Optional: Documentation
note: "Simple moving average crossover strategy with 20/50 period EMAs",
// Required: Throttling interval
interval: "5m",
// Required: Signal generation logic
getSignal: async (symbol) => {
// Fetch historical data
const candles = await getCandles(symbol, "1h", 100);
// Calculate indicators
const sma20 = calculateSMA(candles, 20);
const sma50 = calculateSMA(candles, 50);
// Generate signal based on crossover
if (sma20 > sma50) {
const currentPrice = await getAveragePrice(symbol);
return {
position: "long",
priceTakeProfit: currentPrice * 1.02, // 2% profit target
priceStopLoss: currentPrice * 0.98, // 2% stop loss
minuteEstimatedTime: 240, // 4 hours
note: "SMA20 crossed above SMA50",
};
}
// No signal
return null;
},
// Optional: Lifecycle callbacks
callbacks: {
onOpen: (symbol, signal, price, backtest) => {
console.log(`[OPEN] ${symbol} @ ${price}`);
},
onClose: (symbol, signal, price, backtest) => {
console.log(`[CLOSE] ${symbol} @ ${price}`);
},
onTick: (symbol, result, backtest) => {
if (result.action === "closed") {
console.log(`PnL: ${result.pnl.pnlPercentage}%`);
}
},
},
// Optional: Link to risk profile
riskName: "conservative",
});
Type alias: string
Unique identifier for strategy schemas. Used as registry key in StrategySchemaService.
Type: "1m" | "3m" | "5m" | "15m" | "30m" | "1h"
Throttling intervals for signal generation. Enforced by ClientStrategy._lastSignalTimestamp tracking.
Extended version of ISignalDto with auto-generated fields:
id: UUID v4 (always present)exchangeName: From contextstrategyName: From contextscheduledAt: Creation timestamppendingAt: Activation timestampsymbol: Trading pair_isScheduled: Internal runtime flagUsed internally by ClientStrategy and emitted in callbacks.
Discriminated union of tick results:
IStrategyTickResultIdle: No signal, idle stateIStrategyTickResultScheduled: Scheduled signal createdIStrategyTickResultOpened: New signal openedIStrategyTickResultActive: Signal monitoring TP/SLIStrategyTickResultClosed: Signal closed with PnLIStrategyTickResultCancelled: Scheduled signal cancelledUse discriminant result.action for type-safe handling.