This page documents the Walker API for multi-strategy comparison. Walker orchestrates multiple backtest runs across different strategies and ranks them by performance metrics.
For information about running individual backtests, see Backtest API. For information about walker schemas and registration, see Walker Schemas.
The Walker API provides functionality to compare multiple trading strategies in parallel by running backtests for each strategy and ranking them by a selected metric (e.g., Sharpe Ratio, win rate, total PNL). Walker is implemented as a singleton utility class that wraps WalkerGlobalService with simplified method signatures.
Key Capabilities:
The Walker singleton provides five main methods for strategy comparison operations.
Runs walker comparison for a symbol with async generator iteration. Yields progress updates after each strategy completes.
Signature:
Walker.run(
symbol: string,
context: {
walkerName: string;
}
): AsyncGenerator<WalkerContract>
Parameters:
symbol - Trading pair symbol (e.g., "BTCUSDT")context.walkerName - Walker schema name registered via addWalker()Returns: Async generator yielding progress updates after each strategy completion
Behavior:
WalkerGlobalService.run() which calls WalkerLogicPublicService.run()Progress Event Structure:
interface WalkerContract {
strategiesTested: number;
totalStrategies: number;
bestStrategy: string;
bestMetric: number;
strategyName: string;
metricValue: number;
}
Runs walker comparison in background without yielding results. Consumes all progress updates internally. Returns cancellation function.
Signature:
Walker.background(
symbol: string,
context: {
walkerName: string;
}
): () => void
Parameters:
symbol - Trading pair symbol (e.g., "BTCUSDT")context.walkerName - Walker schema nameReturns: Cancellation function that stops all strategy backtests
Behavior:
Walker.run()doneWalkerSubject when all strategies completeerrorEmitterGets walker results data with all strategy comparison metrics.
Signature:
Walker.getData(
symbol: string,
walkerName: WalkerName
): Promise<IWalkerResults>
Parameters:
symbol - Trading symbol used in comparisonwalkerName - Walker schema nameReturns: Promise resolving to walker results data structure
Result Structure:
interface IWalkerResults {
bestStrategy: string;
bestMetric: number;
strategies: Array<{
strategyName: string;
stats: BacktestStatistics;
metric: number;
}>;
}
Behavior:
WalkerMarkdownService.getData() with symbol, walkerName, metric, and contextGenerates markdown report with all strategy comparisons.
Signature:
Walker.getReport(
symbol: string,
walkerName: WalkerName
): Promise<string>
Parameters:
symbol - Trading symbolwalkerName - Walker schema nameReturns: Promise resolving to markdown formatted report string
Report Format:
Saves walker report to disk as markdown file.
Signature:
Walker.dump(
symbol: string,
walkerName: WalkerName,
path?: string
): Promise<void>
Parameters:
symbol - Trading symbolwalkerName - Walker schema namepath - Optional directory path (default: "./logs/walker")Behavior:
Walker.getReport(){path}/{walkerName}.mdThe following diagram shows how Walker orchestrates multiple backtest runs and aggregates results.
Key Points:
MethodContextService.runAsyncIterator for context propagationWalker provides specialized event listeners for monitoring comparison progress and completion.
Subscribes to walker progress events with queued async processing. Emits after each strategy completes.
Signature:
listenWalker(fn: (event: WalkerContract) => void): () => void
Event Structure:
interface WalkerContract {
strategiesTested: number;
totalStrategies: number;
bestStrategy: string;
bestMetric: number;
strategyName: string;
metricValue: number;
}
Returns: Unsubscribe function
Subscribes to filtered walker progress events with one-time execution. Useful for waiting for specific conditions.
Signature:
listenWalkerOnce(
filterFn: (event: WalkerContract) => boolean,
fn: (event: WalkerContract) => void
): () => void
Parameters:
filterFn - Predicate to filter which events trigger the callbackfn - Callback function (called only once when filter matches)Returns: Unsubscribe function to cancel before it fires
Subscribes to walker completion events with final results. Emits when all strategies have been tested.
Signature:
listenWalkerComplete(fn: (results: IWalkerResults) => void): () => void
Event Structure:
interface IWalkerResults {
bestStrategy: string;
bestMetric: number;
strategies: Array<{
strategyName: string;
stats: BacktestStatistics;
metric: number;
}>;
}
Returns: Unsubscribe function
Subscribes to walker background execution completion events. Emits when Walker.background() completes.
Signature:
listenDoneWalker(fn: (event: DoneContract) => void): () => void
Event Structure:
interface DoneContract {
exchangeName: string;
strategyName: string; // Actually walkerName
backtest: boolean; // Always true for walker
symbol: string;
}
Returns: Unsubscribe function
Subscribes to filtered walker background completion events with one-time execution.
Signature:
listenDoneWalkerOnce(
filterFn: (event: DoneContract) => boolean,
fn: (event: DoneContract) => void
): () => void
Parameters:
filterFn - Predicate to filter eventsfn - Callback function (called only once)Returns: Unsubscribe function
The following diagram shows the event emission sequence during walker execution.
Event Timing:
walkerEmitter - Emits after each strategy completes (N times for N strategies)walkerCompleteSubject - Emits once when all strategies completedoneWalkerSubject - Emits only for Walker.background() after completionimport { addWalker, Walker, listenWalker } from "backtest-kit";
// Register walker schema
addWalker({
walkerName: "btc-walker",
exchangeName: "binance",
frameName: "1d-backtest",
strategies: ["strategy-a", "strategy-b", "strategy-c"],
metric: "sharpeRatio",
callbacks: {
onStrategyStart: (strategyName, symbol) => {
console.log(`Starting: ${strategyName}`);
},
onStrategyComplete: (strategyName, symbol, stats) => {
console.log(`Completed: ${strategyName} - Sharpe: ${stats.sharpeRatio}`);
},
onComplete: (results) => {
console.log(`Best: ${results.bestStrategy}`);
},
},
});
// Listen to progress
listenWalker((event) => {
console.log(`Progress: ${event.strategiesTested}/${event.totalStrategies}`);
console.log(`Best so far: ${event.bestStrategy} (${event.bestMetric})`);
console.log(`Current: ${event.strategyName} (${event.metricValue})`);
});
// Run walker
for await (const progress of Walker.run("BTCUSDT", {
walkerName: "btc-walker"
})) {
// Progress available here too
console.log(`Tested ${progress.strategiesTested} strategies`);
}
// Get final results
const results = await Walker.getData("BTCUSDT", "btc-walker");
console.log("Best strategy:", results.bestStrategy);
console.log("Best metric:", results.bestMetric);
// Generate report
const markdown = await Walker.getReport("BTCUSDT", "btc-walker");
console.log(markdown);
// Save to disk
await Walker.dump("BTCUSDT", "btc-walker"); // ./logs/walker/btc-walker.md
import { Walker, listenWalkerComplete, listenDoneWalker } from "backtest-kit";
// Listen to completion
listenWalkerComplete((results) => {
console.log("Walker completed!");
console.log("Best strategy:", results.bestStrategy);
console.log("Best metric:", results.bestMetric);
// Access all strategy results
results.strategies.forEach(({ strategyName, stats, metric }) => {
console.log(`${strategyName}: ${metric}`);
});
});
// Listen to background task done
listenDoneWalker((event) => {
console.log("Background walker done:", event.strategyName);
// Auto-save report
Walker.dump(event.symbol, event.strategyName);
});
// Run in background
const cancel = Walker.background("BTCUSDT", {
walkerName: "btc-walker"
});
// Later: cancel if needed
// cancel();
import { addWalker, Walker } from "backtest-kit";
// Walker comparing by Sharpe Ratio (risk-adjusted returns)
addWalker({
walkerName: "walker-sharpe",
exchangeName: "binance",
frameName: "1d-backtest",
strategies: ["strategy-a", "strategy-b", "strategy-c"],
metric: "sharpeRatio", // Default, best for risk-adjusted comparison
});
// Walker comparing by win rate
addWalker({
walkerName: "walker-winrate",
exchangeName: "binance",
frameName: "1d-backtest",
strategies: ["strategy-a", "strategy-b", "strategy-c"],
metric: "winRate", // Best for consistency
});
// Walker comparing by total PNL
addWalker({
walkerName: "walker-totalpnl",
exchangeName: "binance",
frameName: "1d-backtest",
strategies: ["strategy-a", "strategy-b", "strategy-c"],
metric: "totalPnl", // Best for absolute returns
});
// Walker comparing by certainty ratio (avgWin / |avgLoss|)
addWalker({
walkerName: "walker-certainty",
exchangeName: "binance",
frameName: "1d-backtest",
strategies: ["strategy-a", "strategy-b", "strategy-c"],
metric: "certaintyRatio", // Best for risk/reward profile
});
// Run each walker
for await (const _ of Walker.run("BTCUSDT", { walkerName: "walker-sharpe" })) {}
for await (const _ of Walker.run("BTCUSDT", { walkerName: "walker-winrate" })) {}
for await (const _ of Walker.run("BTCUSDT", { walkerName: "walker-totalpnl" })) {}
for await (const _ of Walker.run("BTCUSDT", { walkerName: "walker-certainty" })) {}
// Compare results
const sharpeBest = await Walker.getData("BTCUSDT", "walker-sharpe");
const winrateBest = await Walker.getData("BTCUSDT", "walker-winrate");
const pnlBest = await Walker.getData("BTCUSDT", "walker-totalpnl");
const certaintyBest = await Walker.getData("BTCUSDT", "walker-certainty");
console.log("Best by Sharpe:", sharpeBest.bestStrategy);
console.log("Best by Win Rate:", winrateBest.bestStrategy);
console.log("Best by Total PNL:", pnlBest.bestStrategy);
console.log("Best by Certainty:", certaintyBest.bestStrategy);
import { Walker, listenWalker } from "backtest-kit";
let strategiesTested = 0;
listenWalker((event) => {
strategiesTested = event.strategiesTested;
});
// Run walker in background
const cancel = Walker.background("BTCUSDT", {
walkerName: "btc-walker"
});
// Cancel after 10 seconds (or based on some condition)
setTimeout(() => {
cancel();
console.log(`Cancelled after testing ${strategiesTested} strategies`);
}, 10000);
// Or cancel based on condition
listenWalkerOnce(
(event) => event.strategiesTested >= 5,
(event) => {
cancel();
console.log("Cancelled after 5 strategies");
}
);
The data structure returned by Walker.getData() and emitted by walkerCompleteSubject.
interface IWalkerResults {
/** Name of the best performing strategy */
bestStrategy: string;
/** Metric value for the best strategy */
bestMetric: number;
/** Array of all tested strategies with their results */
strategies: Array<{
/** Strategy name */
strategyName: string;
/** Complete backtest statistics for this strategy */
stats: BacktestStatistics;
/** Extracted metric value used for comparison */
metric: number;
}>;
}
BacktestStatistics Structure:
interface BacktestStatistics {
signalList: IStrategyTickResultClosed[];
totalSignals: number;
winCount: number;
lossCount: number;
winRate: number | null;
avgPnl: number | null;
totalPnl: number | null;
stdDev: number | null;
sharpeRatio: number | null;
annualizedSharpeRatio: number | null;
certaintyRatio: number | null;
expectedYearlyReturns: number | null;
}
Available Metrics for Comparison:
sharpeRatio - Risk-adjusted return (avgPnl / stdDev)winRate - Win percentage (0-100)avgPnl - Average PNL percentage per tradetotalPnl - Cumulative PNL percentagecertaintyRatio - avgWin / |avgLoss|The following diagram shows how Walker integrates with the service layer.
Service Responsibilities:
WalkerGlobalService - Validation orchestration and delegationWalkerLogicPublicService - Context wrapper (MethodContext)WalkerLogicPrivateService - Core loop through strategies arrayBacktestLogicPrivateService - Run each strategy's backtestBacktestMarkdownService - Collect results per strategyWalkerMarkdownService - Aggregate results and compute best strategy| Aspect | Walker.run() | Multiple Backtest.run() |
|---|---|---|
| Orchestration | Automatic sequential execution | Manual iteration required |
| Progress Tracking | Built-in progress events | Must implement manually |
| Best Strategy Selection | Automatic based on metric | Must compare manually |
| Report Generation | Unified comparison report | Individual reports only |
| Context Propagation | Single walker context | Separate context per strategy |
| Data Clearing | Automatic clear before run | Must clear manually |
| Metric Extraction | Automatic based on metric config | Must extract manually |
| Event Emission | Walker-specific events | Backtest-specific events |
When to Use Walker:
When to Use Individual Backtests:
Walker validates all schemas before starting execution. If any validation fails, an error is thrown immediately.
Validation Checks:
walkerValidationService.validate)exchangeValidationService.validate)frameValidationService.validate)strategyValidationService.validate)riskValidationService.validate)Background Execution Errors:
Errors in Walker.background() are caught and emitted to errorEmitter instead of throwing. Use listenError() to handle them.
import { Walker, listenError } from "backtest-kit";
listenError((error) => {
console.error("Walker error:", error.message);
// Handle error (log, alert, retry, etc.)
});
Walker.background("BTCUSDT", {
walkerName: "btc-walker"
});
Walker clears accumulated data before each run to ensure fresh comparison:
// From Walker.run()
backtest.walkerMarkdownService.clear(context.walkerName);
// Clear backtest data for all strategies
for (const strategyName of walkerSchema.strategies) {
backtest.backtestMarkdownService.clear(strategyName);
backtest.scheduleMarkdownService.clear(strategyName);
backtest.strategyGlobalService.clear(strategyName);
const { riskName } = backtest.strategySchemaService.get(strategyName);
riskName && backtest.riskGlobalService.clear(riskName);
}
This ensures: