Skip to main content

Documentation Index

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

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

Before running backtests or live trading, every component of Backtest Kit must be registered through schema functions. These functions populate the internal registries that the execution engine queries on every tick. Registration is stateful and global — schemas registered once are available for the lifetime of the process. Call them at startup, before any Backtest.background() or Live.background() invocations.

addStrategySchema

Registers a trading strategy with the engine. The strategy’s getSignal function is the primary entry point for signal generation. It is called on every tick (subject to interval throttling), validated automatically for TP/SL logic, and persisted to disk in live mode for crash recovery.
import { addStrategySchema } from 'backtest-kit';

addStrategySchema({
  strategyName: 'my-strategy',
  interval: '15m',         // optional: throttle getSignal calls
  riskName: 'conservative', // optional: apply a risk profile
  getSignal: async (symbol, when, currentPrice) => {
    // Return a signal object or null to stay idle
    return null;
  },
  onInit: async (symbol, strategyName, exchangeName, frameName) => {
    // Called once before the first tick for this symbol
  },
  onDispose: async (symbol, strategyName, exchangeName, frameName) => {
    // Called once after the last tick (backtest complete, or live stopped)
  },
});

Parameters

strategyName
string
required
Unique identifier for this strategy. Used as the key in the schema registry and in all reports, dumps, and persistence files.
getSignal
(symbol: string, when: Date, currentPrice: number) => Promise<ISignalDto | null>
required
The signal generation function. Called on every tick while no position is active. Return a signal object to open a position or null to stay idle. Signals are validated automatically — invalid TP/SL relationships or negative prices cause the signal to be silently rejected (logged to listenError).
interval
'1m' | '3m' | '5m' | '15m' | '30m' | '1h'
Throttle interval for getSignal. When set, getSignal is not called more frequently than this interval. Useful for strategies that rely on LLM inference or expensive computations. Defaults to calling on every available tick.
riskName
string
Name of a risk schema registered via addRiskSchema. If provided, every candidate signal is passed through the risk validations before being accepted. Rejected signals emit a listenRisk event instead of opening a position.
onInit
(symbol, strategyName, exchangeName, frameName) => Promise<void>
Lifecycle callback. Invoked once before the first tick for a given symbol. Use for one-time setup such as loading ML models, warming caches, or opening database connections.
onDispose
(symbol, strategyName, exchangeName, frameName) => Promise<void>
Lifecycle callback. Invoked once after the last tick — when a backtest completes or a live session is stopped. Use for cleanup such as flushing buffers or closing connections.
onOpen
(backtest, symbol, signal) => Promise<void>
Called immediately after a position is opened. Receives the full signal row, including the effective entry price.
onClose
(backtest, symbol, priceClose, signal) => Promise<void>
Called immediately after a position is closed for any reason (take-profit, stop-loss, time expiry, or manual close). Receives the closing price and the final signal row.
onHighestProfit
(currentPrice, timestamp) => Promise<void>
Called when the position reaches a new peak unrealized profit (new high watermark). Use for trailing-take or alerting logic.
onMaxDrawdown
(currentPrice, timestamp) => Promise<void>
Called when the position reaches a new maximum drawdown (new low watermark). Use for DCA or risk-reduction logic.
onSchedulePing
(symbol, currentPrice, backtest, timestamp) => Promise<void>
Called on every tick while a signal is in the scheduled (pending entry at limit price) state. Fires approximately once per minute.
onActivePing
(symbol, currentPrice, backtest, timestamp) => Promise<void>
Called on every tick while a position is active (open, monitoring TP/SL). Use for DCA, partial profit-taking, trailing stops, and breakeven adjustments.
onIdlePing
(symbol, currentPrice, backtest, timestamp) => Promise<void>
Called on every tick while no position is active and no signal is scheduled. Use for pre-market analysis or idle monitoring.

Signal Object Shape

getSignal must return either null or an object conforming to ISignalDto:
interface ISignalDto {
  position: 'long' | 'short';
  priceOpen?: number;          // defaults to current VWAP if omitted (market entry)
  priceTakeProfit: number;
  priceStopLoss: number;
  minuteEstimatedTime: number; // use Infinity for no time-based expiry
  cost: number;                // dollar amount per entry (e.g. 100 for $100 per DCA rung)
  id?: string;                 // optional: stable ID for deduplication / logging
  note?: string;               // optional: human-readable reason for the signal
}
position
'long' | 'short'
required
Direction of the trade.
priceOpen
number
Desired entry price. If omitted, the engine uses the current VWAP as the entry price (market-style entry). If provided and the current price is not yet at priceOpen, the signal is placed in scheduled state until the price is reached.
priceTakeProfit
number
required
Take-profit price. Must be above priceOpen for longs and below for shorts — validated automatically.
priceStopLoss
number
required
Stop-loss price. Must be below priceOpen for longs and above for shorts — validated automatically.
minuteEstimatedTime
number
required
Maximum lifetime of the trade in minutes. Set to Infinity to hold until TP or SL is hit with no timeout. This value determines how many candles are fetched during backtesting for fast forward-simulation.
cost
number
required
Dollar amount allocated to the initial entry. This same amount is used for each subsequent DCA entry via commitAverageBuy.
id
string
Optional stable identifier for the signal. Useful for correlating signals across logs, reports, and external systems. Auto-generated if not provided.
note
string
Optional human-readable annotation. Appears in reports and notification payloads. Useful for recording LLM reasoning, indicator values, or the trigger condition.

Position Helper Functions

The Position class provides convenience constructors for common TP/SL configurations. Both methods calculate prices as percentages relative to currentPrice and respect the position direction.
import { Position } from 'backtest-kit';

// Moonbag: enter at market, no take-profit (TP is set to a very large value),
// stop-loss at percentStopLoss% below entry (long) or above entry (short).
const moonbagSignal = Position.moonbag({
  position: 'long',
  currentPrice: 45000,
  percentStopLoss: 10,   // SL at 10% below entry
});

// Bracket: symmetric TP and SL distances from entry, expressed as percentages.
const bracketSignal = Position.bracket({
  position: 'long',
  currentPrice: 45000,
  percentTakeProfit: 2,  // TP at +2% from entry
  percentStopLoss: 1,    // SL at -1% from entry
});
Both helpers return a partial ISignalDto (without cost or minuteEstimatedTime) that you merge with the rest of your signal fields.

addExchangeSchema

Registers a data source (exchange) with the engine. The getCandles function is the critical adapter — it must fetch OHLCV data from your data provider and return it in the framework’s canonical format. All candle fetching inside strategies (via getCandles, getNextCandles, getRawCandles) is routed through this adapter.
import ccxt from 'ccxt';
import { addExchangeSchema } from 'backtest-kit';

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),
});
exchangeName
string
required
Unique identifier for this exchange. Referenced by Backtest.background(), Live.background(), and addStrategySchema.
getCandles
(symbol, interval, since: Date, limit: number, backtest: boolean) => Promise<ICandleData[]>
required
Adapter function that fetches OHLCV candles. The engine guarantees that since is aligned to the interval boundary and that exactly limit candles are expected in return. The backtest flag is true during backtesting and false during live trading — useful for routing to historical vs. live data endpoints.
formatPrice
(symbol: string, price: number) => string
Formats a price value to the exchange’s required precision. Used in reports and broker adapter payloads. Defaults to price.toFixed(2) if not provided.
formatQuantity
(symbol: string, quantity: number) => string
Formats a quantity value to the exchange’s required precision. Used in broker adapter payloads. Defaults to quantity.toFixed(8) if not provided.
getOrderBook
(symbol, depth, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>
Optional. Fetches an order book snapshot. Called by getOrderBook() inside strategies. If not provided, calling getOrderBook() throws a runtime error. The from/to window is aligned to CC_ORDER_BOOK_TIME_OFFSET_MINUTES boundaries — live implementations may ignore the time range and return the current snapshot.
getAggregatedTrades
(symbol, from: Date, to: Date, backtest: boolean) => Promise<IAggregatedTradeData[]>
Optional. Fetches aggregated trade records. Called by getAggregatedTrades() inside strategies. If not provided, calling getAggregatedTrades() throws a runtime error. Live implementations may ignore from/to and return the most recent trades.

addFrameSchema

Registers a time frame for backtesting. The frame defines the date range and candle interval that the engine iterates over. Each Date in the generated timeframe array becomes one execution tick — the engine calls your strategy’s getSignal (and checks TP/SL) once per tick.
import { addFrameSchema } from 'backtest-kit';

addFrameSchema({
  frameName: 'dec-2025-1m',
  interval: '1m',
  startDate: new Date('2025-12-01'),
  endDate: new Date('2025-12-31'),
});
frameName
string
required
Unique identifier for this frame. Referenced by Backtest.background() and Backtest.run().
interval
string
required
Candle interval for the backtest timeframe. Determines the spacing between ticks and the step size used when aligning candle requests. Supported values: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M.
startDate
Date
required
Inclusive start of the backtest window. The engine generates ticks starting at this date, aligned to the interval boundary.
endDate
Date
required
Exclusive end of the backtest window. The engine stops generating ticks at this date.

addRiskSchema

Registers a risk profile. A risk profile is a named collection of validation functions. When a strategy references a riskName, every candidate signal is passed through all validations in sequence before being accepted. Any validation that throws (or returns a rejection object) causes the signal to be silently discarded — the rejection details are emitted to listenRisk subscribers.
import { addRiskSchema } from 'backtest-kit';

addRiskSchema({
  riskName: 'conservative',
  validations: [
    // Validation 1: require take-profit distance ≥ 1%
    ({ pendingSignal, currentPrice }) => {
      const { priceOpen = currentPrice, priceTakeProfit, position } = pendingSignal;
      const tpDistance =
        position === 'long'
          ? ((priceTakeProfit - priceOpen) / priceOpen) * 100
          : ((priceOpen - priceTakeProfit) / priceOpen) * 100;
      if (tpDistance < 1) {
        throw new Error(`TP too close: ${tpDistance.toFixed(2)}%`);
      }
    },
    // Validation 2: require reward-to-risk ratio ≥ 2:1
    ({ pendingSignal, currentPrice }) => {
      const { priceOpen = currentPrice, priceTakeProfit, priceStopLoss, position } = pendingSignal;
      const reward =
        position === 'long' ? priceTakeProfit - priceOpen : priceOpen - priceTakeProfit;
      const risk =
        position === 'long' ? priceOpen - priceStopLoss : priceStopLoss - priceOpen;
      if (reward / risk < 2) {
        throw new Error(`Poor R/R ratio: ${(reward / risk).toFixed(2)}`);
      }
    },
  ],
});
riskName
string
required
Unique identifier for this risk profile. Referenced by addStrategySchema via the riskName field.
validations
IRiskValidationFn[]
required
Array of validation functions. Each function receives an IRiskValidationPayload and must either return normally (signal accepted) or throw an Error (signal rejected with the error message as the rejection note).The payload contains:
  • pendingSignal — the candidate signal
  • currentPrice — VWAP at the time of the check
  • activePositionCount — total number of open positions across all strategies sharing this risk profile

addSizingSchema

Registers a position sizing strategy. Sizing schemas are referenced by action handlers and advanced position management code to compute the dollar amount (cost) dynamically based on account balance and risk parameters.
import { addSizingSchema } from 'backtest-kit';

addSizingSchema({
  sizingName: 'fixed-1pct',
  method: 'fixed-percentage',
  riskPercentage: 1,     // risk 1% of account balance per trade
  minCost: 10,           // minimum position size in dollars
  maxCost: 1000,         // maximum position size in dollars
  maxPercent: 5,         // maximum % of account balance per trade
});
sizingName
string
required
Unique identifier for this sizing schema.
method
'fixed-percentage' | 'kelly' | 'atr-based'
required
Sizing algorithm. fixed-percentage allocates a fixed percentage of account balance. kelly uses the Kelly Criterion scaled by kellyMultiplier. atr-based sizes position relative to the Average True Range for volatility-adjusted risk.
riskPercentage
number
For fixed-percentage and atr-based: maximum percentage of account balance to risk per trade (0–100).
minCost
number
Minimum position size in dollars. Acts as a floor after the sizing calculation.
maxCost
number
Maximum position size in dollars. Acts as a ceiling after the sizing calculation.
maxPercent
number
Maximum percentage of account balance for a single position, regardless of sizing calculation.

Build docs developers (and LLMs) love