This page documents the execution flow of Walker mode, which orchestrates multiple backtest runs to compare strategies and select the best performer. Walker iterates through a list of strategies, runs each through the same backtest configuration, collects performance metrics, and ranks results.
For information about individual backtest execution, see Backtest Execution Flow. For details on how strategies are compared and metrics calculated, see Strategy Comparison. For report generation, see Walker Reports.
Walker mode provides multi-strategy comparison by:
Walker execution is deterministic and reproducible since it uses the same frame (timeframe) and exchange for all strategies.
A walker is defined using IWalkerSchema which specifies:
| Field | Type | Description |
|---|---|---|
walkerName |
string |
Unique identifier for this walker |
exchangeName |
string |
Exchange to use for all strategies |
frameName |
string |
Timeframe to use for all backtests |
strategies |
string[] |
Array of strategy names to compare |
metric |
string (optional) |
Comparison metric (default: "sharpeRatio") |
callbacks |
object (optional) |
Event handlers for lifecycle events |
sharpeRatio - Risk-adjusted return (default)winRate - Win percentageavgPnl - Average PNL percentage per tradetotalPnl - Cumulative PNL percentagecertaintyRatio - Average win / |average loss|Walker execution flows through three service layers:
Before execution begins, Walker performs comprehensive validation:
src/classes/Walker.ts:50-59 validates the walker schema exists and is well-formed.
src/lib/services/global/WalkerGlobalService.ts:64-84 validates:
src/classes/Walker.ts:61-79 clears accumulated data for all strategies:
// Clear walker markdown data
backtest.walkerMarkdownService.clear(walkerName);
// Clear each strategy's data
for (const strategyName of strategies) {
backtest.backtestMarkdownService.clear(strategyName);
backtest.scheduleMarkdownService.clear(strategyName);
backtest.strategyGlobalService.clear(strategyName);
// Clear risk profile if used
const { riskName } = backtest.strategySchemaService.get(strategyName);
riskName && backtest.riskGlobalService.clear(riskName);
}
This ensures clean state for consistent comparisons.
The core of Walker execution is a sequential iteration through the strategies array:
Walker uses the same context management pattern as Backtest and Live modes:
The walker name, exchange name, and frame name are wrapped in a MethodContext:
// From Walker.run()
return backtest.walkerGlobalService.run(symbol, {
walkerName: context.walkerName,
exchangeName: walkerSchema.exchangeName,
frameName: walkerSchema.frameName,
});
This context flows through:
WalkerGlobalService.run() - Validates contextWalkerLogicPublicService.run() - Wraps with MethodContextService.runAsyncIterator()WalkerLogicPrivateService.run() - Core iteration logic with context accessFor each strategy, Walker creates a nested backtest context:
// For each strategy in walker.strategies
for await (const result of Backtest.run(symbol, {
strategyName: currentStrategy,
exchangeName: walkerSchema.exchangeName,
frameName: walkerSchema.frameName
})) {
// Backtest runs in its own context
}
This creates a "context sandwich":
Walker emits two types of events during execution:
Emitted via walkerEmitter after each strategy completes:
| Field | Type | Description |
|---|---|---|
walkerName |
string |
Walker identifier |
symbol |
string |
Trading pair |
strategyName |
string |
Strategy just completed |
strategiesTested |
number |
Count of strategies tested so far |
totalStrategies |
number |
Total strategies in walker |
bestStrategy |
string | null |
Current best strategy name |
bestMetric |
number |
Current best metric value |
metricValue |
number |
Metric value for completed strategy |
Emitted via walkerCompleteSubject when all strategies finish:
interface IWalkerResults {
bestStrategy: string | null;
bestMetric: number;
strategies: Array<{
strategyName: string;
stats: BacktestStatistics;
metric: number;
}>;
}
Users can subscribe to walker events using multiple approaches:
import { listenWalker } from "backtest-kit";
const unsubscribe = listenWalker((event) => {
console.log(`Progress: ${event.strategiesTested}/${event.totalStrategies}`);
console.log(`Best: ${event.bestStrategy} (${event.bestMetric})`);
console.log(`Current: ${event.strategyName} (${event.metricValue})`);
});
import { listenWalkerComplete } from "backtest-kit";
listenWalkerComplete((results) => {
console.log(`Best strategy: ${results.bestStrategy}`);
console.log(`Best metric: ${results.bestMetric}`);
results.strategies.forEach(s => {
console.log(`${s.strategyName}: ${s.metric}`);
});
});
import { listenWalkerOnce } from "backtest-kit";
// Wait for specific condition
listenWalkerOnce(
(event) => event.strategiesTested === 5,
(event) => console.log("5 strategies tested!")
);
Walker supports background execution with cancellation:
const stop = Walker.background("BTCUSDT", {
walkerName: "my-walker"
});
// Later: stop execution
stop();
src/classes/Walker.ts:108-143 implements cancellation:
isStopped flag to truestrategyGlobalService.stop() for each strategydoneWalkerSubject eventThe cancellation is graceful - it allows the current strategy's backtest to complete before stopping.
Walker coordinates with multiple markdown services:
BacktestMarkdownService accumulates closed signals automatically via event subscriptionBacktestMarkdownService.getData() to get statisticsWalkerMarkdownService stores the strategy result for final report generationWalker execution differs from individual Backtest runs:
| Aspect | Backtest.run() | Walker.run() |
|---|---|---|
| Scope | Single strategy | Multiple strategies |
| Context | {strategyName, exchangeName, frameName} |
{walkerName} (pulls exchange/frame from walker schema) |
| Iteration | Through timeframe | Through strategies array |
| Progress Events | Per frame/timestamp | Per strategy completion |
| Result | Closed signals with PNL | Best strategy + rankings |
| Duration | Single timeframe | Multiple full backtests |
| Event Emitter | signalBacktestEmitter |
walkerEmitter |
| Completion Event | doneBacktestSubject |
walkerCompleteSubject |
Walker execution handles errors at multiple levels:
Thrown synchronously before iteration begins:
Caught and propagated via errorEmitter:
src/classes/Walker.ts:135-137 catches errors and emits via errorEmitter:
task().catch((error) =>
errorEmitter.next(new Error(getErrorMessage(error)))
);
Users subscribe to errors:
import { listenError } from "backtest-kit";
listenError((error) => {
console.error("Walker error:", error.message);
});
Walker execution time is cumulative:
Total Time = Sum of Individual Backtest Times
For a walker with 10 strategies over a 1-month frame:
listenWalker() to track execution progressWalker execution orchestrates multi-strategy backtesting through:
The walker provides a systematic approach to strategy optimization by ensuring all strategies are tested under identical conditions.