Skip to main content

Documentation Index

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

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

A frame defines the backtest time window — the start date, end date, and candle interval that backtest-kit uses to drive the historical replay. An exchange schema wires in the market data source, providing OHLCV candles, order book snapshots, and price/quantity formatting functions. This project ships a single ccxt Binance exchange schema used identically across backtest, live, and paper modes.

addFrameSchema

Frame schemas are registered at module load time with addFrameSchema. The jan_2026.frame.ts file defines a one-month backtest window at 1-minute resolution:
// src/logic/frame/jan_2026.frame.ts
import { addFrameSchema } from "backtest-kit";
import { FrameName } from "../../enum/FrameName";

addFrameSchema({
  frameName: FrameName.Jan2026Frame,
  interval: "1m",
  startDate: new Date("2026-01-01T00:00:00Z"),
  endDate: new Date("2026-01-31T23:59:59Z"),
  note: "January 2026",
});
Each field in the schema object:
FieldTypeDescription
frameNamestringUnique identifier for this frame, sourced from the FrameName enum
intervalCandleIntervalCandle interval — "1m" through "1d". Drives the tick frequency during backtest replay
startDateDateInclusive start of the backtest window (UTC)
endDateDateInclusive end of the backtest window (UTC)
notestringHuman-readable label shown in the UI
The FrameName enum maps enum members to the string identifiers that backtest-kit uses internally:
// src/enum/FrameName.ts
export enum FrameName {
    Jan2026Frame = "jan_2026_frame",
}
The string value "jan_2026_frame" must match the frameName passed to Backtest.background() in the entry point. A mismatch causes backtest-kit to report an unknown frame error at startup.

addExchangeSchema

Exchange schemas are registered in modules/backtest.module.ts, modules/live.module.ts, and modules/paper.module.ts — all three files contain an identical registration, since the same Binance ccxt adapter is used in every mode. The schema is registered with addExchangeSchema and identified by exchangeName:
// modules/live.module.ts (identical in backtest.module.ts and paper.module.ts)
import { addExchangeSchema, addFrameSchema, roundTicks, setConfig } from "backtest-kit";
import { singleshot } from "functools-kit";
import ccxt from "ccxt";

setConfig({
  CC_MAX_STOPLOSS_DISTANCE_PERCENT: 100,
});

const getExchange = singleshot(async () => {
  const exchange = new ccxt.binance({
    options: {
      defaultType: "spot",
      adjustForTimeDifference: true,
      recvWindow: 60000,
    },
    enableRateLimit: true,
  });
  await exchange.loadMarkets();
  return exchange;
});

addExchangeSchema({
  exchangeName: "ccxt-exchange",
  getCandles: async (symbol, interval, since, limit) => { /* ... */ },
  getOrderBook: async (symbol, depth, _from, _to, backtest) => { /* ... */ },
  formatPrice: async (symbol, price) => { /* ... */ },
  formatQuantity: async (symbol, quantity) => { /* ... */ },
});

Lazy exchange initialisation with singleshot

The getExchange factory is wrapped with singleshot from functools-kit. This ensures that new ccxt.binance(...) and exchange.loadMarkets() are called exactly once across the lifetime of the process, regardless of how many times getCandles, getOrderBook, formatPrice, or formatQuantity are invoked concurrently. Subsequent calls receive the already-initialised exchange instance from a cached Promise.

getCandles

Fetches OHLCV data from Binance and maps the raw tuple array to backtest-kit’s CandleData shape:
getCandles: async (symbol, interval, since, limit) => {
  const exchange = await getExchange();
  const candles = await exchange.fetchOHLCV(
    symbol,
    interval,
    since.getTime(),
    limit,
  );
  return candles.map(([timestamp, open, high, low, close, volume]) => ({
    timestamp,
    open,
    high,
    low,
    close,
    volume,
  }));
},
since is passed as a Date by backtest-kit and converted to milliseconds via .getTime() to match the ccxt fetchOHLCV API. The returned array must be in ascending timestamp order.

getOrderBook

Fetches a live order book from Binance. The function accepts a backtest boolean flag — when true (historical replay), it throws immediately because there is no meaningful order book data to return for a past timestamp:
getOrderBook: async (symbol, depth, _from, _to, backtest) => {
  if (backtest) {
    throw new Error(
      "Order book fetching is not supported in backtest mode for the default exchange schema. Please implement it according to your needs.",
    );
  }
  const exchange = await getExchange();
  const bookData = await exchange.fetchOrderBook(symbol, depth);
  return {
    symbol,
    asks: bookData.asks.map(([price, quantity]) => ({
      price: String(price),
      quantity: String(quantity),
    })),
    bids: bookData.bids.map(([price, quantity]) => ({
      price: String(price),
      quantity: String(quantity),
    })),
  };
},
Order book fetching throws in backtest mode. If your strategy calls getOrderBook during a backtest, the call will throw with the message above. To support order book logic in backtests, replace this implementation with a custom mock that reconstructs the book from OHLCV data or a stored snapshot.

formatPrice

Rounds a raw price to the market’s tick size using roundTicks from backtest-kit. Falls back to ccxt’s priceToPrecision if no tick size is available:
formatPrice: async (symbol, price) => {
  const exchange = await getExchange();
  const market = exchange.market(symbol);
  const tickSize = market.limits?.price?.min || market.precision?.price;
  if (tickSize !== undefined) {
    return roundTicks(price, tickSize);
  }
  return exchange.priceToPrecision(symbol, price);
},

formatQuantity

Rounds a raw quantity to the market’s step size using roundTicks. Falls back to amountToPrecision if no step size is available:
formatQuantity: async (symbol, quantity) => {
  const exchange = await getExchange();
  const market = exchange.market(symbol);
  const stepSize = market.limits?.amount?.min || market.precision?.amount;
  if (stepSize !== undefined) {
    return roundTicks(quantity, stepSize);
  }
  return exchange.amountToPrecision(symbol, quantity);
},

ExchangeName enum

The exchangeName string passed to addExchangeSchema must match the value used in Backtest.background(), Live.background(), and warmCandles(). The ExchangeName enum is the single source of truth:
// src/enum/ExchangeName.ts
export enum ExchangeName {
    CCXT = "ccxt-exchange",
}

setConfig

setConfig from backtest-kit is called before addExchangeSchema to override framework defaults:
setConfig({
  CC_MAX_STOPLOSS_DISTANCE_PERCENT: 100,
});
CC_MAX_STOPLOSS_DISTANCE_PERCENT: 100 raises the maximum allowed stop-loss distance to 100% of the entry price. The default value is much lower and would reject wide stops. Since the January 2026 signals use HARD_STOP = 1.0 (percent), this setting is not strictly required for normal operation, but it prevents backtest-kit from silently clamping or rejecting stop levels on volatile assets.
The ccxt Binance adapter is initialised with two important options for live and paper modes: adjustForTimeDifference: true automatically corrects for clock skew between your server and Binance, and recvWindow: 60000 extends the request validity window to 60 seconds. Both options reduce the frequency of Timestamp for this request is outside of the recvWindow errors in production.

Build docs developers (and LLMs) love