Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit/llms.txt

Use this file to discover all available pages before exploring further.

Switching from backtest to live trading in Backtest Kit requires changing one class name and removing the frameName field. Everything else — your addExchangeSchema, addRiskSchema, addStrategySchema registrations, and your getSignal logic — stays exactly the same. The engine handles real-time clock progression, crash-safe persistence, and infinite monitoring automatically.

Live.background

Live.background starts an infinite monitoring loop in the background and returns a cancellation function. Signals flow through the same global event listeners you already attached for backtest.
import { Live, listenSignalLive } from 'backtest-kit';

const stop = Live.background('BTCUSDT', {
  strategyName: 'my-strategy',
  exchangeName: 'binance',
  // No frameName — live mode drives time from new Date()
});

listenSignalLive((event) => {
  if (event.action === 'opened') {
    console.log(`Signal opened: ${event.signal.position} at ${event.currentPrice}`);
  }
  if (event.action === 'closed') {
    console.log(`Signal closed: ${event.closeReason}, PNL: ${event.pnl.pnlPercentage.toFixed(2)}%`);
  }
});

// Graceful shutdown: stop new signals, let open positions wind down naturally
process.on('SIGINT', () => stop());
The cancellation function calls strategyConnectionService.stopStrategy(), which sets an internal _stopped flag. The next tick detects the flag and exits. Any active pending signal continues monitoring until it hits TP, SL, or time expiry — the position is never abandoned.

Live.run (Async Iterator)

For scripts and LLM agent loops, use the async iterator form:
for await (const result of Live.run('BTCUSDT', {
  strategyName: 'my-strategy',
  exchangeName: 'binance',
})) {
  // Yields only 'opened', 'closed', and 'cancelled' events
  // 'idle' and 'active' ticks are suppressed
  if (result.action === 'closed') {
    console.log(result.pnl.pnlPercentage);
  }
}
Live.run is an infinite generator — it never completes on its own. Use break or cancel the outer async context to stop it.

Crash Recovery Flow

Live mode is designed to survive process crashes. Here is what happens on each restart:
1
Load Persisted State
2
On the very first tick, ClientStrategy.waitForInit() (protected by singleshot) reads the last saved signal from disk:
3
public waitForInit = singleshot(async () => {
  if (!this.params.execution.context.backtest) {
    this._pendingSignal = await PersistSignalAdapter.readSignalData(
      this.params.strategyName,
      this.params.execution.context.symbol
    );
  }
});
4
Resume Monitoring
5
If a pending signal was found, the engine skips getSignal and immediately resumes TP/SL monitoring at the current market price. The open position is never re-entered.
6
Continue Until Natural Exit
7
The restored signal continues monitoring until it hits TP, SL, or minuteEstimatedTime expiry — exactly as if the process had never restarted.
8
Persist Every Mutation
9
Every call to setPendingSignal(signal) writes atomically to ./dump/signal/{strategyName}/{symbol}.json using writeFileAtomic. A crash mid-write cannot corrupt the file.

Custom Persistence Adapter

Replace the default file-based persistence with any backend:
import { PersistSignalAdapter } from 'backtest-kit';

class RedisPersistSignal {
  constructor(symbol, strategyName, exchangeName) { /* ... */ }
  async waitForInit(initial) { /* ... */ }
  async readSignalData() { /* read from Redis */ }
  async writeSignalData(signalRow) { /* write to Redis */ }
}

PersistSignalAdapter.usePersistSignalAdapter(RedisPersistSignal);

Live Reports

Generate and save the same reports in live mode using the Live facade:
// Markdown report to ./dump/live/
await Live.dump('BTCUSDT', {
  strategyName: 'my-strategy',
  exchangeName: 'binance',
});

// In-memory statistics
const stats = await Live.getData('BTCUSDT', {
  strategyName: 'my-strategy',
  exchangeName: 'binance',
});

console.log(`Closed trades: ${stats.totalClosed}`);
console.log(`Win rate:      ${stats.winRate?.toFixed(1)}%`);
console.log(`Sharpe:        ${stats.sharpeRatio?.toFixed(3)}`);
LiveStatisticsModel includes all closed-trade metrics plus the full eventList with every tick event (idle, opened, active, closed) for dashboard integration.

Key Differences from Backtest Mode

AspectBacktestLive
Time sourceClientFrameDate[] arraynew Date() every tick
LoopFinite — exhausts timeframes[]Infinite — while(true)
Frame requiredYesNo
Signal persistenceSkipped (memory only)Atomic file write on every mutation
Crash recoveryN/AwaitForInit() restores last signal
Performance~700× real-time per symbolReal-time (wall clock)

Connecting to a Real Exchange

To place actual orders on the exchange, register a Broker adapter before calling Live.background. The adapter’s methods are called atomically — if the exchange rejects an order, the internal position state rolls back and the engine retries on the next tick.
import { Broker, IBroker } from 'backtest-kit';

Broker.useBrokerAdapter(
  class implements Partial<IBroker> {
    async waitForInit() {
      // Connect to exchange, load markets
    }
    async onSignalOpenCommit(payload) {
      // Place limit buy order
    }
    async onSignalCloseCommit(payload) {
      // Place limit sell order
    }
    // ... other optional methods
  }
);

// Subscribe broker to sync events (signal-open, signal-close)
Broker.enable();
See the full Spot and Futures Broker adapter examples in the README for production-grade order placement with fill polling, partial-fill rollback, and SL/TP restoration.
Never hard-code API keys. Store them in environment variables (process.env.BINANCE_API_KEY) and use a .env file locally. In Docker deployments, pass secrets via docker-compose.yaml environment configuration.

Build docs developers (and LLMs) love