📟 @backtest-kit/cli

Zero-boilerplate CLI for launching backtests, paper trading, and live trading. Run any backtest-kit strategy from the command line — no setup code required.

screenshot

Ask DeepWiki npm TypeScript

Point the CLI at your strategy file, choose a mode, and it handles exchange connectivity, candle caching, UI dashboard, and Telegram notifications for you.

📚 Backtest Kit Docs | 🌟 GitHub

  • 🚀 Zero Config: Run npx @backtest-kit/cli --backtest ./strategy.mjs — no boilerplate needed
  • 🔄 Three Modes: Backtest on historical data, paper trade on live prices, or deploy live bots
  • 💾 Auto Candle Cache: Warms OHLCV cache for all required intervals before backtest starts
  • 🌐 Web Dashboard: Launch @backtest-kit/ui with a single --ui flag
  • 📬 Telegram Alerts: Send formatted trade notifications with charts via --telegram
  • 🔌 Default Binance: CCXT Binance exchange schema registered automatically when none is provided
  • 🧩 Module Hooks: Drop a live.module.mjs, paper.module.mjs, or backtest.module.mjs to register a Broker adapter. No manual wiring needed.
  • 🗃️ Transactional Live Orders: Broker adapter intercepts every trade mutation before internal state changes — exchange rejection rolls back the operation atomically.
  • 🔑 Pluggable Logger: Override the built-in logger with setLogger() from your strategy module
  • 🛑 Graceful Shutdown: SIGINT stops the active run and cleans up all subscriptions safely

@backtest-kit/cli wraps the backtest-kit engine and resolves all scaffolding automatically:

Mode Command Line Args Description
Backtest --backtest Run strategy on historical candle data
Paper --paper Live prices, no real orders
Live --live Real trades via exchange API
UI Dashboard --ui Web dashboard at http://localhost:60050
Telegram --telegram Trade notifications with price charts

Add @backtest-kit/cli to your project and wire it up in package.json scripts:

npm install @backtest-kit/cli
{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
"paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
"start": "npx @backtest-kit/cli --live ./src/index.mjs"
},
"dependencies": {
"@backtest-kit/cli": "latest",
"backtest-kit": "latest",
"ccxt": "latest"
}
}

Or run once without installing:

npx @backtest-kit/cli --backtest ./src/index.mjs

Create your strategy entry point (src/index.mjs). The file registers schemas via backtest-kit@backtest-kit/cli is only the runner:

// src/index.mjs
import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
import ccxt from 'ccxt';

// Register exchange
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 frame (backtest only)
addFrameSchema({
frameName: 'feb-2024',
interval: '1m',
startDate: new Date('2024-02-01'),
endDate: new Date('2024-02-29'),
});

// Register strategy
addStrategySchema({
strategyName: 'my-strategy',
interval: '15m',
getSignal: async (symbol) => {
// return signal or null
return null;
},
});

Run a backtest:

npm run backtest -- --symbol BTCUSDT

Run with UI dashboard and Telegram:

npm run backtest -- --symbol BTCUSDT --ui --telegram

Run live trading:

npm start -- --symbol BTCUSDT --ui
Command Line Args Type Description
--backtest boolean Run historical backtest (default: false)
--paper boolean Paper trading (live prices, no orders) (default: false)
--live boolean Run live trading (default: false)
--ui boolean Start web UI dashboard (default: false)
--telegram boolean Enable Telegram notifications (default: false)
--verbose boolean Log each candle fetch (default: false)
--noCache boolean Skip candle cache warming before backtest (default: false)
--symbol string Trading pair (default: "BTCUSDT")
--strategy string Strategy name (default: first registered)
--exchange string Exchange name (default: first registered)
--frame string Backtest frame name (default: first registered)
--cacheInterval string Intervals to pre-cache before backtest (default: "1m, 15m, 30m, 4h")

Positional argument (required): path to your strategy entry point file (set once in package.json scripts).

{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
}
}

Runs the strategy against historical candle data using a registered FrameSchema.

{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs"
}
}
npm run backtest

Before running, the CLI warms the candle cache for every interval in --cacheInterval. On the next run, cached data is used directly — no API calls needed. Pass --noCache to skip this step entirely.

Connects to the live exchange but does not place real orders. Identical code path to live — safe for strategy validation.

{
"scripts": {
"paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs"
}
}
npm run paper

Deploys a real trading bot. Requires exchange API keys configured in your .env or environment.

{
"scripts": {
"start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs"
}
}
npm start

@backtest-kit/cli works out of the box in a monorepo where each strategy lives in its own subdirectory. When the CLI loads your entry point file, it automatically changes the working directory to the file's location — so all relative paths (dump/, modules/, template/) resolve inside that strategy's folder, not the project root.

Internally, ResolveService does the following before executing your entry point:

process.chdir(path.dirname(entryPoint))  // cwd → strategy directory
dotenv.config({ path: rootDir + '/.env' }) // load root .env first
dotenv.config({ path: strategyDir + '/.env', override: true }) // strategy .env overrides

Everything that follows — candle cache warming, report generation, module loading, template resolution — uses the new cwd automatically.

monorepo/
├── package.json # root scripts (one per strategy)
├── .env # shared API keys (exchange, Telegram, etc.)
└── strategies/
├── oct_2025/
│ ├── index.mjs # entry pointregisters exchange/frame/strategy schemas
│ ├── .env # overrides root .env for this strategy
│ ├── modules (optional)
│ | ├── live.module.mjs # broker adapter for --live mode (optional)
│ | ├── paper.module.mjs # broker adapter for --paper mode (optional)
│ | ├── backtest.module.mjs # broker adapter for --backtest mode (optional)
│ ├── template/ # custom Mustache templates (optional)
│ └── dump/ # auto-created: candle cache + backtest reports
└── dec_2025/
├── index.mjs
├── .env
└── dump/
{
"scripts": {
"backtest:oct": "npx @backtest-kit/cli --backtest ./strategies/oct_2025/index.mjs",
"backtest:dec": "npx @backtest-kit/cli --backtest ./strategies/dec_2025/index.mjs"
},
"dependencies": {
"@backtest-kit/cli": "latest",
"backtest-kit": "latest",
"ccxt": "latest"
}
}
npm run backtest:oct
npm run backtest:dec
Resource Path (relative to strategy dir) Isolated
Candle cache ./dump/data/candle/ ✅ per-strategy
Backtest reports ./dump/ ✅ per-strategy
Broker module (live) ./modules/live.module.mjs ✅ per-strategy
Broker module (paper) ./modules/paper.module.mjs ✅ per-strategy
Broker module (backtest) ./modules/backtest.module.mjs ✅ per-strategy
Telegram templates ./template/*.mustache ✅ per-strategy
Environment variables ./.env (overrides root) ✅ per-strategy

Each strategy run produces its own dump/ directory, making it straightforward to compare results across time periods — both by inspection and by pointing an AI agent at a specific strategy folder.

Starts @backtest-kit/ui server. Access the interactive dashboard at:

http://localhost:60050

Customize host/port via environment variables CC_WWWROOT_HOST and CC_WWWROOT_PORT.

Sends formatted HTML messages with 1m / 15m / 1h price charts to your Telegram channel for every position event: opened, closed, scheduled, cancelled, risk rejection, partial profit/loss, trailing stop/take, and breakeven.

Requires CC_TELEGRAM_TOKEN and CC_TELEGRAM_CHANNEL in your environment.

The CLI supports mode-specific module files that are loaded as side-effect imports before the strategy starts. Each file is expected to call Broker.useBrokerAdapter() from backtest-kit to register a broker adapter.

Mode Module file Loaded before
--live ./modules/live.module.mjs Live.background()
--paper ./modules/paper.module.mjs Live.background() (paper)
--backtest ./modules/backtest.module.mjs Backtest.background()

File is resolved relative to cwd (the strategy directory). All of .mjs, .cjs, .ts extensions are tried automatically. Missing module is a soft warning — not an error.

The module file is a side-effect import. When the CLI loads it, your code runs and registers the adapter. From that point on, backtest-kit intercepts every trade-mutating call through the adapter before updating internal state — if the adapter throws, the position state is never changed.

// live.module.mjs
import { Broker } from 'backtest-kit';
import { myExchange } from './exchange.mjs';

class MyBroker {
async onSignalOpenCommit({ symbol, priceOpen, direction }) {
await myExchange.openPosition(symbol, direction, priceOpen);
}

async onSignalCloseCommit({ symbol, priceClosed }) {
await myExchange.closePosition(symbol, priceClosed);
}

async onPartialProfitCommit({ symbol, cost, currentPrice }) {
await myExchange.createOrder({
symbol,
side: 'sell',
quantity: cost / currentPrice,
});
}

async onAverageBuyCommit({ symbol, cost, currentPrice }) {
await myExchange.createOrder({
symbol,
side: 'buy',
quantity: cost / currentPrice,
});
}
}

Broker.useBrokerAdapter(MyBroker);

Broker.enable();
Method Payload type Triggered on
onSignalOpenCommit BrokerSignalOpenPayload Position activation
onSignalCloseCommit BrokerSignalClosePayload SL / TP / manual close
onPartialProfitCommit BrokerPartialProfitPayload PP
onPartialLossCommit BrokerPartialLossPayload PL
onTrailingStopCommit BrokerTrailingStopPayload SL adjustment
onTrailingTakeCommit BrokerTrailingTakePayload TP adjustment
onBreakevenCommit BrokerBreakevenPayload SL moved to entry
onAverageBuyCommit BrokerAverageBuyPayload DCA entry

All methods are optional. Unimplemented hooks are silently skipped. In backtest mode all broker calls are skipped automatically — no adapter code runs during backtests.

import { Broker, IBroker, BrokerSignalOpenPayload, BrokerSignalClosePayload } from 'backtest-kit';

class MyBroker implements Partial<IBroker> {
async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
// place open order on exchange
}

async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
// place close order on exchange
}
}

Broker.useBrokerAdapter(MyBroker);

Broker.enable();

@backtest-kit/cli automatically detects the format of your strategy file and loads it with the appropriate runtime — no flags or configuration required.

Format Extension Runtime Use Case
TypeScript .ts tsx via tsImport() TypeScript strategies with cross-imports (ESM ↔ CJS)
ES Module .mjs Native import() Modern JavaScript with top-level await and ESM syntax
CommonJS .cjs Native require() Legacy or dual-package strategies

Run TypeScript strategy files directly — no tsc compilation step needed. Powered by tsx, which handles cross-format imports transparently:

{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest ./src/index.ts"
},
"dependencies": {
"@backtest-kit/cli": "latest",
"backtest-kit": "latest",
"tsx": "latest"
}
}

Standard ESM format. Supports top-level await, named exports, and import syntax:

{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
}
}

For projects that compile to or use CommonJS. Loaded via require():

{
"scripts": {
"backtest": "npx @backtest-kit/cli --backtest ./dist/index.cjs"
}
}

Create a .env file in your project root:

# Telegram notifications (required for --telegram)
CC_TELEGRAM_TOKEN=your_bot_token_here
CC_TELEGRAM_CHANNEL=-100123456789

# Web UI server (optional, defaults shown)
CC_WWWROOT_HOST=0.0.0.0
CC_WWWROOT_PORT=60050

# Custom QuickChart service URL (optional)
CC_QUICKCHART_HOST=
Variable Default Description
CC_TELEGRAM_TOKEN Telegram bot token (from @BotFather)
CC_TELEGRAM_CHANNEL Telegram channel or chat ID
CC_WWWROOT_HOST 0.0.0.0 UI server bind address
CC_WWWROOT_PORT 60050 UI server port
CC_QUICKCHART_HOST Self-hosted QuickChart instance URL

When your strategy module does not register an exchange, frame, or strategy name, the CLI falls back to built-in defaults and prints a console warning:

Component Default Warning
Exchange CCXT Binance (default_exchange) Warning: The default exchange schema is set to CCXT Binance...
Frame February 2024 (default_frame) Warning: The default frame schema is set to February 2024...
Symbol BTCUSDT
Cache intervals 1m, 15m, 30m, 4h Used if --cacheInterval not provided; skip entirely with --noCache

Note: The default exchange schema does not support order book fetching in backtest mode. If your strategy calls getOrderBook() during backtest, you must register a custom exchange schema with your own snapshot storage.

In addition to the CLI, @backtest-kit/cli can be used as a library — call run() directly from your own script without spawning a child process or parsing CLI flags.

import { run } from '@backtest-kit/cli';

await run(mode, args);
Parameter Description
mode "backtest" | "paper" | "live" — Execution mode
args Mode-specific options (all optional — same defaults as CLI)

run() can be called only once per process. A second call throws "Should be called only once".

Backtest (mode: "backtest"):

Field Type Description
entryPoint string Path to strategy entry point file
symbol string Trading pair (default: "BTCUSDT")
strategy string Strategy name (default: first registered)
exchange string Exchange name (default: first registered)
frame string Frame name (default: first registered)
cacheInterval CandleInterval[] Intervals to pre-cache (default: ["1m","15m","30m","1h","4h"])
noCache boolean Skip candle cache warming (default: false)
verbose boolean Log each candle fetch (default: false)

Paper and Live (mode: "paper" / mode: "live"):

Field Type Description
entryPoint string Path to strategy entry point file
symbol string Trading pair (default: "BTCUSDT")
strategy string Strategy name (default: first registered)
exchange string Exchange name (default: first registered)
verbose boolean Log each candle fetch (default: false)

Backtest:

import { run } from '@backtest-kit/cli';

await run('backtest', {
entryPoint: './src/index.mjs',
symbol: 'ETHUSDT',
frame: 'feb-2024',
cacheInterval: ['1m', '15m', '1h'],
verbose: true,
});

Paper trading:

import { run } from '@backtest-kit/cli';

await run('paper', {
entryPoint: './src/index.mjs',
symbol: 'BTCUSDT',
});

Live trading:

import { run } from '@backtest-kit/cli';

await run('live', {
entryPoint: './src/index.mjs',
symbol: 'BTCUSDT',
verbose: true,
});

Instead of writing infrastructure code for every project:

❌ Without @backtest-kit/cli (manual setup)

// index.ts
import { setLogger, setConfig, Storage, Notification, Report, Markdown } from 'backtest-kit';
import { serve } from '@backtest-kit/ui';

setLogger({ log: console.log, ... });
Storage.enable();
Notification.enable();
Report.enable();
Markdown.disable();

// ... parse CLI args manually
// ... register exchange schema
// ... warm candle cache
// ... set up Telegram bot
// ... handle SIGINT gracefully
// ... load and run backtest

✅ With @backtest-kit/cli (one script)

{ "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
npm run backtest

Benefits:

  • 🚀 From zero to running backtest in seconds
  • 💾 Automatic candle cache warming with retry logic
  • 🌐 Production-ready web dashboard out of the box
  • 📬 Telegram notifications with price charts — no chart code needed
  • 🛑 Graceful shutdown on SIGINT — no hanging processes
  • 🔌 Works with any backtest-kit strategy file as-is
  • 🧩 Broker adapter hooks via side-effect module files — no CLI internals to touch

Fork/PR on GitHub.

MIT © tripolskypetr