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.

addStrategySchema is the primary registration function for trading logic in Backtest Kit. Every strategy must be registered before it can be referenced in a Backtest.run, Live.run, or Walker.run context. The function stores the schema in an internal registry and validates its structure—including risk profile references and action names—at registration time so that misconfiguration is caught early rather than at execution.

Function Signature

addStrategySchema(strategySchema: IStrategySchema): void

Parameters

strategyName
string
required
A unique string identifier for this strategy. Used as the routing key when calling Backtest.run, Live.run, and risk/action lookup. Duplicate names throw at registration.
interval
SignalInterval
default:"1m"
Minimum time between successive getSignal calls. Backtest Kit enforces this throttle to prevent signal spam.Accepted values: "1m" | "3m" | "5m" | "15m" | "30m" | "1h"
getSignal
async (symbol: string, when: Date, currentPrice: number) => ISignalDto | null
required
The signal generation function. Called on each tick when the interval has elapsed and no pending or scheduled signal is active.
  • Return an ISignalDto to open or schedule a position.
  • Return null to skip this tick with no action.
Runs inside an execution context—getCandles, getAveragePrice, and other context-aware helpers work without explicit parameters.
riskName
string (RiskName)
References a risk profile registered via addRiskSchema. If provided, Backtest Kit runs all validation functions from that profile before accepting a signal. Omit to run without risk checks.
riskList
string[]
An array of risk profile names. Use instead of riskName when a strategy must pass multiple independent risk profiles. All validations must pass for the signal to be accepted.
note
string
Optional developer note for documentation or introspection. Accessible via getStrategySchema(strategyName).note.
callbacks
Partial<IStrategyCallbacks>
Optional lifecycle hooks for observing signal state changes:
  • onOpen(symbol, data, currentPrice, backtest) — fired when a position opens.
  • onClose(symbol, data, priceClose, backtest) — fired when a position closes.
  • onTick(symbol, result, backtest) — fired on every tick with the full result union.
  • onActive, onIdle, onSchedule, onCancel — additional state hooks.
  • onSchedulePing / onActivePing — fired every minute while monitoring.
  • onBreakeven, onPartialProfit, onPartialLoss — milestone hooks.
actions
string[]
An array of action names registered via addActionSchema. Each action receives every signal lifecycle event for this strategy and can send notifications, update Redux state, trigger webhooks, etc.

ISignalDto — Return Value of getSignal

The object returned by getSignal is validated and augmented by Backtest Kit before the signal is accepted.
position
"long" | "short"
required
Trade direction. "long" enters a buy position; "short" enters a sell position.
priceTakeProfit
number
required
Take-profit exit price. Must be above priceOpen for "long" and below for "short".
priceStopLoss
number
required
Stop-loss exit price. Must be below priceOpen for "long" and above for "short".
priceOpen
number
Limit entry price. When provided the signal is scheduled—it waits until the market price reaches priceOpen. When omitted the position opens immediately at the current VWAP price.
minuteEstimatedTime
number
Expected trade duration in minutes. The position closes with time_expired if it has not hit TP or SL within this window. Pass Infinity for no timeout. Defaults to CC_MAX_SIGNAL_LIFETIME_MINUTES (1440).
id
string
Optional UUID for the signal. Auto-generated if omitted.
cost
number
Dollar amount allocated to the initial entry. Defaults to CC_POSITION_ENTRY_COST ($100).
note
string
Human-readable description of the trade rationale, stored alongside the signal.

Example

import { addStrategySchema, addRiskSchema, addExchangeSchema, getCandles } from 'backtest-kit';
import ccxt from 'ccxt';

// Register exchange first
addExchangeSchema({
  exchangeName: 'binance',
  getCandles: async (symbol, interval, since, limit) => {
    const exchange = new ccxt.binance();
    const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
    return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
      timestamp, open, high, low, close, volume,
    }));
  },
  formatPrice: (symbol, price) => price.toFixed(2),
  formatQuantity: (symbol, quantity) => quantity.toFixed(8),
});

// Register risk profile
addRiskSchema({
  riskName: 'demo',
  validations: [
    ({ currentSignal, currentPrice }) => {
      const { priceOpen = currentPrice, priceTakeProfit, position } = currentSignal;
      const tpDistance = position === 'long'
        ? ((priceTakeProfit - priceOpen) / priceOpen) * 100
        : ((priceOpen - priceTakeProfit) / priceOpen) * 100;
      if (tpDistance < 1) throw new Error(`TP too close: ${tpDistance.toFixed(2)}%`);
    },
  ],
});

// Register strategy
addStrategySchema({
  strategyName: 'rsi-trend',
  interval: '5m',
  riskName: 'demo',
  getSignal: async (symbol, when, currentPrice) => {
    const candles = await getCandles(symbol, '1h', 14);
    // compute RSI from candles ...
    const rsi = 28; // placeholder

    if (rsi < 30) {
      return {
        position: 'long',
        priceOpen: currentPrice * 0.998,       // 0.2% below market — scheduled
        priceTakeProfit: currentPrice * 1.03,   // +3% target
        priceStopLoss: currentPrice * 0.985,    // -1.5% stop
        minuteEstimatedTime: 240,               // 4-hour window
        note: `RSI oversold at ${rsi}`,
      };
    }
    return null; // no signal this tick
  },
  callbacks: {
    onOpen: (symbol, signal, price, backtest) =>
      console.log(`[${backtest ? 'BT' : 'LIVE'}] Opened ${signal.position} on ${symbol} @ ${price}`),
    onClose: (symbol, signal, priceClose, backtest) =>
      console.log(`[${backtest ? 'BT' : 'LIVE'}] Closed ${symbol} @ ${priceClose}`),
  },
});
When getSignal returns null, Backtest Kit treats the current tick as idle and moves to the next timeframe. The interval throttle timer is not reset by a null return—the next getSignal call still waits for the configured interval to elapse.

Build docs developers (and LLMs) love