This document describes the framework's error handling infrastructure, including error propagation channels, background task error management, risk validation error handling, and event-based error subscription patterns.
Error handling in backtest-kit is built on a pub-sub event system using Subject emitters from functools-kit. Errors are caught at execution boundaries and propagated to subscribers via two dedicated error channels: errorEmitter for general background execution failures and validationSubject for risk validation failures.
For information about the general event system architecture, see Event System. For risk validation logic, see Risk Validation. For logging configuration, see Logging System.
The framework provides two distinct error emitter channels, each handling specific error categories:
| Error Emitter | Purpose | Source Operations | Event Type |
|---|---|---|---|
errorEmitter |
General execution errors | Backtest.background(), Live.background(), Walker.background() |
Error |
validationSubject |
Risk validation failures | ClientRisk.checkSignal() validation function throws |
Error |
The errorEmitter handles errors from background execution tasks. When Backtest.background(), Live.background(), or Walker.background() encounter unhandled exceptions, they are caught and propagated through this channel.
Declaration: src/config/emitters.ts:28-31
export const errorEmitter = new Subject<Error>();
The validationSubject handles errors thrown by custom risk validation functions. When risk validation logic throws an error during signal checking, it is caught and emitted separately to allow targeted debugging of validation failures.
Declaration: src/config/emitters.ts:75-79
export const validationSubject = new Subject<Error>();
Background execution methods (*.background()) wrap async generators in try-catch blocks and propagate errors to errorEmitter. This ensures that errors in infinite loops or long-running backtests don't crash the process silently.
Backtest.background():
src/classes/Backtest.ts:114-117
task().catch((error) =>
errorEmitter.next(new Error(getErrorMessage(error)))
);
Live.background():
task().catch((error) =>
errorEmitter.next(new Error(getErrorMessage(error)))
);
Walker.background():
task().catch((error) =>
errorEmitter.next(new Error(getErrorMessage(error)))
);
All background tasks use getErrorMessage() from functools-kit to normalize error objects into consistent Error instances with string messages. This handles cases where thrown values are not Error objects.
Diagram: Background Task Error Propagation
This diagram shows how errors in background execution are caught, normalized, and propagated to user error handlers through errorEmitter.
Risk validation errors are handled separately from general execution errors. When custom validation functions throw errors during ClientRisk.checkSignal(), they are caught and emitted to validationSubject while allowing execution to continue.
Risk validation functions are user-defined and can throw errors to reject signals. These errors are not considered fatal execution errors but rather validation rejections that should be logged and monitored.
The framework catches validation errors at the risk checking boundary and:
validationSubject for debugging/monitoringfalse from checkSignal() to reject the signalThis pattern enables strategies to continue running even when validation logic has issues, while still providing visibility into validation failures.
The framework provides two listener functions for subscribing to error events with queued async processing.
Subscribes to general background execution errors with sequential processing.
Function Signature: src/function/event.ts:232-235
export function listenError(fn: (error: Error) => void)
Parameters:
fn: Callback function receiving Error objects from background task failuresReturns: Unsubscribe function to stop listening
Usage Example:
import { listenError, Backtest } from "backtest-kit";
const unsubscribe = listenError((error) => {
console.error("Background execution error:", error.message);
// Send to monitoring service
reportToSentry(error);
});
Backtest.background("BTCUSDT", {
strategyName: "my-strategy",
exchangeName: "binance",
frameName: "1d-backtest"
});
// Later: stop listening
unsubscribe();
Subscribes to risk validation errors with sequential processing.
Function Signature: src/function/event.ts:649-652
export function listenValidation(fn: (error: Error) => void)
Parameters:
fn: Callback function receiving Error objects from validation function throwsReturns: Unsubscribe function to stop listening
Usage Example:
import { listenValidation } from "backtest-kit";
const unsubscribe = listenValidation((error) => {
console.error("Risk validation error:", error.message);
// Debug validation logic issues
if (error.message.includes("portfolio limit")) {
console.warn("Portfolio limit validation failing");
}
});
// Later: stop listening
unsubscribe();
Both error listeners use queued() wrapper from functools-kit to ensure errors are processed sequentially even if callbacks are async. This prevents race conditions in error logging or reporting.
src/function/event.ts:234 and src/function/event.ts:651
return errorEmitter.subscribe(queued(async (error) => fn(error)));
return validationSubject.subscribe(queued(async (error) => fn(error)));
Diagram: Error Handling System Architecture
This diagram shows the complete error handling pipeline from error sources through capture, normalization, emission, subscription, and user handling.
Diagram: Error Handling Code Entity Mapping
This diagram maps error types to their corresponding code entities, showing where errors originate and how they flow through specific functions and classes.
Error handling complements but does not replace the logging system. While errors are emitted through dedicated channels, the framework's logging interface provides a warn() method for non-fatal issues that don't require error propagation.
The ILogger interface includes a warn() method for recording potentially problematic situations that don't prevent execution:
warn(topic: string, ...args: any[]): void;
Use Cases:
| Condition | Handler | Propagation |
|---|---|---|
| Background task throws | errorEmitter.next() |
Yes, to listenError() subscribers |
| Validation function throws | validationSubject.next() |
Yes, to listenValidation() subscribers |
| Recoverable issue | logger.warn() |
No, logged only |
| Fatal issue requiring stop | throw new Error() |
Yes, caught by background task wrapper |
Diagram: Error Handling Sequence
This sequence diagram illustrates the temporal flow of error handling from background task failure through emission and queued processing in user handlers.
All error handling components are exported from the main module:
listenError,
listenValidation,
export * as emitters from "./config/emitters";
Users can access error emitters directly if needed:
import { emitters } from "backtest-kit";
emitters.errorEmitter.subscribe((error) => {
// Direct subscription without wrapper
});