This page documents the public-facing Live Trading API provided by the Live class. This API enables production-ready live trading execution with infinite async generators, crash-safe state persistence, and real-time signal monitoring. The API is designed for production use with automatic state recovery after process crashes.
For backtesting operations, see Backtest API. For detailed implementation of crash recovery mechanisms, see Crash Recovery. For signal persistence internals, see Signal Persistence. For the complete live execution flow including service orchestration, see Live Execution Flow.
The Live Trading API is implemented as a singleton class LiveUtils exported as Live. It provides four primary methods for live trading operations:
| Method | Return Type | Purpose |
|---|---|---|
run() |
AsyncIterableIterator<IStrategyTickResult> |
Infinite generator yielding signal events |
background() |
Promise<() => void> |
Background execution with cancellation |
getReport() |
Promise<string> |
Generate markdown report |
dump() |
Promise<void> |
Save report to disk |
Unlike the Backtest API which terminates when the timeframe is exhausted, the Live API runs indefinitely until manually stopped or the process crashes. State is persisted atomically before each signal transition, enabling seamless recovery on restart.
Live.run(
symbol: string,
context: {
strategyName: string;
exchangeName: string;
}
): AsyncIterableIterator<IStrategyTickResult>
The run() method initiates live trading execution for a trading pair. It returns an infinite async generator that yields signal events in real-time. The generator executes with 1-minute intervals, monitoring active signals and checking for new signal generation opportunities.
Diagram: Live.run Execution Path
The execution flow propagates through dependency injection layers:
Live.run() logs the request and delegates to liveGlobalServiceLiveGlobalService wraps the call with MethodContextService for context propagationLiveLogicPrivateService implements the infinite generator with 1-minute sleep intervalsstrategyGlobalService.tick() to check signal statePersistSignalAdapter before yieldingThe generator yields only opened and closed results. The idle and active states are filtered out to reduce noise in live mode:
| Result Type | When Yielded | Signal State |
|---|---|---|
IStrategyTickResultOpened |
New signal created and validated | Just opened |
IStrategyTickResultClosed |
Signal hit TP/SL or time expired | Just closed |
IStrategyTickResultIdle |
❌ Not yielded in live mode | No active signal |
IStrategyTickResultActive |
❌ Not yielded in live mode | Monitoring signal |
This filtering is implemented in src/lib/services/logic/private/LiveLogicPrivateService.ts where only opened and closed actions are yielded to the caller.
Diagram: Crash Recovery Flow
Every signal state change is persisted atomically before yielding results. This ensures:
Live.background(
symbol: string,
context: {
strategyName: string;
exchangeName: string;
}
): Promise<() => void>
The background() method runs live trading without yielding results to the caller. It internally consumes all generator values, allowing the strategy to execute for side effects only (callbacks, persistence, logging). Returns a cancellation function to stop execution gracefully.
// Implementation from src/classes/Live.ts:85-117
public background = async (symbol, context) => {
const iterator = this.run(symbol, context);
let isStopped = false;
let lastValue = null;
const task = async () => {
while (true) {
const { value, done } = await iterator.next();
if (value) {
lastValue = value;
}
if (done) break;
if (lastValue?.action === "closed" && isStopped) break;
}
}
task(); // Fire and forget
return () => { isStopped = true; }; // Cancellation closure
}
The method waits for the next closed event before honoring the cancellation flag. This ensures signals are not interrupted mid-lifecycle.
Diagram: Background Cancellation State Machine
The cancellation mechanism ensures signals are not left in an inconsistent state when stopping live trading.
Live.getReport(strategyName: string): Promise<string>
Generates a markdown-formatted report for the specified strategy. The report includes:
The report is generated by LiveMarkdownService which accumulates events passively via event listeners.
Live.dump(
strategyName: string,
path?: string
): Promise<void>
Saves the markdown report to disk. Default path is ./logs/live/{strategyName}.md. Custom paths can be specified via the optional path parameter.
| Section | Content |
|---|---|
| Header | Strategy name, total events, closed signals |
| Statistics | Win rate (% and W/L ratio), average PNL |
| Signal Table | Columns: Timestamp, Action, Symbol, Signal ID, Position, Prices (Open/TP/SL), PNL, Close Reason |
The generator yields a discriminated union IStrategyTickResult with the action field as the discriminator:
Diagram: Live Result Type Hierarchy
Type guards enable safe property access:
for await (const result of Live.run("BTCUSDT", context)) {
if (result.action === "opened") {
// TypeScript knows: result is IStrategyTickResultOpened
console.log(result.signal.id);
} else if (result.action === "closed") {
// TypeScript knows: result is IStrategyTickResultClosed
console.log(result.pnl.pnlPercentage);
console.log(result.closeReason);
}
}
// Reference: README.md:142-169
import { Live } from "backtest-kit";
for await (const result of Live.run("BTCUSDT", {
strategyName: "my-strategy",
exchangeName: "binance"
})) {
if (result.action === "opened") {
console.log("Signal opened:", result.signal.id);
// State already persisted to disk
}
if (result.action === "closed") {
console.log("Closed:", {
reason: result.closeReason,
pnl: result.pnl.pnlPercentage
});
// Generate report after each close
await Live.dump("my-strategy");
}
}
// Reference: README.md:387-418
import { Live, listenSignalLive } from "backtest-kit";
// Start background execution
const cancel = await Live.background("BTCUSDT", {
strategyName: "my-strategy",
exchangeName: "binance"
});
// React to events
listenSignalLive((event) => {
if (event.action === "closed") {
console.log("PNL:", event.pnl.pnlPercentage);
}
});
// Stop on condition
setTimeout(() => cancel(), 3600000); // Stop after 1 hour
// Reference: README.md:693-715
import { Live } from "backtest-kit";
const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"];
await Promise.all(
symbols.map(async (symbol) => {
for await (const result of Live.run(symbol, {
strategyName: "my-strategy",
exchangeName: "binance"
})) {
console.log(`[${symbol}]`, result.action);
}
})
);
Each symbol maintains independent state persistence in separate files: ./data/signal/binance-my-strategy-BTCUSDT.json, etc.
| Feature | Live.run() | Backtest.run() |
|---|---|---|
| Generator Type | Infinite | Finite (exhausts timeframe) |
| Time Progression | Real-time (Date.now()) | Historical (frame timestamps) |
| Sleep Interval | 1 minute + 1ms | No sleep (fast iteration) |
| Yielded Results | opened, closed only |
closed only (after fast-forward) |
| State Persistence | Enabled (crash recovery) | Disabled (stateless) |
| Context Parameter | { strategyName, exchangeName } |
{ strategyName, exchangeName, frameName } |
| Termination | Manual (break/cancel) | Automatic (frame end) |
Diagram: Live API Service Dependencies
The Live API delegates to service layers which handle:
MethodContextService and ExecutionContextService inject implicit contextStrategyGlobalService routes to correct ClientStrategy instancePersistSignalAdapter handles crash-safe file writesLiveMarkdownService accumulates events passivelyThe Live API does not catch exceptions - they bubble up to the caller. This design enables crash recovery:
For custom error handling, wrap the generator in try-catch:
try {
for await (const result of Live.run("BTCUSDT", context)) {
// Process result
}
} catch (error) {
console.error("Live trading error:", error);
// Log, alert, restart, etc.
}