This page documents the interval throttling system in backtest-kit, which prevents excessive getSignal() calls by enforcing minimum time delays between signal generation attempts. Interval throttling ensures strategies execute at defined timeframes (e.g., every 5 minutes) rather than on every tick, reducing computational overhead and aligning signal generation with strategy timeframes.
For information about the signal lifecycle after generation, see Signal Lifecycle Overview. For details on real-time monitoring loops, see Real-time Monitoring.
backtest-kit supports six predefined throttling intervals, each enforcing a minimum time between getSignal() invocations:
| Interval Value | Duration (Minutes) | Duration (Milliseconds) | Typical Use Case |
|---|---|---|---|
"1m" |
1 | 60,000 | High-frequency scalping, rapid signal updates |
"3m" |
3 | 180,000 | Short-term momentum strategies |
"5m" |
5 | 300,000 | Common intraday trading interval |
"15m" |
15 | 900,000 | Medium-term intraday strategies |
"30m" |
30 | 1,800,000 | Swing trading, position management |
"1h" |
60 | 3,600,000 | Long-term position strategies |
Diagram: Interval Throttling Decision Flow
The throttling check occurs in GET_SIGNAL_FN before invoking the user-defined getSignal() function:
// From ClientStrategy.ts:336-352
const currentTime = self.params.execution.context.when.getTime();
{
const intervalMinutes = INTERVAL_MINUTES[self.params.interval];
const intervalMs = intervalMinutes * 60 * 1000;
// Check if enough time has passed since last getSignal
if (
self._lastSignalTimestamp !== null &&
currentTime - self._lastSignalTimestamp < intervalMs
) {
return null;
}
self._lastSignalTimestamp = currentTime;
}
Key Implementation Points:
ExecutionContextService.context.when for temporal consistency_lastSignalTimestamp starts as null, allowing immediate first executionnull immediately if throttled, preventing unnecessary computation_lastSignalTimestamp only after throttle check passesThe INTERVAL_MINUTES constant maps SignalInterval string values to numeric minute durations:
| Constant | File Location | Type |
|---|---|---|
INTERVAL_MINUTES |
src/client/ClientStrategy.ts:34-41 | Record<SignalInterval, number> |
Strategies specify their throttling interval via the interval field in IStrategySchema:
// From interfaces/Strategy.interface.ts
export interface IStrategySchema {
strategyName: StrategyName;
interval: SignalInterval; // Throttling interval
getSignal: (symbol: string, when: Date) => Promise<ISignalDto | null>;
// ... other fields
}
// Example from README.md
addStrategy({
strategyName: 'llm-strategy',
interval: '5m', // getSignal() called at most every 5 minutes
getSignal: async (symbol) => {
// Strategy logic here
},
});
In backtest mode, ClientStrategy.tick() is called once per candle iteration. The throttle ensures getSignal() only executes when the time difference between candles exceeds the configured interval:
Diagram: Interval Throttling in Backtest Mode
In live mode, ClientStrategy.tick() is called continuously in a loop with TICK_TTL sleep intervals (typically 1 minute). The throttle prevents redundant getSignal() executions between meaningful time boundaries:
Diagram: Interval Throttling in Live Mode Loop
Each ClientStrategy instance maintains its own _lastSignalTimestamp:
// From ClientStrategy.ts class definition
export class ClientStrategy implements IStrategy {
private _lastSignalTimestamp: number | null = null;
private _isStopped = false;
private _pendingSignal: ISignalRow | null = null;
private _scheduledSignal: IScheduledSignalRow | null = null;
// ...
}
Instance Isolation Guarantees:
symbol:strategyName:backtest| Component | Relationship | File Reference |
|---|---|---|
IStrategySchema |
Defines interval configuration |
src/interfaces/Strategy.interface.ts:132-151 |
StrategyConnectionService |
Passes interval to ClientStrategy constructor |
src/lib/services/connection/StrategyConnectionService.ts:123-156 |
ExecutionContextService |
Provides context.when for time source |
src/lib/services/context/ExecutionContextService.ts |
GET_SIGNAL_FN |
Implements throttle logic wrapper | src/client/ClientStrategy.ts:332-476 |
| Strategy Type | Recommended Interval | Rationale |
|---|---|---|
| Scalping | "1m" |
High-frequency entries, rapid market changes |
| Day Trading | "5m" or "15m" |
Balance between responsiveness and noise reduction |
| Swing Trading | "30m" or "1h" |
Longer-term positions, reduced false signals |
| Position Trading | "1h" |
Weekly/monthly timeframes, minimal overhead |
Longer intervals reduce:
getSignal() invocations mean fewer expensive LLM requestsgetCandles() fetches (when using live data sources)The throttle compares timestamps from two sources:
Current Time: ExecutionContextService.context.when.getTime()
Date.now() from system clockLast Signal Time: _lastSignalTimestamp (stored in milliseconds)
The throttle uses strict inequality (<), meaning:
currentTime - lastSignalTimestamp === intervalMs, the throttle allows executioninterval='5m' and lastSignalTimestamp=00:00:00, the signal at 00:05:00 will execute (not throttled)The throttle mechanism is validated by multiple test suites:
| Test Suite | Focus | File Reference |
|---|---|---|
timing.test.mjs |
Interval timing accuracy | test/e2e/timing.test.mjs |
defend.test.mjs |
Edge cases (boundary conditions, multiple signals) | test/e2e/defend.test.mjs |
scheduled.test.mjs |
Throttle with scheduled signals | test/e2e/scheduled.test.mjs |
getSignal() is called (throttling)These are independent parameters. A strategy with interval='5m' can have minuteEstimatedTime=120 (2 hours).
In live mode with interval='5m', even though tick() runs every minute, getSignal() only executes every 5 minutes. Price checks for active signals still occur every minute (TP/SL monitoring), but new signal generation is throttled.
Interval throttling in backtest-kit enforces minimum time delays between getSignal() calls, preventing excessive strategy evaluations and aligning signal generation with meaningful timeframes. The system uses per-instance _lastSignalTimestamp tracking with temporal consistency via ExecutionContextService, ensuring identical behavior across backtest and live modes while maintaining symbol-strategy isolation.