Logic Services implement the core execution orchestration layer for the three operational modes: Backtest, Live, and Walker. These services manage the temporal progression, signal lifecycle coordination, and result streaming for each execution mode.
This page documents the Logic Service architecture, the Private/Public service separation pattern, and the AsyncGenerator streaming model. For information about Strategy execution logic, see ClientStrategy. For command validation and delegation, see Service Architecture Overview.
Logic Services are organized into three execution mode families, each with Private and Public service tiers:
| Execution Mode | Private Service | Public Service | Purpose |
|---|---|---|---|
| Backtest | BacktestLogicPrivateService |
BacktestLogicPublicService |
Historical simulation with timeframe iteration |
| Live | LiveLogicPrivateService |
LiveLogicPublicService |
Real-time trading with crash recovery |
| Walker | WalkerLogicPrivateService |
WalkerLogicPublicService |
Strategy comparison with metric optimization |
Private Services implement the core execution algorithms using AsyncGenerator streaming. They have no context management and require explicit parameters.
Public Services wrap Private Services with MethodContextService to provide implicit context propagation, allowing downstream functions to access strategyName, exchangeName, and frameName without explicit parameters.
The Logic Service architecture implements a two-tier separation pattern where Private Services contain pure execution algorithms and Public Services add context management.
Private Services implement the core execution logic with these properties:
inject<T>(TYPES.*) patternsrc/lib/services/logic/private/BacktestLogicPrivateService.ts:33-46 demonstrates the Private Service dependency pattern:
export class BacktestLogicPrivateService {
private readonly loggerService = inject<LoggerService>(TYPES.loggerService);
private readonly strategyGlobalService = inject<StrategyGlobalService>(
TYPES.strategyGlobalService
);
private readonly exchangeGlobalService = inject<ExchangeGlobalService>(
TYPES.exchangeGlobalService
);
private readonly frameGlobalService = inject<FrameGlobalService>(
TYPES.frameGlobalService
);
private readonly methodContextService = inject<TMethodContextService>(
TYPES.methodContextService
);
Public Services wrap Private Services with context injection:
MethodContextService.bind()context object instead of explicit service referencesrun(symbol, context) signatureThe Public Service pattern is implemented in src/lib/services/logic/public/BacktestLogicPublicService.ts using function composition:
public readonly run = (
symbol: string,
context: {
strategyName: string;
exchangeName: string;
frameName: string;
}
): AsyncGenerator<IStrategyBacktestResult> =>
this.methodContextService.bind(
context,
() => this.backtestLogicPrivateService.run(symbol)
);
This pattern allows downstream functions like getCandles() and getSignal() to access context implicitly through MethodContextService.
BacktestLogicPrivateService implements historical simulation through sequential timeframe iteration with skip-ahead optimization.
Timeframe Iteration: src/lib/services/logic/private/BacktestLogicPrivateService.ts:69-74 loads the complete date range from FrameGlobalService and iterates sequentially:
const timeframes = await this.frameGlobalService.getTimeframe(
symbol,
this.methodContextService.context.frameName
);
Signal Detection: src/lib/services/logic/private/BacktestLogicPrivateService.ts:96 calls tick() with backtest=true to check for signal generation at each timeframe:
result = await this.strategyGlobalService.tick(symbol, when, true);
Scheduled Signal Handling: src/lib/services/logic/private/BacktestLogicPrivateService.ts:113-155 fetches extended candle range for scheduled signals to monitor activation/cancellation plus full signal duration:
const candlesNeeded = GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
candles = await this.exchangeGlobalService.getNextCandles(
symbol,
"1m",
candlesNeeded,
when,
true
);
Skip-Ahead Optimization: src/lib/services/logic/private/BacktestLogicPrivateService.ts:227-232 advances the timeframe iterator to the signal close time, avoiding redundant tick() calls:
while (
i < timeframes.length &&
timeframes[i].getTime() < backtestResult.closeTimestamp
) {
i++;
}
Performance Tracking: src/lib/services/logic/private/BacktestLogicPrivateService.ts:214-224 emits granular timing metrics for signal processing duration:
await performanceEmitter.next({
timestamp: currentTimestamp,
previousTimestamp: previousEventTimestamp,
metricType: "backtest_signal",
duration: signalEndTime - signalStartTime,
strategyName: this.methodContextService.context.strategyName,
exchangeName: this.methodContextService.context.exchangeName,
symbol,
backtest: true,
});
All operations are wrapped in try-catch blocks that emit errors via errorEmitter and continue iteration:
tick() failures skip the current timeframegetNextCandles() failures skip signal processingbacktest() failures skip signal resultLiveLogicPrivateService implements real-time trading through an infinite polling loop with crash recovery support.
Infinite Loop: src/lib/services/logic/private/LiveLogicPrivateService.ts:68 implements continuous monitoring that never completes:
while (true) {
const when = new Date();
// ... tick logic
await sleep(TICK_TTL);
}
Real-Time Timestamps: src/lib/services/logic/private/LiveLogicPrivateService.ts:70 creates fresh Date objects for each iteration, ensuring time progresses naturally:
const when = new Date();
Tick Interval: src/lib/services/logic/private/LiveLogicPrivateService.ts:12 defines a 61-second polling interval (slightly over 1 minute to avoid clock skew issues):
const TICK_TTL = 1 * 60 * 1_000 + 1;
Selective Yielding: src/lib/services/logic/private/LiveLogicPrivateService.ts:110-126 filters tick results, only yielding opened and closed actions while sleeping for idle, active, and scheduled:
if (result.action === "active") {
await sleep(TICK_TTL);
continue;
}
if (result.action === "idle") {
await sleep(TICK_TTL);
continue;
}
if (result.action === "scheduled") {
await sleep(TICK_TTL);
continue;
}
// Yield opened, closed results
yield result as IStrategyTickResultClosed | IStrategyTickResultOpened;
Crash Recovery: State recovery happens transparently in ClientStrategy.waitForInit() which is called by StrategyGlobalService before the first tick(). The Live Logic Service has no explicit recovery logic because persistence is handled by ClientStrategy.
Error Resilience: src/lib/services/logic/private/LiveLogicPrivateService.ts:75-88 catches all tick errors, emits them, sleeps, and continues the loop:
try {
result = await this.strategyGlobalService.tick(symbol, when, false);
} catch (error) {
this.loggerService.warn(
"liveLogicPrivateService tick failed, retrying after sleep",
{ symbol, when: when.toISOString(), error: errorData(error) }
);
await errorEmitter.next(error);
await sleep(TICK_TTL);
continue;
}
WalkerLogicPrivateService orchestrates strategy comparison by running multiple backtest executions sequentially and tracking the best-performing strategy by a configurable metric.
Sequential Execution: src/lib/services/logic/private/WalkerLogicPrivateService.ts:107-228 runs one backtest at a time to avoid resource contention:
for (const strategyName of strategies) {
const iterator = this.backtestLogicPublicService.run(symbol, {
strategyName,
exchangeName: context.exchangeName,
frameName: context.frameName,
});
result = await resolveDocuments(iterator);
// ... process results
}
Backtest Delegation: src/lib/services/logic/private/WalkerLogicPrivateService.ts:117-121 uses BacktestLogicPublicService to execute each strategy with proper context injection:
const iterator = this.backtestLogicPublicService.run(symbol, {
strategyName,
exchangeName: context.exchangeName,
frameName: context.frameName,
});
Statistics Extraction: src/lib/services/logic/private/WalkerLogicPrivateService.ts:165 retrieves computed metrics from BacktestMarkdownService:
const stats = await this.backtestMarkdownService.getData(symbol, strategyName);
Metric Comparison: src/lib/services/logic/private/WalkerLogicPrivateService.ts:167-186 extracts and validates the configured metric, updating best strategy if superior:
const value = stats[metric];
const metricValue =
value !== null &&
value !== undefined &&
typeof value === "number" &&
!isNaN(value) &&
isFinite(value)
? value
: null;
const isBetter =
bestMetric === null ||
(metricValue !== null && metricValue > bestMetric);
if (isBetter && metricValue !== null) {
bestMetric = metricValue;
bestStrategy = strategyName;
}
Progress Yielding: src/lib/services/logic/private/WalkerLogicPrivateService.ts:190-227 constructs and yields WalkerContract after each strategy completes:
const walkerContract: WalkerContract = {
walkerName: context.walkerName,
exchangeName: context.exchangeName,
frameName: context.frameName,
symbol,
strategyName,
stats,
metricValue,
metric,
bestMetric,
bestStrategy,
strategiesTested,
totalStrategies: strategies.length,
};
await walkerEmitter.next(walkerContract);
yield walkerContract;
Schema Callbacks: src/lib/services/logic/private/WalkerLogicPrivateService.ts:109-111, src/lib/services/logic/private/WalkerLogicPrivateService.ts:217-224, and src/lib/services/logic/private/WalkerLogicPrivateService.ts:246-248 invoke optional lifecycle callbacks:
onStrategyStart(strategyName, symbol): Before backtest beginsonStrategyComplete(strategyName, symbol, stats, metricValue): After backtest finishesonStrategyError(strategyName, symbol, error): If backtest failsonComplete(finalResults): After all strategies completeCancellation Support: src/lib/services/logic/private/WalkerLogicPrivateService.ts:96-104 listens for walkerStopSubject events to allow external cancellation:
const listenStop = walkerStopSubject
.filter((data) => {
let isOk = true;
isOk = isOk && data.symbol === symbol;
isOk = isOk && data.strategyName === pendingStrategy;
return isOk;
})
.map(() => CANCEL_SYMBOL)
.toPromise();
All Logic Services use AsyncGenerator functions (async *) for memory-efficient result streaming with support for early termination.
| Aspect | Traditional Array Return | AsyncGenerator Streaming |
|---|---|---|
| Memory Usage | Accumulates all results in memory | Processes one result at a time |
| Time to First Result | Must complete entire execution | Yields results incrementally |
| Early Termination | Not supported | Consumer can break to cancel |
| Progress Monitoring | No visibility until complete | Real-time progress via yielded results |
| Error Recovery | Single point of failure | Can catch errors per iteration |
Backtest Mode - Finite Generator:
IStrategyBacktestResult for each closed signalLive Mode - Infinite Generator:
IStrategyTickResultOpened and IStrategyTickResultClosedwhile(true) loop)Walker Mode - Progress Generator:
WalkerContract after each strategy completesstrategiesTested counterConsumers iterate Logic Services using for await...of:
// Backtest: Collect all results
const results = [];
for await (const result of backtestLogic.run("BTCUSDT")) {
results.push(result);
console.log("Closed:", result.closeReason, result.pnl.pnlPercentage);
}
// Live: Infinite monitoring
for await (const result of liveLogic.run("BTCUSDT")) {
if (result.action === "opened") {
console.log("New signal:", result.signal.id);
}
if (result.action === "closed") {
console.log("PNL:", result.pnl.pnlPercentage);
}
}
// Walker: Progress tracking
for await (const progress of walkerLogic.run("BTCUSDT", strategies, metric, ctx)) {
console.log(`Progress: ${progress.strategiesTested}/${progress.totalStrategies}`);
console.log(`Best: ${progress.bestStrategy} = ${progress.bestMetric}`);
}
Early termination example from src/lib/services/logic/private/BacktestLogicPrivateService.ts:58-59:
for await (const result of backtestLogic.run("BTCUSDT")) {
if (result.pnl.pnlPercentage < -10) break; // Cancel remaining execution
}
Logic Services emit events at key execution points for observability and progress tracking.
| Service | Events Emitted | Purpose |
|---|---|---|
BacktestLogicPrivateService |
progressBacktestEmitterperformanceEmittererrorEmitter |
Frame progress Timing metrics Error logging |
LiveLogicPrivateService |
performanceEmittererrorEmitter |
Tick timing Error logging |
WalkerLogicPrivateService |
progressWalkerEmitterwalkerEmitterwalkerCompleteSubjecterrorEmitter |
Strategy progress Current best Final results Error logging |
Backtest Progress: src/lib/services/logic/private/BacktestLogicPrivateService.ts:84-92 emits frame-level progress with completion percentage:
await progressBacktestEmitter.next({
exchangeName: this.methodContextService.context.exchangeName,
strategyName: this.methodContextService.context.strategyName,
symbol,
totalFrames,
processedFrames: i,
progress: totalFrames > 0 ? i / totalFrames : 0,
});
Walker Progress: src/lib/services/logic/private/WalkerLogicPrivateService.ts:206-214 emits strategy-level progress:
await progressWalkerEmitter.next({
walkerName: context.walkerName,
exchangeName: context.exchangeName,
frameName: context.frameName,
symbol,
totalStrategies: strategies.length,
processedStrategies: strategiesTested,
progress: strategies.length > 0 ? strategiesTested / strategies.length : 0,
});
All Logic Services emit granular timing metrics via performanceEmitter:
metricType: Identifies the operation type
"backtest_timeframe": Time per timeframe iteration"backtest_signal": Time to process one signal"backtest_total": Total backtest duration"live_tick": Time per live tickduration: Elapsed time in millisecondstimestamp: Current timestamppreviousTimestamp: Previous event timestamp (for interval calculation)strategyName, exchangeName, symbol, backtest: Context metadataExample from src/lib/services/logic/private/BacktestLogicPrivateService.ts:214-224:
await performanceEmitter.next({
timestamp: currentTimestamp,
previousTimestamp: previousEventTimestamp,
metricType: "backtest_signal",
duration: signalEndTime - signalStartTime,
strategyName: this.methodContextService.context.strategyName,
exchangeName: this.methodContextService.context.exchangeName,
symbol,
backtest: true,
});
Logic Services coordinate execution by orchestrating calls to Global Services, Schema Services, and Markdown Services.
BacktestLogicPrivateService Dependencies:
StrategyGlobalService: Calls tick() and backtest() for signal lifecycleExchangeGlobalService: Calls getNextCandles() for future data fetchingFrameGlobalService: Calls getTimeframe() to load date rangeMethodContextService: Reads strategyName, exchangeName, frameName from contextLiveLogicPrivateService Dependencies:
StrategyGlobalService: Calls tick() for signal status checkingMethodContextService: Reads strategyName, exchangeName from contextWalkerLogicPrivateService Dependencies:
BacktestLogicPublicService: Delegates to backtest executionWalkerSchemaService: Retrieves walker configuration and callbacksBacktestMarkdownService: Extracts statistics for metric comparisonBacktest Timeframe Iteration: src/lib/services/logic/private/BacktestLogicPrivateService.ts:69-74
FrameGlobalService.getTimeframe()
→ returns Date[] array
→ BacktestLogic iterates sequentially
→ StrategyGlobalService.tick() per timeframe
Live Continuous Polling: src/lib/services/logic/private/LiveLogicPrivateService.ts:68-74
while (true):
new Date() creates current timestamp
→ StrategyGlobalService.tick(when, false)
→ sleep(TICK_TTL)
Walker Strategy Comparison: src/lib/services/logic/private/WalkerLogicPrivateService.ts:107-165
for each strategy:
BacktestLogicPublicService.run(symbol, context)
→ await all results
→ BacktestMarkdownService.getData(symbol, strategy)
→ compare metric values
| Characteristic | Backtest | Live | Walker |
|---|---|---|---|
| Completion | Finite (exhausts timeframes) | Infinite (never completes) | Finite (exhausts strategies) |
| Time Progression | Historical (from frame array) | Real-time (new Date()) |
Historical (delegates to Backtest) |
| Result Type | IStrategyBacktestResult |
IStrategyTickResultOpenedIStrategyTickResultClosed |
WalkerContract |
| Primary Loop | for over timeframes |
while(true) infinite |
for over strategies |
| Sleep/Delay | None (continuous) | TICK_TTL (61s) between ticks |
None (sequential) |
| Persistence | None (stateless) | Crash recovery via ClientStrategy |
None (stateless) |
| Progress Events | Frame count and percentage | No progress events | Strategy count and percentage |
| Signal Processing | Fast-forward with getNextCandles() |
Real-time monitoring | Delegates to Backtest |
| Skip Optimization | Yes (jump to closeTimestamp) | No (continuous monitoring) | N/A (runs full backtest) |
| Error Handling | Skip timeframe, continue | Sleep and retry | Skip strategy, continue |
| Context Parameters | strategyName, exchangeName, frameName |
strategyName, exchangeName |
walkerName, exchangeName, frameName |
| Use Case | Historical analysis | Production trading | Strategy comparison |