Skip to main content

Documentation Index

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

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

A strategy in backtest-kit is a named unit of trading logic registered at startup time through addStrategySchema. Once registered, the engine resolves it by name when Backtest.background or Live.background is called, invoking getSignal for every new position entry and routing lifecycle events to any attached listeners. Strategy files live under ./content/ and are loaded by the CLI at runtime — they are never bundled into @pro/* packages, so they can be edited and swapped without rebuilding the monorepo.

addStrategySchema

Registers a named strategy for use by the parallel runner.
addStrategySchema({
  strategyName: string;
  getSignal: (symbol: string, when: Date, currentPrice: number) => Promise<SignalResult>;
})

Parameters

strategyName
string
required
Unique identifier for this strategy. Must match the strategyName passed to Backtest.background or Live.background. In the apr_2026 example this is "apr_2026_strategy".
getSignal
function
required
Async function called by the engine when it needs a new position signal for a given symbol. Receives the symbol string, the current backtest timestamp as a Date, and the close price of the current candle.

Event listeners

Strategy files attach listeners for the two main lifecycle events: active-position ticks and errors. Both listeners are registered at module evaluation time and persist for the lifetime of the process.

listenActivePing

Called on every candle tick while a position is active for the symbol. The handler receives a context object with the current market state and is the primary place to implement ladder-averaging, profit-target checks, and trailing-stop logic.
listenActivePing(handler: (ctx: {
  symbol: string;
  currentPrice: number;
  data: unknown;
  timestamp: number;
}) => Promise<void>)
handler
function
required
Async callback invoked on every active candle tick.

listenError

Called when an unhandled error occurs inside the strategy runtime.
listenError(handler: (error: Error) => void)

Helper functions

These functions from backtest-kit are used inside listenActivePing handlers to implement ladder-averaging and position management.
Returns the array of currently open position entries for the given symbol. Use .length to count ladder steps.
const { length: steps } = await getPositionEntries(symbol);
Returns true if a new entry at currentPrice would fall within the percentage band of an existing entry. Prevents stacking duplicate entries at the same price level.
const hasOverlap = await getPositionEntryOverlap(symbol, currentPrice, {
  upperPercent: LADDER_UPPER_STEP, // 5
  lowerPercent: LADDER_LOWER_STEP, // 1
});
Adds a new ladder step to the open position at the current price. The engine averages the entry price and adjusts the overall stop-loss accordingly.
await commitAverageBuy(symbol, LADDER_STEP_COST);
Returns the current unrealized PnL as a percentage (e.g., 3.0 for +3%). Used to trigger target-profit exits.
const currentProfit = await getPositionPnlPercent(symbol);
Closes all pending position entries for the symbol and records a note. The note field is a markdown string stored in the position record for review in @backtest-kit/ui.
await commitClosePending(symbol, {
  id: "unknown",
  note: str.newline("# Позиция закрыта по target pnl"),
});
Structured JSONL logging used throughout strategy files. Log.info records operational events; Log.debug records diagnostic data such as error details.
Log.info("position closed due to the target pnl reached", { symbol, data });
Log.debug("error", { error: errorData(error), message: getErrorMessage(error) });

Full example: apr_2026.strategy.ts

The reference strategy implements a ladder-averaging long strategy with a hard stop-loss, a configurable maximum number of steps, and a target-profit exit. Two listenActivePing handlers run on every active tick: one adds a new ladder step if conditions are met, and the other checks for the profit target and closes the position.
Test strategy logic in Mode B (--backtest without --entry) against a single symbol before enabling Mode A parallel execution. Mode B is faster to iterate and produces isolated, deterministic results for a single trading pair.
// content/apr_2026.strategy/apr_2026.strategy.ts
import {
  addStrategySchema,
  listenError,
  listenActivePing,
  Log,
  Position,
  commitClosePending,
  getPositionPnlPercent,
  getPositionEntryOverlap,
  getPositionEntries,
  commitAverageBuy,
} from "backtest-kit";
import { errorData, getErrorMessage, str } from "functools-kit";

const HARD_STOP = 25.0;
const TARGET_PROFIT = 3;

const LADDER_STEP_COST = 100;
const LADDER_UPPER_STEP = 5;
const LADDER_LOWER_STEP = 1;

const LADDER_MAX_STEPS = 10;

addStrategySchema({
  strategyName: "apr_2026_strategy",
  getSignal: async (symbol, when, currentPrice) => {
    console.log(symbol, when.getTime())
    return {
      position: "long",
      ...Position.moonbag({
        position: "long",
        currentPrice,
        percentStopLoss: HARD_STOP,
      }),
      minuteEstimatedTime: 50,
      cost: LADDER_STEP_COST,
    };
  },
});

listenActivePing(async ({ symbol, currentPrice }) => {
  const { length: steps } = await getPositionEntries(symbol);
  if (steps >= LADDER_MAX_STEPS) {
    return;
  }
  const hasOverlap = await getPositionEntryOverlap(symbol, currentPrice, {
    upperPercent: LADDER_UPPER_STEP,
    lowerPercent: LADDER_LOWER_STEP,
  });
  if (hasOverlap) {
    return;
  }
  await commitAverageBuy(symbol, LADDER_STEP_COST);
});


listenActivePing(async ({ symbol, data, timestamp }) => {
  console.log("active", symbol, timestamp)
  const currentProfit = await getPositionPnlPercent(symbol);
  if (currentProfit < TARGET_PROFIT) {
    return;
  }
  Log.info("position closed due to the target pnl reached", {
    symbol,
    data,
  });
  await commitClosePending(symbol, {
    id: "unknown",
    note: str.newline(
      "# Позиция закрыта по target pnl",
    ),
  });
});

listenError((error) => {
  console.log(error);
  Log.debug("error", {
    error: errorData(error),
    message: getErrorMessage(error),
  });
});

Build docs developers (and LLMs) love