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.

addActionSchema registers a named event handler that receives every signal lifecycle event emitted by a strategy. Actions are the recommended integration point for side effects that must run alongside strategy execution without modifying signal logic: sending Telegram notifications, dispatching Redux actions, writing to external databases, triggering webhooks, or logging to monitoring systems. A single action can be attached to multiple strategies; a single strategy can have multiple actions. Each action instance is created per strategy-frame pair and lives for the duration of that run. Backtest Kit calls init() once before the first tick, routes events through the handler methods, and calls dispose() exactly once when the run completes or is cancelled.

Function Signature

addActionSchema(actionSchema: IActionSchema): void

Parameters

actionName
string (ActionName)
required
A unique string identifier for this action handler. Duplicate names throw at registration. Referenced in addStrategySchema via the actions array.
handler
TActionCtor | Partial<IPublicAction>
required
The action implementation. Accepts either:
  • A class constructor new (strategyName, frameName, actionName, backtest) => Partial<IPublicAction> — instantiated per strategy-frame pair.
  • A plain object implementing some or all IPublicAction methods — shared across all strategy-frame pairs.
All methods are optional. Unimplemented methods are silently skipped.
note
string
Optional developer note.
callbacks
Partial<IActionCallbacks>
Alternative to embedding logic in the handler class. Stateless callback functions for each event, called in addition to any handler methods:
  • onInit(actionName, strategyName, frameName, backtest) — initialization hook.
  • onDispose(actionName, strategyName, frameName, backtest) — cleanup hook.
  • onSignal(event, actionName, strategyName, frameName, backtest) — all modes.
  • onSignalLive(event, ...) — live mode only.
  • onSignalBacktest(event, ...) — backtest mode only.
  • onBreakevenAvailable(event, ...) — when SL reaches breakeven threshold.
  • onPartialProfitAvailable(event, ...) — on 10%, 20%, … profit milestones.
  • onPartialLossAvailable(event, ...) — on 10%, 20%, … loss milestones.
  • onPingScheduled(event, ...) — every minute while signal is scheduled.
  • onPingActive(event, ...) — every minute while position is open.
  • onPingIdle(event, ...) — every tick with no active signal.
  • onRiskRejection(event, ...) — when a signal fails risk validation.
  • onSignalSync(event, ...) — when a limit order is opened or closed on the exchange. Exceptions propagate—throwing rejects the exchange operation; the framework retries on the next tick.

IAction Handler Methods

The handler class may implement any subset of these methods (all are optional):
MethodWhen Called
init()Once before first tick
signal(event)Every tick, all modes
signalLive(event)Every tick in live mode
signalBacktest(event)Every candle in backtest mode
breakevenAvailable(event)Once per signal when breakeven threshold reached
partialProfitAvailable(event)Once per 10%/20%/…/100% profit milestone
partialLossAvailable(event)Once per 10%/20%/…/100% loss milestone
pingScheduled(event)Every minute while scheduled signal waits
pingActive(event)Every minute while pending position is open
pingIdle(event)Every tick with no signal
riskRejection(event)When signal rejected by risk
signalSync(event)When limit order opens/closes on exchange
dispose()Once at run completion

Common Use Cases

  • Telegram / Discord notifications — send formatted alerts when positions open, close, or hit milestones.
  • State management — dispatch Redux or Zustand actions for real-time UI updates.
  • Audit logging — write every lifecycle event to a database or JSONL file.
  • Webhooks — POST trade data to external analytics pipelines.
  • Broker synchronization — confirm exchange order status via onSignalSync.

Example — Telegram Notifier

import { addActionSchema, addStrategySchema, IStrategyTickResult, BreakevenContract } from 'backtest-kit';

// Class-based handler — instantiated per strategy-frame pair
class TelegramNotifier {
  private chatId: string;

  constructor(
    private strategyName: string,
    private frameName: string,
    private actionName: string,
    private backtest: boolean,
  ) {
    this.chatId = process.env.TELEGRAM_CHAT_ID!;
  }

  async init() {
    console.log(`TelegramNotifier ready for ${this.strategyName}/${this.frameName}`);
  }

  async signalLive(event: IStrategyTickResult) {
    if (event.action === 'opened') {
      await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: this.chatId,
          text: `🟢 [${this.strategyName}] ${event.signal.position.toUpperCase()} opened on ${event.symbol}\n` +
                `Entry: ${event.signal.priceOpen}\n` +
                `TP: ${event.signal.priceTakeProfit} | SL: ${event.signal.priceStopLoss}`,
        }),
      });
    }
    if (event.action === 'closed') {
      const emoji = event.pnl.pnlPercentage > 0 ? '✅' : '🔴';
      await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: this.chatId,
          text: `${emoji} [${this.strategyName}] Closed ${event.symbol} (${event.closeReason})\n` +
                `PNL: ${event.pnl.pnlPercentage.toFixed(2)}%`,
        }),
      });
    }
  }

  async breakevenAvailable(event: BreakevenContract) {
    await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: this.chatId,
        text: `⚖️ [${this.strategyName}] Breakeven available on ${event.symbol} @ ${event.currentPrice}`,
      }),
    });
  }

  async dispose() {
    console.log(`TelegramNotifier disposed for ${this.strategyName}/${this.frameName}`);
  }
}

addActionSchema({
  actionName: 'telegram-notifier',
  handler: TelegramNotifier,
});

// Attach to a strategy
addStrategySchema({
  strategyName: 'my-strategy',
  interval: '5m',
  actions: ['telegram-notifier'],
  getSignal: async (symbol) => { /* ... */ return null; },
});
Action errors are caught by ActionProxy and forwarded to errorEmitter—they never crash the strategy run. The only exception is signalSync / onSignalSync: errors there propagate up and prevent the exchange operation from executing, giving the broker adapter a chance to retry.

Build docs developers (and LLMs) love