The Event System provides observability and monitoring capabilities for backtest-kit through a publish-subscribe architecture. It enables external code to react to strategy execution events, errors, and progress updates without coupling to internal execution logic.
This document covers event emitters, listener functions, queued processing, and event flow patterns. For signal lifecycle states, see Signal Lifecycle. For execution mode details, see Execution Modes. For callback configuration, see Strategy Schemas.
The event system uses Subject instances from functools-kit as event emitters. All listener functions wrap callbacks with the queued utility to ensure sequential processing even when callbacks are asynchronous.
Core Pattern:
Event Source → Subject.next() → Queued Listener → User Callback
Event Emitter Diagram
Signal events represent strategy execution state changes. Three emitters handle different execution contexts:
| Emitter | Purpose | Event Type |
|---|---|---|
signalEmitter |
All signal events (live + backtest) | IStrategyTickResult |
signalBacktestEmitter |
Backtest-only signals | IStrategyTickResult |
signalLiveEmitter |
Live-only signals | IStrategyTickResult |
The IStrategyTickResult discriminated union includes:
IStrategyTickResultIdle - No active positionIStrategyTickResultScheduled - Delayed entry signal createdIStrategyTickResultOpened - New position openedIStrategyTickResultActive - Position being monitoredIStrategyTickResultClosed - Position closed with PnLIStrategyTickResultCancelled - Scheduled signal cancelledCompletion events signal when background execution finishes:
| Emitter | Purpose | Event Type | Trigger |
|---|---|---|---|
doneBacktestSubject |
Backtest completion | DoneContract |
Backtest.background() ends |
doneLiveSubject |
Live trading completion | DoneContract |
Live.background() ends |
doneWalkerSubject |
Walker completion | DoneContract |
Walker.background() ends |
Progress events track execution advancement:
| Emitter | Purpose | Event Type | Frequency |
|---|---|---|---|
progressEmitter |
Backtest progress | ProgressContract |
Per timeframe batch |
walkerEmitter |
Walker strategy progress | WalkerContract |
Per strategy completion |
walkerCompleteSubject |
Walker final results | IWalkerResults |
Once at end |
| Emitter | Purpose | Event Type | Trigger |
|---|---|---|---|
errorEmitter |
Background execution errors | Error |
Caught exceptions in .background() |
validationSubject |
Risk validation failures | Error |
Risk validation function throws |
| Emitter | Purpose | Event Type | Usage |
|---|---|---|---|
performanceEmitter |
Timing metrics | PerformanceContract |
Profiling and bottleneck detection |
All listener functions follow a consistent pattern:
function listen*(fn: (event: EventType) => void): () => void
Returns an unsubscribe function to stop listening.
| Function | Emitter | Queued | Description |
|---|---|---|---|
listenSignal() |
signalEmitter |
Yes | All signal events |
listenSignalBacktest() |
signalBacktestEmitter |
Yes | Backtest signals only |
listenSignalLive() |
signalLiveEmitter |
Yes | Live signals only |
listenError() |
errorEmitter |
Yes | Background errors |
listenDoneBacktest() |
doneBacktestSubject |
Yes | Backtest completion |
listenDoneLive() |
doneLiveSubject |
Yes | Live completion |
listenDoneWalker() |
doneWalkerSubject |
Yes | Walker completion |
listenProgress() |
progressEmitter |
Yes | Backtest progress |
listenPerformance() |
performanceEmitter |
Yes | Performance metrics |
listenWalker() |
walkerEmitter |
Yes | Walker progress |
listenWalkerComplete() |
walkerCompleteSubject |
Yes | Walker final results |
listenValidation() |
validationSubject |
Yes | Risk validation errors |
Once listeners use .filter().once() pattern for one-time execution:
function listen*Once(
filterFn: (event: EventType) => boolean,
fn: (event: EventType) => void
): () => void
| Function | Emitter | Pattern |
|---|---|---|
listenSignalOnce() |
signalEmitter |
Filter + auto-unsubscribe |
listenSignalBacktestOnce() |
signalBacktestEmitter |
Filter + auto-unsubscribe |
listenSignalLiveOnce() |
signalLiveEmitter |
Filter + auto-unsubscribe |
listenDoneBacktestOnce() |
doneBacktestSubject |
Filter + auto-unsubscribe |
listenDoneLiveOnce() |
doneLiveSubject |
Filter + auto-unsubscribe |
listenDoneWalkerOnce() |
doneWalkerSubject |
Filter + auto-unsubscribe |
listenWalkerOnce() |
walkerEmitter |
Filter + auto-unsubscribe |
All continuous listeners wrap callbacks with queued() from functools-kit to ensure sequential execution:
export function listenSignal(fn: (event: IStrategyTickResult) => void) {
backtest.loggerService.log(LISTEN_SIGNAL_METHOD_NAME);
return signalEmitter.subscribe(queued(async (event) => fn(event)));
}
Guarantees:
Queued Processing Flow
Backtest Signal Event Flow
Key Emission Points:
BacktestLogicPrivateService.run() for each tick resultBacktest.background() finishesLive Signal Event Flow
Key Emission Points:
LiveLogicPrivateService.run() for each tick resultLive.background() stops and last position closesWalker Progress Event Flow
Key Emission Points:
Event payloads are defined as TypeScript interfaces:
interface DoneContract {
exchangeName: string;
strategyName: string;
backtest: boolean;
symbol: string;
}
Emitted by completion subjects (doneBacktestSubject, doneLiveSubject, doneWalkerSubject).
interface ProgressContract {
strategyName: string;
exchangeName: string;
frameName: string;
symbol: string;
processedFrames: number;
totalFrames: number;
progress: number; // 0-1 float
}
Emitted by progressEmitter during backtest execution.
interface PerformanceContract {
metricType: PerformanceMetricType;
duration: number; // milliseconds
strategyName?: string;
exchangeName?: string;
symbol?: string;
}
Emitted by performanceEmitter for profiling.
interface WalkerContract {
walkerName: string;
strategyName: string;
exchangeName: string;
frameName: string;
symbol: string;
strategiesTested: number;
totalStrategies: number;
metricValue: number;
bestStrategy: string;
bestMetric: number;
}
Emitted by walkerEmitter after each strategy completes.
Events are emitted by logic services and client classes:
BacktestLogicPrivateService:
// Emit signal events
await signalEmitter.next(result);
await signalBacktestEmitter.next(result);
// Emit progress
await progressEmitter.next({...});
Backtest.background():
// Emit completion
await doneBacktestSubject.next({
exchangeName: context.exchangeName,
strategyName: context.strategyName,
backtest: true,
symbol,
});
Basic Listener:
import { listenSignalBacktest } from "backtest-kit";
const unsubscribe = listenSignalBacktest((event) => {
if (event.action === "closed") {
console.log(`PnL: ${event.pnl.pnlPercentage}%`);
}
});
// Later: stop listening
unsubscribe();
Once Listener:
import { listenDoneBacktestOnce } from "backtest-kit";
listenDoneBacktestOnce(
(event) => event.symbol === "BTCUSDT",
(event) => {
console.log("BTCUSDT backtest completed");
}
);
Error Handling:
import { listenError } from "backtest-kit";
listenError((error) => {
console.error("Background error:", error.message);
// Send to monitoring service
});
Markdown services automatically subscribe to event emitters to accumulate data for report generation:
BacktestMarkdownService:
signalBacktestEmitterLiveMarkdownService:
signalLiveEmitterWalkerMarkdownService:
walkerEmitter and walkerCompleteSubject| Event Type | Emitter | Listener | Contract | Queued | Once Variant |
|---|---|---|---|---|---|
| All signals | signalEmitter |
listenSignal() |
IStrategyTickResult |
Yes | Yes |
| Backtest signals | signalBacktestEmitter |
listenSignalBacktest() |
IStrategyTickResult |
Yes | Yes |
| Live signals | signalLiveEmitter |
listenSignalLive() |
IStrategyTickResult |
Yes | Yes |
| Backtest done | doneBacktestSubject |
listenDoneBacktest() |
DoneContract |
Yes | Yes |
| Live done | doneLiveSubject |
listenDoneLive() |
DoneContract |
Yes | Yes |
| Walker done | doneWalkerSubject |
listenDoneWalker() |
DoneContract |
Yes | Yes |
| Progress | progressEmitter |
listenProgress() |
ProgressContract |
Yes | No |
| Performance | performanceEmitter |
listenPerformance() |
PerformanceContract |
Yes | No |
| Walker progress | walkerEmitter |
listenWalker() |
WalkerContract |
Yes | Yes |
| Walker complete | walkerCompleteSubject |
listenWalkerComplete() |
IWalkerResults |
Yes | No |
| Errors | errorEmitter |
listenError() |
Error |
Yes | No |
| Validation | validationSubject |
listenValidation() |
Error |
Yes | No |