Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-redis-postgres-pgpool-docker/llms.txt

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

backtest-kit-redis-postgres-pgpool-docker ships three execution modes — backtest, live, and paper — each gated by a pair of CLI flags and backed by a dedicated entry-point file in src/main/. All three share the same strategy logic and persist adapters; only the data source (historical candles vs. live exchange feed) and position execution path differ.

CLI Flags and the getArgs() Helper

All CLI flags are parsed by src/helpers/getArgs.ts using Node’s built-in parseArgs. The result is memoised via singleshot so every module that calls getArgs() receives the same object:
// src/helpers/getArgs.ts
import { singleshot } from "functools-kit";
import { parseArgs }  from "util";

export const getArgs = singleshot(() => {
  const { values } = parseArgs({
    args: process.argv,
    options: {
      entry:    { type: "boolean", default: false },
      backtest: { type: "boolean", default: false },
      live:     { type: "boolean", default: false },
      paper:    { type: "boolean", default: false },
    },
    strict: false,
    allowPositionals: true,
  });
  return { values };
});
FlagTypePurpose
--entrybooleanAllows the strategy to actually execute. Without it, the module is imported but main() returns immediately — useful for REPL inspection.
--backtestbooleanActivates the backtest entry point (src/main/backtest.ts).
--livebooleanActivates the live trading entry point (src/main/live.ts).
--paperbooleanActivates the paper trading entry point (src/main/paper.ts).
The --ui flag (e.g. --ui ./build/index.cjs) is consumed by the @backtest-kit/cli runtime, not by getArgs. It tells the CLI which compiled bundle to load and enables the web UI on port :60050.

Backtest Mode

Backtest mode replays historical candles stored in PostgreSQL, running the strategy tick-by-tick against the persisted OHLCV data. Because all data is local, an entire month of 1-minute candles can replay in seconds.

Entry point: src/main/backtest.ts

import { getArgs }                           from "../helpers/getArgs";
import ioc                                   from "../lib";
import { Backtest, warmCandles, waitForReady } from "backtest-kit";
import { ExchangeName }                      from "../enum/ExchangeName";
import { FrameName }                         from "../enum/FrameName";
import { StrategyName }                      from "../enum/StrategyName";

const main = async () => {
  const { values } = getArgs();

  if (!values.entry)    return;   // no --entry flag → skip
  if (!values.backtest) return;   // no --backtest flag → skip

  // Wait for PostgreSQL and Redis to be ready
  await ioc.postgresService.waitForInit();
  await ioc.redisService.waitForInit();

  await waitForReady(true);       // true = backtest mode

  // Pre-populate candle cache for the replay window
  await warmCandles({
    exchangeName: ExchangeName.CCXT,
    from:         new Date("2026-01-01T00:00:00Z"),
    to:           new Date("2026-01-31T23:59:59Z"),
    interval:     "1m",
    symbol:       "TRXUSDT",
  });

  // Start the replay in the background
  Backtest.background("TRXUSDT", {
    exchangeName: ExchangeName.CCXT,
    frameName:    FrameName.Jan2026Frame,
    strategyName: StrategyName.Jan2026Strategy,
  });
};

main();
warmCandles fetches OHLCV data from the registered exchange adapter and writes it to the candle-items table via the Candle persist adapter. The candles are fetched once and cached permanently, so subsequent replays of the same window do not hit the exchange. Backtest.background starts the replay loop as a non-blocking background task. The frameName selects the time window and interval defined by addFrameSchema; strategyName selects the getSignal function registered by addStrategySchema.

CLI command

npm run start -- --entry --backtest --ui ./build/index.cjs

Docker Compose

MODE=backtest ENTRY=1 UI=1 docker-compose up -d

Live Mode

Live mode connects to the exchange in real time, subscribes to the candle stream for each symbol, and calls getSignal on every new closed candle. Positions are placed as real orders.

Entry point: src/main/live.ts

import { getArgs }              from "../helpers/getArgs";
import ioc                      from "../lib";
import { Live, waitForReady }   from "backtest-kit";
import { ExchangeName }         from "../enum/ExchangeName";
import { StrategyName }         from "../enum/StrategyName";

const main = async () => {
  const { values } = getArgs();

  if (!values.entry) return;
  if (!values.live)  return;

  await ioc.postgresService.waitForInit();
  await ioc.redisService.waitForInit();

  await waitForReady(false);    // false = live/paper mode

  Live.background("TRXUSDT", {
    exchangeName: ExchangeName.CCXT,
    strategyName: StrategyName.Jan2026Strategy,
  });
};

main();
Notice that live mode does not use warmCandles or a frameName — the frame is only relevant to backtest replay. waitForReady(false) signals to the framework that look-ahead bias protection should not enforce a simulation timestamp ceiling.

CLI command

npm run start -- --entry --live --ui ./build/index.cjs

Docker Compose

MODE=live ENTRY=1 UI=1 docker-compose up -d

Paper Mode

Paper mode uses the exact same Live.background path as live mode, but the CLI and Docker environment route order execution to a simulated (paper) account — no real funds are used. The strategy, persist adapters, and web UI all behave identically to live mode.

Entry point: src/main/paper.ts

import { getArgs }              from "../helpers/getArgs";
import ioc                      from "../lib";
import { Live, waitForReady }   from "backtest-kit";
import { ExchangeName }         from "../enum/ExchangeName";
import { StrategyName }         from "../enum/StrategyName";

const main = async () => {
  const { values } = getArgs();

  if (!values.entry) return;
  if (!values.paper) return;

  await ioc.postgresService.waitForInit();
  await ioc.redisService.waitForInit();

  await waitForReady(false);

  Live.background("TRXUSDT", {
    exchangeName: ExchangeName.CCXT,
    strategyName: StrategyName.Jan2026Strategy,
  });
};

main();

CLI command

npm run start -- --entry --paper --ui ./build/index.cjs

Docker Compose

MODE=paper ENTRY=1 UI=1 docker-compose up -d

Mode Comparison

Backtest

Replays historical OHLCV data stored in PostgreSQL. Requires warmCandles to pre-fill the candle cache and a frameName to define the replay window. Fastest feedback loop.

Live

Streams real candles from the exchange via getCandles in the registered exchange adapter. Places real orders. Requires valid API credentials in the exchange adapter.

Paper

Uses the same live candle stream as live mode but simulates order execution with no real funds. Ideal for validating strategy behaviour in current market conditions.

Additional npm Scripts

Builds the bundle with Rollup then starts the CLI. The -- separator passes additional flags directly to the CLI binary.
npm run start -- --entry --backtest --ui ./build/index.cjs

The --entry Flag: Module-Only vs. Execution Mode

Without --entry every main() function returns immediately after getArgs() is called. This means the strategy module is imported and registered (all addStrategySchema, addFrameSchema, addExchangeSchema calls run) but no connection to PostgreSQL or Redis is opened and no trading loop is started. This is intentional: it lets tooling or the start:repl script load the module for introspection without triggering side-effects.
Use start:repl without --entry to explore the IoC container, run ad-hoc queries against the database services, or inspect enum values — all without starting a live or backtest loop.

Web UI and Health Check

Pass --ui ./build/index.cjs (CLI) or set UI=1 (Docker) to enable the web UI served by @backtest-kit/ui on port 60050.
URLPurpose
http://localhost:60050Strategy dashboard — open positions, closed signals, P&L curves
http://localhost:60050/api/v1/health/health_checkHealth check endpoint polled by Docker every 30 s
The Docker Compose healthcheck pings the health endpoint every 30 seconds with a 10-second timeout. If the container fails three consecutive checks it is marked unhealthy.
Port 60050 is bound on all interfaces by default. In production environments, place a reverse proxy in front of the UI or restrict access with firewall rules.

Backtest.background Call Pattern

When adding a new symbol or strategy context, follow this pattern in src/main/backtest.ts:
// Warm candles for each symbol you intend to replay
await warmCandles({
  exchangeName: ExchangeName.CCXT,
  from:         new Date("2026-01-01T00:00:00Z"),
  to:           new Date("2026-01-31T23:59:59Z"),
  interval:     "1m",
  symbol:       "BTCUSDT",
});

// Start a background replay for each symbol
Backtest.background("BTCUSDT", {
  exchangeName: ExchangeName.CCXT,
  frameName:    FrameName.Jan2026Frame,
  strategyName: StrategyName.Jan2026Strategy,
});
Multiple Backtest.background calls can run in parallel — each symbol gets its own independent replay loop. The persist adapters use (symbol, strategyName, exchangeName) as the unique context key, so there is no state bleed between symbols.

Build docs developers (and LLMs) love