This document explains the live trading execution flow orchestrated by LiveLogicPrivateService and LiveLogicPublicService. It covers the infinite loop mechanics, real-time signal streaming, context propagation, and timing intervals used for continuous market monitoring.
For crash recovery mechanisms and state persistence, see Crash Recovery. For signal monitoring logic (take profit, stop loss, time expiration), see Real-time Monitoring. For the overall live trading system overview, see Live Trading.
Live execution in backtest-kit operates as an infinite async generator that continuously monitors trading signals at 1-minute intervals. Unlike backtesting which iterates through historical timeframes, live mode progresses in real-time using new Date() at each iteration.
The execution model consists of two layers:
Results stream to consumers as they occur, yielding only opened and closed signal events while filtering out idle and active states to reduce noise.
The following diagram shows how live execution flows through the service layers:
Live Execution Service Architecture
The core of live execution is implemented in LiveLogicPrivateService.run() as an infinite async generator:
Live Loop Structure
The implementation uses these key constructs:
| Component | Purpose | Location |
|---|---|---|
while (true) |
Infinite loop for continuous monitoring | src/lib/services/logic/private/LiveLogicPrivateService.ts:58 |
new Date() |
Real-time progression (not historical) | src/lib/services/logic/private/LiveLogicPrivateService.ts:59 |
strategyGlobalService.tick() |
Check signal state at current time | src/lib/services/logic/private/LiveLogicPrivateService.ts:61 |
sleep(TICK_TTL) |
Wait 1 minute + 1ms between iterations | src/lib/services/logic/private/LiveLogicPrivateService.ts:69-80 |
yield result |
Stream opened/closed events to consumer | src/lib/services/logic/private/LiveLogicPrivateService.ts:78 |
Live execution uses MethodContextService to propagate schema names (strategyName, exchangeName) through the execution chain without explicit parameters:
Context Propagation Flow
The context object contains:
interface IMethodContext {
exchangeName: ExchangeName; // Which exchange to use
strategyName: StrategyName; // Which strategy to execute
frameName: FrameName; // Empty string for live mode
}
This context is read by StrategyConnectionService and ExchangeConnectionService to route requests to the correct registered schema instances. The scoped context eliminates the need to pass these names as parameters through every function call.
Live execution filters and streams results based on their action type:
Result Filtering Logic
The filtering logic prevents overwhelming the consumer with status updates:
| Action Type | Yielded? | Reason | Interface |
|---|---|---|---|
idle |
❌ No | No active signal, nothing to report | IStrategyTickResultIdle |
active |
❌ No | Signal being monitored, no change | IStrategyTickResultActive |
opened |
✅ Yes | New signal created, user should know | IStrategyTickResultOpened |
closed |
✅ Yes | Signal completed with PNL, critical event | IStrategyTickResultClosed |
Live execution uses precise timing to ensure consistent 1-minute monitoring:
Timing Constants
| Constant | Value | Purpose | Location |
|---|---|---|---|
TICK_TTL |
1 * 60 * 1_000 + 1 |
60,001ms (1 minute + 1ms) | src/lib/services/logic/private/LiveLogicPrivateService.ts:7 |
The extra 1ms ensures operations complete before the next tick begins, preventing timing edge cases.
Timing Diagram
Each iteration consists of:
new Date()strategyGlobalService.tick() which involves VWAP calculation and signal checkingopened or closedLiveLogicPrivateService coordinates with global services to execute strategy logic:
Service Interaction Diagram
Key interactions:
StrategyGlobalService.tick(): Wraps ClientStrategy.tick() with execution context injection. Passes symbol, when (Date), and backtest=false flag. src/lib/services/logic/private/LiveLogicPrivateService.ts:61
ClientStrategy: Executes signal generation logic, monitors active signals, and calculates PNL when signals close.
ExchangeGlobalService: Called by ClientStrategy to fetch real-time VWAP via getAveragePrice() for price monitoring.
Context Injection: ExecutionContextService provides {symbol, when, backtest: false} to all operations, enabling functions like getCandles() to work without explicit parameters.
Before entering the infinite loop, LiveLogicPrivateService relies on ClientStrategy.waitForInit() to recover persisted state:
Initialization Sequence
This initialization ensures:
For detailed crash recovery mechanisms, see Crash Recovery.
The following example demonstrates live execution with result streaming:
import { Live } from "backtest-kit";
// Infinite generator - runs until process terminates
for await (const result of Live.run("BTCUSDT", {
strategyName: "my-strategy",
exchangeName: "binance",
})) {
if (result.action === "opened") {
console.log(`[OPENED] Signal ID: ${result.signal.id}`);
console.log(` Position: ${result.signal.position}`);
console.log(` Entry: ${result.signal.priceOpen}`);
console.log(` TP: ${result.signal.priceTakeProfit}`);
console.log(` SL: ${result.signal.priceStopLoss}`);
} else if (result.action === "closed") {
console.log(`[CLOSED] Signal ID: ${result.signal.id}`);
console.log(` Reason: ${result.closeReason}`);
console.log(` PNL: ${result.pnl.pnlPercentage.toFixed(2)}%`);
console.log(` Entry: ${result.pnl.priceOpen}`);
console.log(` Exit: ${result.pnl.priceClose}`);
}
// Loop continues indefinitely
// Use Ctrl+C or process.exit() to stop
}
Each iteration of the loop represents one 1-minute tick. Results stream immediately when signals open or close, enabling real-time event processing and logging.