@backtest-kit/cli 13.6.0 โ†’ 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ๐Ÿ“Ÿ @backtest-kit/cli
4
4
 
5
- > Zero-boilerplate CLI for launching backtests, paper trading, and live trading. Run any backtest-kit strategy from the command line โ€” no setup code required.
5
+ > Zero-boilerplate CLI for [backtest-kit](https://www.npmjs.com/package/backtest-kit). Point it at a strategy file, pick a mode, and it handles exchange connectivity, candle caching, the web dashboard, Telegram alerts, and graceful shutdown for you โ€” no setup code.
6
6
 
7
7
  ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
8
8
 
@@ -10,1898 +10,864 @@
10
10
  [![npm](https://img.shields.io/npm/v/@backtest-kit/cli.svg?style=flat-square)](https://npmjs.org/package/@backtest-kit/cli)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
12
12
 
13
- Point the CLI at your strategy file, choose a mode, and it handles exchange connectivity, candle caching, UI dashboard, and Telegram notifications for you.
13
+ ๐Ÿ“š **[Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** ยท ๐ŸŒŸ **[Reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example)** ยท ๐Ÿ™ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
14
14
 
15
- ๐Ÿ“š **[Backtest Kit Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** | ๐ŸŒŸ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
15
+ > **New here?** The fastest real setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) โ€” a working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there, not from scratch.
16
16
 
17
- > **New to backtest-kit?** The fastest way to get a real, production-ready setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) โ€” a fully working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there instead of from scratch.
17
+ ---
18
18
 
19
- ## ๐ŸŽ๏ธ CLI Init
20
-
21
- Minimal scaffold โ€” all boilerplate stays inside @backtest-kit/cli:
22
-
23
- ```bash
24
- npx @backtest-kit/cli --init --output backtest-kit-project
25
- cd backtest-kit-project
26
- npm install
27
- npm start -- --help
28
- ```
29
-
30
- ## ๐Ÿค” Philosophy
31
-
32
- `@backtest-kit/cli` is designed to do **two things well** โ€” and the same tool covers both.
33
-
34
- **1. The lightest possible runner for a solo quant on day one.**
35
-
36
- You write a strategy file, point the CLI at it, and you're trading. No DI container to learn, no project scaffold to fight, no infrastructure code to copy-paste. One developer, one strategy, one command:
37
-
38
- ```bash
39
- npx @backtest-kit/cli --init
40
- npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts
41
- ```
42
-
43
- That's the whole onboarding. The first day you have an idea, you can backtest it. The first week you have an edge, you can paper-trade it. The first month you have a P&L, you can run it live โ€” same CLI, different flag.
44
-
45
- **2. Built-in monorepo tooling for when the business takes off.**
46
-
47
- The moment you start making money is the worst possible moment to rewrite your stack in another language. So the CLI is also a monorepo-grade runner from day one โ€” even if you don't use it that way at first.
48
-
49
- ```
50
- monorepo/
51
- โ”œโ”€โ”€ content/
52
- โ”‚ โ”œโ”€โ”€ feb_2026.strategy/
53
- โ”‚ โ”œโ”€โ”€ feb_2026.strategy.ts # strategy production code
54
- โ”‚ โ”œโ”€โ”€ feb_2026.test.ts # developer playground
55
- โ”œโ”€โ”€ packages/
56
- โ”‚ โ”œโ”€โ”€ shared-broker/ # shared broker code
57
- โ”‚ โ”œโ”€โ”€ shared-signals/ # common indicators (RSI, MACD)
58
- ```
59
-
60
-
61
- As a result: you used to backtest your first idea is the same tool you use to run a desk of strategies in production. No rewrite, no language switch, no framework migration when the business scales โ€” only more files in the monorepo.
62
-
63
- ## โœจ Features
64
-
65
- - ๐Ÿš€ **Zero Config**: Run `npx @backtest-kit/cli --backtest ./strategy.mjs` โ€” no boilerplate needed
66
- - ๐Ÿ”„ **Four Modes**: Backtest on historical data, walker A/B comparison, paper trade on live prices, or deploy live bots
67
- - ๐Ÿ’พ **Auto Candle Cache**: Warms OHLCV cache for all required intervals before backtest starts
68
- - ๐ŸŒ **Web Dashboard**: Launch `@backtest-kit/ui` with a single `--ui` flag
69
- - ๐Ÿ“ฌ **Telegram Alerts**: Send formatted trade notifications with charts via `--telegram`
70
- - ๐Ÿ”Œ **Default Binance**: CCXT Binance exchange schema registered automatically when none is provided
71
- - ๐Ÿงฉ **Module Hooks**: Drop a `live.module.mjs`, `paper.module.mjs`, or `backtest.module.mjs` to register a `Broker` adapter. No manual wiring needed.
72
- - ๐Ÿ—ƒ๏ธ **Transactional Live Orders**: Broker adapter intercepts every trade mutation before internal state changes โ€” exchange rejection rolls back the operation atomically.
73
- - ๐Ÿ”‘ **Pluggable Logger**: Override the built-in logger with `setLogger()` from your strategy module
74
- - ๐Ÿ›‘ **Graceful Shutdown**: SIGINT stops the active run and cleans up all subscriptions safely
75
-
76
- ## ๐Ÿ“‹ What It Does
77
-
78
- `@backtest-kit/cli` wraps the `backtest-kit` engine and resolves all scaffolding automatically:
79
-
80
- | Mode | Command Line Args | Description |
81
- |------------------|----------------------------|----------------------------------------------|
82
- | **Backtest** | `--backtest` | Run strategy on historical candle data |
83
- | **Walker** | `--walker` | A/B compare multiple strategies on the same historical data |
84
- | **Paper** | `--paper` | Live prices, no real orders |
85
- | **Live** | `--live` | Real trades via exchange API |
86
- | **Main** | `--main` | Run a custom entry point with a prepared environment, no trading harness |
87
- | **UI Dashboard** | `--ui` | Web dashboard at `http://localhost:60050` |
88
- | **Telegram** | `--telegram` | Trade notifications with price charts |
89
- | **PineScript** | `--pine` | Run a local `.pine` indicator against exchange data |
90
- | **Pine Editor** | `--editor` | Open the visual Pine Script editor in the browser |
91
- | **Candle Dump** | `--dump` | Fetch and save raw OHLCV candles to a file |
92
- | **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price and direction |
93
- | **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live broker adapter |
94
- | **Flush** | `--flush` | Delete report/log/markdown/agent folders from strategy dump dir |
95
- | **Init Project** | `--init` | Scaffold a new backtest-kit project |
96
-
97
- ## ๐Ÿš€ Installation
98
-
99
- Add `@backtest-kit/cli` to your project and wire it up in `package.json` scripts:
19
+ ## ๐Ÿš€ Quick Start
100
20
 
101
21
  ```bash
102
- npm install @backtest-kit/cli
103
- ```
104
-
105
- ```json
106
- {
107
- "scripts": {
108
- "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
109
- "paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
110
- "start": "npx @backtest-kit/cli --live ./src/index.mjs"
111
- },
112
- "dependencies": {
113
- "@backtest-kit/cli": "latest",
114
- "backtest-kit": "latest",
115
- "ccxt": "latest"
116
- }
117
- }
22
+ # Scaffold a project (boilerplate stays inside the CLI; docs auto-fetched)
23
+ npx @backtest-kit/cli --init --output backtest-kit-project
24
+ cd backtest-kit-project && npm install && npm start -- --help
118
25
  ```
119
26
 
120
- Or run once without installing:
27
+ The whole onboarding is: write a strategy file that registers schemas via `backtest-kit`, point the CLI at it, choose a flag.
121
28
 
122
29
  ```bash
123
- npx @backtest-kit/cli --backtest ./src/index.mjs
30
+ npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts --symbol BTCUSDT
124
31
  ```
125
32
 
126
- ## ๐Ÿ“– Quick Start
127
-
128
- Create your strategy entry point (`src/index.mjs`). The file registers schemas via `backtest-kit` โ€” `@backtest-kit/cli` is only the runner:
33
+ <details>
34
+ <summary>The strategy entry point (the CLI is only the runner)</summary>
129
35
 
130
36
  ```javascript
131
- // src/index.mjs
37
+ // src/index.mjs โ€” registers schemas via backtest-kit; @backtest-kit/cli just runs it
132
38
  import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
133
39
  import ccxt from 'ccxt';
134
40
 
135
- // Register exchange
136
41
  addExchangeSchema({
137
42
  exchangeName: 'binance',
138
43
  getCandles: async (symbol, interval, since, limit) => {
139
44
  const exchange = new ccxt.binance();
140
45
  const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
141
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
142
- timestamp, open, high, low, close, volume,
143
- }));
46
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
47
+ ({ timestamp, open, high, low, close, volume }));
144
48
  },
145
49
  formatPrice: (symbol, price) => price.toFixed(2),
146
50
  formatQuantity: (symbol, quantity) => quantity.toFixed(8),
147
51
  });
148
52
 
149
- // Register frame (backtest only)
150
- addFrameSchema({
151
- frameName: 'feb-2024',
152
- interval: '1m',
153
- startDate: new Date('2024-02-01'),
154
- endDate: new Date('2024-02-29'),
155
- });
156
-
157
- // Register strategy
158
- addStrategySchema({
159
- strategyName: 'my-strategy',
160
- interval: '15m',
161
- getSignal: async (symbol) => {
162
- // return signal or null
163
- return null;
164
- },
165
- });
166
- ```
167
-
168
- Run a backtest:
169
-
170
- ```bash
171
- npm run backtest -- --symbol BTCUSDT
172
- ```
173
-
174
- Run with UI dashboard and Telegram:
175
-
176
- ```bash
177
- npm run backtest -- --symbol BTCUSDT --ui --telegram
178
- ```
179
-
180
- Run live trading:
181
-
182
- ```bash
183
- npm start -- --symbol BTCUSDT --ui
184
- ```
185
-
186
- ## ๐ŸŽ›๏ธ CLI Flags
187
-
188
- | Command Line Args | Type | Description |
189
- |---------------------------|---------|--------------------------------------------------------------------|
190
- | `--backtest` | boolean | Run historical backtest (default: `false`) |
191
- | `--walker` | boolean | Run Walker A/B strategy comparison (default: `false`) |
192
- | `--paper` | boolean | Paper trading (live prices, no orders) (default: `false`) |
193
- | `--live` | boolean | Run live trading (default: `false`) |
194
- | `--main` | boolean | Run a custom entry point with a prepared environment, no trading harness (default: `false`) |
195
- | `--ui` | boolean | Start web UI dashboard (default: `false`) |
196
- | `--telegram` | boolean | Enable Telegram notifications (default: `false`) |
197
- | `--verbose` | boolean | Log each candle fetch (default: `false`) |
198
- | `--noCache` | boolean | Skip candle cache warming before backtest (default: `false`) |
199
- | `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before backtest run (default: `false`) |
200
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
201
- | `--strategy` | string | Strategy name (default: first registered) |
202
- | `--exchange` | string | Exchange name (default: first registered) |
203
- | `--frame` | string | Backtest frame name (default: first registered) |
204
- | `--cacheInterval` | string | Intervals to pre-cache before backtest (default: `"1m, 15m, 30m, 4h"`) |
205
- | `--brokerdebug` | boolean | Fire a single broker commit against the live broker adapter (default: `false`) |
206
- | `--commit` | string | Commit type for `--brokerdebug` (default: `"signal-open"`) |
207
-
208
- **Positional argument (required):** path to your strategy entry point file (set once in `package.json` scripts).
209
-
210
- ```json
211
- {
212
- "scripts": {
213
- "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
214
- }
215
- }
216
- ```
217
-
218
- ## ๐Ÿƒ Execution Modes
219
-
220
- ### Backtest
221
-
222
- Runs the strategy against historical candle data using a registered `FrameSchema`.
223
-
224
- ```json
225
- {
226
- "scripts": {
227
- "backtest": "npx @backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs"
228
- }
229
- }
230
- ```
231
-
232
- ```bash
233
- npm run backtest
234
- ```
235
-
236
- Before running, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from the strategy's `dump/` directory, then 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 cache warming, `--noFlush` to keep existing output folders.
237
-
238
- ### Paper Trading
239
-
240
- Connects to the live exchange but does not place real orders. Identical code path to live โ€” safe for strategy validation.
241
-
242
- ```json
243
- {
244
- "scripts": {
245
- "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs"
246
- }
247
- }
248
- ```
53
+ addFrameSchema({ frameName: 'feb-2024', interval: '1m',
54
+ startDate: new Date('2024-02-01'), endDate: new Date('2024-02-29') });
249
55
 
250
- ```bash
251
- npm run paper
56
+ addStrategySchema({ strategyName: 'my-strategy', interval: '15m',
57
+ getSignal: async (symbol) => null }); // return a signal or null
252
58
  ```
253
59
 
254
- ### Live Trading
255
-
256
- Deploys a real trading bot. Requires exchange API keys configured in your `.env` or environment.
60
+ Wire it into `package.json` once and the positional path never changes:
257
61
 
258
62
  ```json
259
63
  {
260
64
  "scripts": {
261
- "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs"
262
- }
263
- }
264
- ```
265
-
266
- ```bash
267
- npm start
268
- ```
269
-
270
- ### Walker โ€” A/B Strategy Comparison
271
-
272
- Runs the same historical period against multiple strategy files and prints a ranked comparison report. Use it to pick the best variant before deploying to backtest or live.
273
-
274
- ```json
275
- {
276
- "scripts": {
277
- "walker": "npx @backtest-kit/cli --walker --symbol BTCUSDT --noCache ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts"
278
- }
65
+ "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
66
+ "paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
67
+ "start": "npx @backtest-kit/cli --live ./src/index.mjs"
68
+ },
69
+ "dependencies": { "@backtest-kit/cli": "latest", "backtest-kit": "latest", "ccxt": "latest" }
279
70
  }
280
71
  ```
281
72
 
282
73
  ```bash
283
- npm run walker
74
+ npm run backtest -- --symbol BTCUSDT --ui --telegram # add integrations with flags
284
75
  ```
285
76
 
286
- Each positional argument is a separate strategy entry point. Before loading them, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from each entry point's `dump/` directory. Pass `--noFlush` to keep existing output. All files are loaded without changing `process.cwd()` โ€” `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files.
77
+ </details>
287
78
 
288
- If no frame is registered, the CLI falls back to the last 31 days from `Date.now()` with a console warning.
79
+ ---
289
80
 
290
- **Walker-specific flags:**
81
+ ## ๐Ÿค” Philosophy โ€” React vs Next.js, but for trading
291
82
 
292
- | Flag | Type | Description |
293
- |------|------|-------------|
294
- | `--walker` | boolean | Enable Walker comparison mode |
295
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
296
- | `--cacheInterval` | string | Intervals to pre-cache (default: `"1m, 15m, 30m, 4h"`) |
297
- | `--noCache` | boolean | Skip candle cache warming (default: `false`) |
298
- | `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before walker run (default: `false`) |
299
- | `--verbose` | boolean | Log each candle fetch and strategy progress (default: `false`) |
300
- | `--output` | string | Output file base name (default: `walker_{SYMBOL}_{TIMESTAMP}`) |
301
- | `--json` | boolean | Save results as JSON to `./dump/<output>.json` |
302
- | `--markdown` | boolean | Save report as Markdown to `./dump/<output>.md` |
83
+ `@backtest-kit/cli` does **two things well with one tool**.
303
84
 
304
- **Output modes:**
85
+ **1. The lightest runner for a solo quant on day one.** Write a strategy, point the CLI at it, you're trading. No DI container to learn, no scaffold to fight, no infra to copy-paste. The day you have an idea you can backtest it; the week you have an edge you can paper-trade it; the month you have a P&L you can run it live โ€” same CLI, different flag.
305
86
 
306
- - No flag โ€” print Markdown report to stdout
307
- - `--json` โ€” save `Walker.getData()` result as JSON and exit
308
- - `--markdown` โ€” save `Walker.getReport()` as `.md` file and exit
87
+ **2. A monorepo-grade runner for when the business takes off.** The moment you start making money is the worst moment to rewrite your stack. So the CLI is monorepo-ready from day one even if you don't use it that way at first: per-strategy `.env`, per-strategy broker modules, folder-based import aliases, isolated dump dirs. The tool you backtested your first idea with is the tool that runs a desk of strategies in production โ€” no rewrite, no language switch, only more files.
309
88
 
310
- **Module hook:** `./modules/walker.module` is loaded automatically before the comparison starts (same rules as other modes โ€” `.ts`, `.mjs`, `.cjs` tried in order).
89
+ ---
311
90
 
312
- **Example โ€” compare three variants and save the report:**
91
+ ## ๐Ÿ—บ๏ธ Mode matrix
313
92
 
314
- ```bash
315
- npx @backtest-kit/cli --walker \
316
- --symbol BTCUSDT \
317
- --noCache \
318
- --markdown \
319
- --output feb_2026_comparison \
320
- ./content/feb_2026_v1.strategy.ts \
321
- ./content/feb_2026_v2.strategy.ts \
322
- ./content/feb_2026_v3.strategy.ts
323
- # โ†’ ./dump/feb_2026_comparison.md
324
- ```
93
+ Every invocation is **one mode** (a primary flag) + a positional strategy/entry path + optional modifiers. `--ui` and `--telegram` are integrations that attach to any trading mode.
325
94
 
326
- ### Main โ€” Custom Entry Point, No Trading Harness
95
+ | Mode | Flag | What it does |
96
+ |------|------|--------------|
97
+ | **Backtest** | `--backtest` | Run a strategy on historical candle data (uses a `FrameSchema`) |
98
+ | **Paper** | `--paper` | Live prices, no real orders โ€” identical code path to live |
99
+ | **Live** | `--live` | Real trades via exchange API |
100
+ | **Walker** | `--walker` | A/B-compare multiple strategies on the same history, ranked report |
101
+ | **Main** | `--main` | Run a custom entry point with the full environment prepared, **no** trading harness |
102
+ | **Pine** | `--pine` | Run a local `.pine` indicator against exchange data |
103
+ | **Editor** | `--editor` | Open the visual Pine Script editor in the browser |
104
+ | **Candle Dump** | `--dump` | Fetch & save raw OHLCV candles to a file |
105
+ | **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price & direction |
106
+ | **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live adapter |
107
+ | **Flush** | `--flush` | Delete report/log/markdown/agent folders from a strategy dump dir |
108
+ | **Init** | `--init` | Scaffold a new project |
109
+ | **Docker** | `--docker` | Scaffold a self-contained Docker workspace |
110
+ | *modifiers* | `--ui` ยท `--telegram` ยท `--entry` | Web dashboard ยท Telegram alerts ยท fan out one strategy across many symbols |
327
111
 
328
- Runs a single entry point with the full CLI environment prepared โ€” `.env`, `config/setup.config`, `config/loader.config`, and `./modules/main.module` are all loaded, the working directory is changed to the entry point folder, and graceful shutdown is wired up โ€” but the CLI **never** starts a trading harness. Unlike `--backtest` / `--live` / `--paper` / `--walker`, it does not call `Backtest.background`/`Live.background`/`Walker.background` and does not pick a symbol, warm the cache, or resolve a strategy/exchange/frame. The entry point decides what to run.
329
-
330
- ```json
331
- {
332
- "scripts": {
333
- "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts"
334
- }
335
- }
336
- ```
337
-
338
- ```bash
339
- npm run main
340
- ```
341
-
342
- **Main-specific flags:**
112
+ <details>
113
+ <summary>Complete core flag reference</summary>
343
114
 
344
115
  | Flag | Type | Description |
345
116
  |------|------|-------------|
346
- | `--main` | boolean | Enable Main mode |
347
- | `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before the run (default: `false`) |
348
-
349
- Exactly **one** positional entry point is required โ€” the CLI throws `Entry point is required` if none is given. `process.cwd()` is changed to the entry point directory and its local `.env` is loaded (overriding the root `.env`), identical to the other modes.
350
-
351
- Although the CLI starts nothing itself, any `Backtest` / `Live` / `Walker` run that **your** entry point launches is still managed: the process exits automatically once `listenDone*` reports the run is complete, the first `Ctrl+C` stops every active run via `*.list()` / `*.stop()`, and a second `Ctrl+C` force-quits the process tree.
352
-
353
- **Module hook:** `./modules/main.module` is loaded automatically before the entry point runs (same `.ts` / `.mjs` / `.cjs` resolution rules as other modes).
354
-
355
- Main is a casual `--entry` alternative if you need to bootstrap environment for a quick action: for example, using 3rdparty API with automatic `.env` import.
356
-
357
- ## ๐Ÿ™ Multiple Symbol Parallel
358
-
359
- > **For Poweruser โ€” skip unless needed.** The standard flow runs one symbol from `--symbol`. Use `--entry` only to fan out one strategy across many symbols at once, or to drive `*.background()` from a UI / DB / API.
360
-
361
- With `--entry`, the CLI does only the boilerplate โ€” `Setup`, providers (`--ui` / `--telegram`), the matching `./modules/<mode>.module`, SIGINT that stops every active run via `*.list()`, and `shutdown()` once `listenDone*` reports all your runs complete. Picking the symbol set, warming cache, and calling `*.background()` is on you.
362
-
363
- The `--entry` flag is a modifier โ€” combine it with exactly one of `--backtest` / `--live` / `--paper` / `--walker`. One positional: the path to your entry file.
364
-
365
- ```bash
366
- npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
367
- ```
368
-
369
- ### Example: backtest a strategy on five symbols at once
370
-
371
- ```javascript
372
- // src/multi-symbol.mjs
373
- import {
374
- addExchangeSchema,
375
- addFrameSchema,
376
- addStrategySchema,
377
- Backtest,
378
- warmCandles,
379
- } from "backtest-kit";
380
- import ccxt from "ccxt";
381
-
382
- addExchangeSchema({
383
- exchangeName: "binance",
384
- getCandles: async (symbol, interval, since, limit) => {
385
- const exchange = new ccxt.binance();
386
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
387
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
388
- timestamp, open, high, low, close, volume,
389
- }));
390
- },
391
- formatPrice: (symbol, price) => price.toFixed(2),
392
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
393
- });
394
-
395
- addFrameSchema({
396
- frameName: "feb-2026",
397
- interval: "1m",
398
- startDate: new Date("2026-02-01"),
399
- endDate: new Date("2026-02-28"),
400
- });
401
-
402
- addStrategySchema({
403
- strategyName: "my-strategy",
404
- interval: "15m",
405
- getSignal: async (symbol) => null,
406
- });
407
-
408
- // Decide the symbol set yourself โ€” from a UI, database, API, or just a list.
409
- const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"];
410
-
411
- for (const symbol of symbols) {
412
-
413
- //
414
- // Optional
415
- //
416
- // await warmCandles({
417
- // exchangeName: "binance,
418
- // from: new Date("2026-01-01T00:00:00Z"),
419
- // to: new Date("2026-01-31T23:59:59Z"),
420
- // interval: "1m",
421
- // symbol,
422
- // })
423
-
424
-
425
- Backtest.background(symbol, {
426
- strategyName: "my-strategy",
427
- exchangeName: "binance",
428
- frameName: "feb-2026",
429
- });
430
- }
431
- ```
117
+ | `--backtest` | boolean | Run historical backtest (default `false`) |
118
+ | `--walker` | boolean | Run Walker A/B comparison (default `false`) |
119
+ | `--paper` | boolean | Paper trading โ€” live prices, no orders (default `false`) |
120
+ | `--live` | boolean | Run live trading (default `false`) |
121
+ | `--main` | boolean | Custom entry point, no trading harness (default `false`) |
122
+ | `--ui` | boolean | Start web UI dashboard (default `false`) |
123
+ | `--telegram` | boolean | Enable Telegram notifications (default `false`) |
124
+ | `--verbose` | boolean | Log each candle fetch (default `false`) |
125
+ | `--noCache` | boolean | Skip candle cache warming before backtest (default `false`) |
126
+ | `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before run (default `false`) |
127
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
128
+ | `--strategy` | string | Strategy name (default: first registered) |
129
+ | `--exchange` | string | Exchange name (default: first registered) |
130
+ | `--frame` | string | Backtest frame name (default: first registered) |
131
+ | `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
132
+ | `--brokerdebug` | boolean | Fire a single broker commit against the live adapter (default `false`) |
133
+ | `--commit` | string | Commit type for `--brokerdebug` (default `"signal-open"`) |
432
134
 
433
- The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter)
135
+ **Positional argument (required):** path to your strategy entry point file โ€” set once in `package.json` scripts. Tool-specific flags (`--pine`, `--dump`, `--pnldebug`, `--docker`, โ€ฆ) are documented in their sections below.
434
136
 
435
- ## ๐Ÿ—‚๏ธ Monorepo Usage
137
+ </details>
436
138
 
437
- `@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.
139
+ ---
438
140
 
439
- ### How It Works
141
+ ## ๐Ÿ“ˆ Trading modes
440
142
 
441
- Internally, `ResolveService` does the following before executing your entry point:
143
+ The four modes that actually run strategies share one engine and one set of guarantees โ€” only the clock and the order routing differ.
442
144
 
443
- ```
444
- process.chdir(path.dirname(entryPoint)) // cwd โ†’ strategy directory
445
- dotenv.config({ path: rootDir + '/.env' }) // load root .env first
446
- dotenv.config({ path: strategyDir + '/.env', override: true }) // strategy .env overrides
447
- ```
145
+ ### Backtest ยท Paper ยท Live
448
146
 
449
- Everything that follows โ€” candle cache warming, report generation, module loading, template resolution โ€” uses the new cwd automatically.
147
+ <details>
148
+ <summary>How each behaves</summary>
450
149
 
451
- ### Project Structure
452
-
453
- ```
454
- monorepo/
455
- โ”œโ”€โ”€ package.json # root scripts (one per strategy)
456
- โ”œโ”€โ”€ .env # shared API keys (exchange, Telegram, etc.)
457
- โ””โ”€โ”€ strategies/
458
- โ”œโ”€โ”€ oct_2025/
459
- โ”‚ โ”œโ”€โ”€ index.mjs # entry point โ€” registers exchange/frame/strategy schemas
460
- โ”‚ โ”œโ”€โ”€ .env # overrides root .env for this strategy
461
- โ”‚ โ”œโ”€โ”€ modules (optional)
462
- โ”‚ | โ”œโ”€โ”€ live.module.mjs # broker adapter for --live mode (optional)
463
- โ”‚ | โ”œโ”€โ”€ paper.module.mjs # broker adapter for --paper mode (optional)
464
- โ”‚ | โ”œโ”€โ”€ backtest.module.mjs # broker adapter for --backtest mode (optional)
465
- โ”‚ โ”œโ”€โ”€ template/ # custom Mustache templates (optional)
466
- โ”‚ โ””โ”€โ”€ dump/ # auto-created: candle cache + backtest reports
467
- โ””โ”€โ”€ dec_2025/
468
- โ”œโ”€โ”€ index.mjs
469
- โ”œโ”€โ”€ .env
470
- โ””โ”€โ”€ dump/
471
- ```
472
-
473
- ### Root `package.json`
150
+ **Backtest** (`--backtest`) โ€” runs against historical candles via a registered `FrameSchema`. Before running, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from the strategy's `dump/` dir, then warms the candle cache for every interval in `--cacheInterval`; subsequent runs reuse the cache with no API calls. `--noCache` skips warming, `--noFlush` keeps output folders.
474
151
 
475
152
  ```json
476
- {
477
- "scripts": {
478
- "backtest:oct": "npx @backtest-kit/cli --backtest ./strategies/oct_2025/index.mjs",
479
- "backtest:dec": "npx @backtest-kit/cli --backtest ./strategies/dec_2025/index.mjs"
480
- },
481
- "dependencies": {
482
- "@backtest-kit/cli": "latest",
483
- "backtest-kit": "latest",
484
- "ccxt": "latest"
485
- }
486
- }
487
- ```
488
-
489
- ```bash
490
- npm run backtest:oct
491
- npm run backtest:dec
492
- ```
493
-
494
- ### Isolated Resources Per Strategy
495
-
496
- | Resource | Path (relative to strategy dir) | Isolated |
497
- |--------------------------|-----------------------------------|------------------|
498
- | Candle cache | `./dump/data/candle/` | โœ… per-strategy |
499
- | Backtest reports | `./dump/` | โœ… per-strategy |
500
- | Broker module (live) | `./modules/live.module.mjs` | โœ… per-strategy |
501
- | Broker module (paper) | `./modules/paper.module.mjs` | โœ… per-strategy |
502
- | Broker module (backtest) | `./modules/backtest.module.mjs` | โœ… per-strategy |
503
- | Config module (walker) | `./modules/walker.module.mjs` | โœ… loaded once |
504
- | Telegram templates | `./template/*.mustache` | โœ… per-strategy |
505
- | Environment variables | `./.env` (overrides root) | โœ… per-strategy |
506
-
507
- 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.
508
-
509
- ## ๐Ÿ”— Shared Import Aliases
510
-
511
- `@backtest-kit/cli` automatically turns every **top-level folder** in `process.cwd()` into a bare import alias available inside any strategy file. No configuration needed โ€” just create the folder.
512
-
513
- ### How It Works
514
-
515
- When the CLI loads a strategy file, it scans the current working directory for subdirectories and registers each one as an import alias. The alias name is the folder name. Both barrel imports and deep subpath imports are supported:
516
-
517
- | Import | Resolves to |
518
- |--------|-------------|
519
- | `import { fn } from "utils"` | `<cwd>/utils/index.ts` (or `.js`, `.mjs`, `.cjs`) |
520
- | `import { calcRSI } from "math/rsi"` | `<cwd>/math/rsi.ts` |
521
- | `import { research } from "logic"` | `<cwd>/logic/index.ts` |
522
- | `import { ResearchResponseContract } from "logic/contract/ResearchResponse.contract"` | `<cwd>/logic/contract/ResearchResponse.contract.ts` |
523
-
524
- ### Project Structure
525
-
526
- ```
527
- my-project/
528
- โ”œโ”€โ”€ utils/ โ† import { formatDate } from "utils"
529
- โ”‚ โ””โ”€โ”€ index.ts
530
- โ”œโ”€โ”€ math/ โ† import { calcRSI } from "math/rsi"
531
- โ”‚ โ””โ”€โ”€ rsi.ts
532
- โ”œโ”€โ”€ logic/ โ† import { research } from "logic"
533
- โ”‚ โ”œโ”€โ”€ index.ts โ† barrel
534
- โ”‚ โ””โ”€โ”€ contract/
535
- โ”‚ โ””โ”€โ”€ ResearchResponse.contract.ts โ† import { ... } from "logic/contract/ResearchResponse.contract"
536
- โ””โ”€โ”€ content/
537
- โ”œโ”€โ”€ feb_2026.strategy.ts โ† uses all three aliases freely
538
- โ””โ”€โ”€ mar_2026.strategy.ts โ† same aliases, no duplication
153
+ { "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" } }
539
154
  ```
540
155
 
541
- This lets you extract shared utilities, math helpers, or AI agent logic (e.g. `agent-swarm-kit` workflows) into named folders and reuse them across every strategy in the project without relative path hell.
542
-
543
- ### TypeScript Support
544
-
545
- Add a matching `paths` entry to your `tsconfig.json` so the editor resolves the aliases:
156
+ **Paper** (`--paper`) โ€” connects to the live exchange but places no real orders. **Identical code path to live** โ€” the safe way to validate a strategy.
546
157
 
547
158
  ```json
548
- {
549
- "compilerOptions": {
550
- "moduleResolution": "bundler",
551
- "paths": {
552
- "logic": ["./logic/index.ts"],
553
- "logic/*": ["./logic/*"],
554
- "math": ["./math/index.ts"],
555
- "math/*": ["./math/*"],
556
- "utils": ["./utils/index.ts"],
557
- "utils/*": ["./utils/*"]
558
- }
559
- },
560
- "include": [
561
- "./logic",
562
- "./math",
563
- "./utils",
564
- "./content",
565
- "./modules",
566
- ],
567
- }
568
- ```
569
-
570
- ## ๐Ÿ”” Integrations
571
-
572
- ### Web Dashboard (`--ui`)
573
-
574
- Starts `@backtest-kit/ui` server. Access the interactive dashboard at:
575
-
576
- ```
577
- http://localhost:60050
578
- ```
579
-
580
- Customize host/port via environment variables `CC_WWWROOT_HOST` and `CC_WWWROOT_PORT`.
581
-
582
- #### Symbol List (`symbol.config`)
583
-
584
- By default the UI shows all symbols from the exchange. To restrict or reorder the list, create a `config/symbol.config` file in your strategy directory (next to the entry point).
585
-
586
- **Resolution order โ€” first match wins:**
587
-
588
- | Priority | Path | Notes |
589
- |----------|------|-------|
590
- | 1 | `{strategyDir}/config/symbol.config` | per-strategy override (cwd after `chdir`) |
591
- | 2 | `{projectRoot}/config/symbol.config` | project-root override (cwd where `npx` was invoked) |
592
- | 3 | `@backtest-kit/cli/config/symbol.config` | built-in default shipped with the package |
593
-
594
- Supported file formats (`.ts`, `.cjs`, `.mjs`, `.js` tried automatically):
595
-
596
- ```ts
597
- // config/symbol.config.ts
598
- export const symbol_list = [
599
- {
600
- icon: "/icon/btc.png",
601
- logo: "/icon/128/btc.png",
602
- symbol: "BTCUSDT",
603
- displayName: "Bitcoin",
604
- color: "#F7931A",
605
- priority: 50,
606
- description: "Bitcoin - the first and most popular cryptocurrency",
607
- },
608
- {
609
- icon: "/icon/eth.png",
610
- logo: "/icon/128/eth.png",
611
- symbol: "ETHUSDT",
612
- displayName: "Ethereum",
613
- color: "#6F42C1",
614
- priority: 50,
615
- description: "Ethereum - a blockchain platform for smart contracts",
616
- },
617
- ];
618
- ```
619
-
620
- #### Notification Filter (`notification.config`)
621
-
622
- Controls which notification categories are shown in the UI dashboard. Create a `config/notification.config` file in your strategy directory to override the defaults.
623
-
624
- **Resolution order โ€” first match wins:**
625
-
626
- | Priority | Path | Notes |
627
- |----------|------|-------|
628
- | 1 | `{strategyDir}/config/notification.config` | per-strategy override (cwd after `chdir`) |
629
- | 2 | `{projectRoot}/config/notification.config` | project-root override (cwd where `npx` was invoked) |
630
- | 3 | `@backtest-kit/cli/config/notification.config` | built-in default shipped with the package |
631
-
632
- **Default values (built-in):**
633
-
634
- | Key | Default | Description |
635
- |-----|---------|-------------|
636
- | `signal` | `true` | Signal lifecycle: opened, scheduled, closed, cancelled |
637
- | `risk` | `true` | Risk manager rejection notifications |
638
- | `info` | `true` | Informational messages attached to an active signal |
639
- | `breakeven` | `true` | Breakeven level reached |
640
- | `common_error` | `true` | Non-fatal runtime errors |
641
- | `critical_error` | `true` | Fatal errors that terminate the session |
642
- | `validation_error` | `true` | Strategy config / input validation errors |
643
- | `strategy_commit` | `true` | All committed actions (partial close, DCA, trailing, etc.) |
644
- | `partial_loss` | `false` | Partial loss level reached (before commit) |
645
- | `partial_profit` | `false` | Partial profit level reached (before commit) |
646
- | `signal_sync` | `false` | Live order fill / exit confirmations from exchange sync |
647
-
648
- ```js
649
- // config/notification.config.ts
650
- export default {
651
- signal: true,
652
- risk: true,
653
- info: true,
654
- breakeven: true,
655
- common_error: true,
656
- critical_error: true,
657
- validation_error: true,
658
- strategy_commit: true,
659
- partial_loss: false,
660
- partial_profit: false,
661
- signal_sync: false,
662
- };
663
- ```
664
-
665
- ### Telegram Notifications (`--telegram`)
666
-
667
- 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.
668
-
669
- Requires `CC_TELEGRAM_TOKEN` and `CC_TELEGRAM_CHANNEL` in your environment.
670
-
671
- #### Telegram Message Adapter (`telegram.config`)
672
-
673
- By default messages are rendered from Mustache templates (`template/*.mustache`). To override rendering programmatically, create a `config/telegram.config` file and export an object with any subset of `get*Markdown` methods. Each method receives the event payload and must return a `Promise<string>` with the Markdown message body.
674
-
675
- Resolution order is the same as other configs (strategy dir โ†’ project root โ†’ package default).
676
-
677
- ```ts
678
- // config/telegram.config.ts
679
- import {
680
- IStrategyTickResultOpened,
681
- IStrategyTickResultClosed,
682
- RiskContract,
683
- } from "backtest-kit";
684
-
685
- export default {
686
- async getOpenedMarkdown(event: IStrategyTickResultOpened): Promise<string> {
687
- return `**Opened** ${event.symbol} at ${event.priceOpen}`;
688
- },
689
- async getClosedMarkdown(event: IStrategyTickResultClosed): Promise<string> {
690
- return `**Closed** ${event.symbol} at ${event.priceClosed}`;
691
- },
692
- async getRiskMarkdown(event: RiskContract): Promise<string> {
693
- return `**Risk rejected** ${event.symbol}`;
694
- },
695
- };
696
- ```
697
-
698
- All methods are optional โ€” unimplemented ones fall back to the Mustache template.
699
-
700
- | Method | Event type |
701
- |--------|------------|
702
- | `getOpenedMarkdown` | `IStrategyTickResultOpened` |
703
- | `getClosedMarkdown` | `IStrategyTickResultClosed` |
704
- | `getScheduledMarkdown` | `IStrategyTickResultScheduled` |
705
- | `getCancelledMarkdown` | `IStrategyTickResultCancelled` |
706
- | `getRiskMarkdown` | `RiskContract` |
707
- | `getPartialProfitMarkdown` | `PartialProfitCommit` |
708
- | `getPartialLossMarkdown` | `PartialLossCommit` |
709
- | `getBreakevenMarkdown` | `BreakevenCommit` |
710
- | `getTrailingTakeMarkdown` | `TrailingTakeCommit` |
711
- | `getTrailingStopMarkdown` | `TrailingStopCommit` |
712
- | `getAverageBuyMarkdown` | `AverageBuyCommit` |
713
- | `getSignalOpenMarkdown` | `SignalOpenContract` |
714
- | `getSignalCloseMarkdown` | `SignalCloseContract` |
715
- | `getCancelScheduledMarkdown` | `CancelScheduledCommit` |
716
- | `getClosePendingMarkdown` | `ClosePendingCommit` |
717
- | `getSignalInfoMarkdown` | `SignalInfoContract` |
718
-
719
- ## ๐Ÿงฉ Module Hooks (Broker Adapter)
720
-
721
- 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.
722
-
723
- | Command Line Args | Module file | Loaded before |
724
- |-------------------|---------------------------------|-----------------------------|
725
- | `--live` | `./modules/live.module.mjs` | `Live.background()` |
726
- | `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
727
- | `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
728
- | `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
729
- | `--main` | `./modules/main.module.mjs` | the custom entry point |
730
- | `--brokerdebug` | `./modules/brokerdebug.module.mjs` | broker commit test |
731
-
732
- > 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.
733
-
734
- ### How It Works
735
-
736
- 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.
737
-
738
- ```javascript
739
- // live.module.mjs
740
- import { Broker } from 'backtest-kit';
741
- import { myExchange } from './exchange.mjs';
742
-
743
- class MyBroker {
744
- async onSignalOpenCommit({ symbol, priceOpen, direction }) {
745
- await myExchange.openPosition(symbol, direction, priceOpen);
746
- }
747
-
748
- async onSignalCloseCommit({ symbol, priceClosed }) {
749
- await myExchange.closePosition(symbol, priceClosed);
750
- }
751
-
752
- async onPartialProfitCommit({ symbol, cost, currentPrice }) {
753
- await myExchange.createOrder({
754
- symbol,
755
- side: 'sell',
756
- quantity: cost / currentPrice,
757
- });
758
- }
759
-
760
- async onAverageBuyCommit({ symbol, cost, currentPrice }) {
761
- await myExchange.createOrder({
762
- symbol,
763
- side: 'buy',
764
- quantity: cost / currentPrice,
765
- });
766
- }
767
- }
768
-
769
- Broker.useBrokerAdapter(MyBroker);
770
-
771
- Broker.enable();
772
- ```
773
-
774
- ### Available Broker Hooks
775
-
776
- | Method | Payload type | Triggered on |
777
- |--------------------------|------------------------------|---------------------------|
778
- | `onSignalOpenCommit` | `BrokerSignalOpenPayload` | Position activation |
779
- | `onSignalCloseCommit` | `BrokerSignalClosePayload` | SL / TP / manual close |
780
- | `onPartialProfitCommit` | `BrokerPartialProfitPayload` | PP |
781
- | `onPartialLossCommit` | `BrokerPartialLossPayload` | PL |
782
- | `onTrailingStopCommit` | `BrokerTrailingStopPayload` | SL adjustment |
783
- | `onTrailingTakeCommit` | `BrokerTrailingTakePayload` | TP adjustment |
784
- | `onBreakevenCommit` | `BrokerBreakevenPayload` | SL moved to entry |
785
- | `onAverageBuyCommit` | `BrokerAverageBuyPayload` | DCA entry |
786
-
787
- All methods are optional. Unimplemented hooks are silently skipped. In backtest mode all broker calls are skipped automatically โ€” no adapter code runs during backtests.
788
-
789
- ### TypeScript
790
-
791
- ```typescript
792
- import { Broker, IBroker, BrokerSignalOpenPayload, BrokerSignalClosePayload } from 'backtest-kit';
793
-
794
- class MyBroker implements Partial<IBroker> {
795
- async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
796
- // place open order on exchange
797
- }
798
-
799
- async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
800
- // place close order on exchange
801
- }
802
- }
803
-
804
- Broker.useBrokerAdapter(MyBroker);
805
-
806
- Broker.enable();
807
- ```
808
-
809
- ## โš™๏ธ Setup Hook (`config/setup.config`)
810
-
811
- `@backtest-kit/cli` loads a `{projectRoot}/config/setup.config` file once before any module hooks or strategy code run. Use it to perform one-time initialization that must happen before the first persistence call โ€” registering a custom storage backend, configuring a logger, seeding global state, or anything else the process needs before `backtest-kit` starts.
812
-
813
- **Important:** When `setup.config` is present, the CLI skips its own default adapter registration โ€” it does **not** call `usePersist()` / `useLocal()` / `useMemory()` for any of the persistence slots. This means your config takes full ownership of the persistence layer: whatever adapters you register in `{projectRoot}/config/setup.config` are the ones `backtest-kit` uses, with no interference from the CLI defaults.
814
-
815
- ### Example: MongoDB + Redis persistence via `@backtest-kit/mongo`
816
-
817
- The most common use-case is swapping the default file-based persistence for a production-grade backend. Install `@backtest-kit/mongo` and call `setup()` โ€” it registers all 15 persistence adapters in one call and reads connection parameters from environment variables:
818
-
819
- ```bash
820
- npm install @backtest-kit/mongo
821
- ```
822
-
823
- ```ts
824
- // config/setup.config.ts
825
- import { setup } from '@backtest-kit/mongo';
826
-
827
- setup();
828
- ```
829
-
830
- ```env
831
- # .env
832
- CC_MONGO_CONNECTION_STRING=mongodb://localhost:27017/backtest-kit
833
- CC_REDIS_HOST=127.0.0.1
834
- CC_REDIS_PORT=6379
835
- ```
836
-
837
- Or pass connection parameters explicitly:
838
-
839
- ```ts
840
- // config/setup.config.ts
841
- import { setup } from '@backtest-kit/mongo';
842
-
843
- setup({
844
- CC_MONGO_CONNECTION_STRING: 'mongodb://mongo:27017/mydb',
845
- CC_REDIS_HOST: 'redis',
846
- CC_REDIS_PORT: 6379,
847
- CC_REDIS_PASSWORD: 'secret',
848
- });
849
- ```
850
-
851
- No changes to strategy code are needed โ€” `setup()` wires up the adapters transparently before `backtest-kit` makes its first persistence call.
852
-
853
- ## ๐Ÿงฉ Module Loader (`config/loader.config`)
854
-
855
- `@backtest-kit/cli` loads a `{projectRoot}/config/loader.config` file **after** `setup.config` but **before** any strategy or module code runs. Unlike `setup.config` (which is loaded for its side effects), `loader.config` exports a function that the CLI explicitly `await`s. Use it whenever you need to **wait for an async dependency** to be ready before the backtest starts.
856
-
857
- ```bash
858
- monorepo/
859
- โ”œโ”€โ”€ packages/
860
- โ”‚ โ”œโ”€โ”€ shared-broker/ # shared broker code
861
- โ”‚ โ”œโ”€โ”€ shared-signals/ # common indicators (RSI, MACD)
862
- โ”‚ โ”œโ”€โ”€ shared-db/ # mongodb wiring
863
- โ”‚ โ”œโ”€โ”€ strategy-momentum/ # strategy code
864
- โ”‚ โ””โ”€โ”€ strategy-mean-reversion/
865
- ```
866
-
867
- **When to use it:**
868
-
869
- - **Wire microfrontends in a monorepo** โ€” resolve and pre-load sibling packages, register cross-package services, or hydrate a shared DI container before strategies import from neighboring workspaces.
870
- - **Wait for a database connection** โ€” open a Mongo/Postgres/Redis connection and verify it's reachable before the first persistence call, so the backtest fails fast instead of mid-run.
871
- - **Warm up caches or external APIs** โ€” pre-fetch reference data (instruments list, calendar, fee tables) so the strategy's first tick doesn't pay the round-trip cost.
872
- - **Run schema migrations** โ€” apply any pending migrations to the persistence backend before signals start flowing.
873
-
874
- `loader.config` supports exactly one of two export styles โ€” **never both at once**. If both are present, the `default` export wins and the named `loader` is ignored.
875
-
876
- ```ts
877
- // config/loader.config.ts โ€” default export (preferred, ESM style)
878
- export default async () => {
879
- await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
880
- await redis.ping();
881
- };
882
- ```
883
-
884
- ```ts
885
- // config/loader.config.ts โ€” named export
886
- export const loader = async () => {
887
- await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
888
- await redis.ping();
889
- };
890
- ```
891
-
892
- ### Example: wait for MongoDB before running a backtest
893
-
894
- `@backtest-kit/mongo`'s `setup()` registers the adapters synchronously but doesn't block until the connection is established. If your backtest depends on data that must be present in Mongo before the first signal fires, use `loader.config` to gate the run on a real connection:
895
-
896
- ```ts
897
- // config/setup.config.ts
898
- import { setup } from '@backtest-kit/mongo';
899
-
900
- setup();
901
- ```
902
-
903
- ```ts
904
- // config/loader.config.ts
905
- import mongoose from 'mongoose';
906
-
907
- export default async () => {
908
- await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
909
- console.log('mongo connection verified, starting backtest');
910
- };
159
+ { "scripts": { "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs" } }
911
160
  ```
912
161
 
913
- ### Example: stitch microfrontends in a monorepo
914
-
915
- When `backtest-kit` strategies live in one workspace and shared services (broker adapters, signal feeds, dashboards) live in sibling workspaces, `loader.config` is the place to wire them together before the runner starts:
916
-
917
- ```ts
918
- // config/loader.config.ts
919
- import "@my-org/brokers";
920
- import "@my-org/signals";
921
- ```
922
-
923
- The `@my-org` alias should be declared in `config/alias.config`.
924
-
925
- ## ๐Ÿ”€ Import Aliases (`config/alias.config`)
926
-
927
- `@backtest-kit/cli` lets you override any nodejs module import โ€” without touching the strategy code. Drop a `config/alias.config` file in your project root and export a mapping from module name to replacement module.
928
-
929
- The alias table is loaded once (on the first `import` call) from `{projectRoot}/config/alias.config` and applied globally to every subsequent module load via `require`/ `import`.
930
-
931
- **Use cases:**
932
-
933
- - Replace a heavy dependency with a lighter stub for backtesting
934
- - Swap any external api for a mock during CI runs
935
-
936
- ```ts
937
- // config/alias.config.ts โ€” named export
938
- export const ccxt = require("./stubs/ccxt.stub.cjs");
939
- ```
940
-
941
- ```js
942
- // config/alias.config.cjs โ€” default export
943
- module.exports = {
944
- ccxt: require("./stubs/ccxt.stub.cjs"),
945
- };
946
- ```
947
-
948
- ```js
949
- // config/alias.config.mjs โ€” default export
950
- import ccxtStub from "./stubs/ccxt.stub.mjs";
951
-
952
- export default {
953
- ccxt: ccxtStub,
954
- };
955
- ```
956
-
957
- Alias config may export an **async factory** instead of a plain mapping. The CLI `await`s it before any strategy code runs, so you can resolve ESM modules dynamically and hand the live bindings to the alias table:
958
-
959
- ```ts
960
- // config/alias.config.ts โ€” async factory (default export)
961
- export default async () => ({
962
- // `nanoid` is ESM-only โ€” `require("nanoid")` would throw,
963
- // so pull it in with a dynamic import and alias it.
964
- nanoid: await import("nanoid"),
965
- });
966
- ```
967
-
968
- ```ts
969
- // config/alias.config.ts โ€” async factory (named `loader` export)
970
- export const loader = async () => ({
971
- "p-limit": await import("p-limit"),
972
- });
973
- ```
974
-
975
- Both export styles are supported โ€” `export default` and `export const loader` โ€” but **never both at once**; if both are present the `default` export wins. The factory must resolve to the same `{ moduleName: replacement }` shape as the object form. The CLI awaits it before loading the first strategy module, so strategy code keeps calling a plain `require("nanoid")` and transparently gets the ESM binding.
976
-
977
- When strategy code calls `require("ccxt")`, the loader checks `IMPORT_ALIAS` first. If a key matches, the mapped value is returned instead of the real module โ€” no monkey-patching of `node_modules` needed.
978
-
979
- **Important:** It is **not** per-strategy โ€” it applies to all modules loaded in the current process.
980
-
981
- ## ๐Ÿ“ฆ Supported Entry Point Formats
982
-
983
- `@backtest-kit/cli` automatically detects the format of your strategy file and loads it with the appropriate runtime โ€” no flags or configuration required.
984
-
985
- | Format | Extension | Runtime | Use Case |
986
- |--------|-----------|---------|----------|
987
- | **TypeScript** | `.ts` | [`tsx`](https://tsx.is/) via `tsImport()` | TypeScript strategies with cross-imports (ESM โ†” CJS) |
988
- | **ES Module** | `.mjs` | Native `import()` | Modern JavaScript with top-level `await` and ESM syntax |
989
- | **CommonJS** | `.cjs` | Native `require()` | Legacy or dual-package strategies |
990
-
991
- ### TypeScript (`.ts`)
992
-
993
- Run TypeScript strategy files directly โ€” no `tsc` compilation step needed. Powered by `tsx`, which handles cross-format imports transparently:
162
+ **Live** (`--live`) โ€” deploys a real bot. Requires exchange API keys in `.env`. Combine with `--ui --telegram` for a monitored deployment.
994
163
 
995
164
  ```json
996
- {
997
- "scripts": {
998
- "backtest": "npx @backtest-kit/cli --backtest ./src/index.ts"
999
- },
1000
- "dependencies": {
1001
- "@backtest-kit/cli": "latest",
1002
- "backtest-kit": "latest",
1003
- "tsx": "latest"
1004
- }
1005
- }
165
+ { "scripts": { "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs" } }
1006
166
  ```
1007
167
 
1008
- ### ES Module (`.mjs`)
1009
-
1010
- Standard ESM format. Supports top-level `await`, named exports, and `import` syntax:
1011
-
1012
- ```json
1013
- {
1014
- "scripts": {
1015
- "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
1016
- }
1017
- }
1018
- ```
168
+ </details>
1019
169
 
1020
- ### CommonJS (`.cjs`)
170
+ ### Walker โ€” A/B strategy comparison
1021
171
 
1022
- For projects that compile to or use CommonJS. Loaded via `require()`:
172
+ Runs the same historical period against multiple strategy files and prints a ranked report. Use it to pick the best variant before deploying.
1023
173
 
1024
- ```json
1025
- {
1026
- "scripts": {
1027
- "backtest": "npx @backtest-kit/cli --backtest ./dist/index.cjs"
1028
- }
1029
- }
174
+ ```bash
175
+ npx @backtest-kit/cli --walker --symbol BTCUSDT --noCache --markdown --output feb_2026_comparison \
176
+ ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
177
+ # โ†’ ./dump/feb_2026_comparison.md
1030
178
  ```
1031
179
 
1032
- ## ๐ŸŒฒ Running Local PineScript Indicators
1033
-
1034
- `@backtest-kit/cli` can execute any local `.pine` file against a real exchange and print the results as a Markdown table โ€” no TradingView account required.
180
+ <details>
181
+ <summary>Walker flags, output modes & behavior</summary>
1035
182
 
1036
- ### CLI Flags
183
+ Each positional argument is a separate strategy entry point. Before loading them the CLI removes the `report`/`log`/`markdown`/`agent` folders from each entry point's `dump/` (skip with `--noFlush`). All files load **without changing `process.cwd()`** โ€” `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files. If no frame is registered, the CLI falls back to the last 31 days from `Date.now()` with a warning.
1037
184
 
1038
185
  | Flag | Type | Description |
1039
186
  |------|------|-------------|
1040
- | `--pine` | boolean | Enable PineScript execution mode |
1041
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
1042
- | `--timeframe` | string | Candle interval (default: `"15m"`) |
1043
- | `--limit` | string | Number of candles to fetch (default: `250`) |
1044
- | `--when` | string | End date for candle window โ€” ISO 8601 or Unix ms (default: now) |
1045
- | `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
1046
- | `--output` | string | Output file base name without extension (default: `.pine` file name) |
1047
- | `--json` | boolean | Write plots as a JSON array to `<pine-dir>/dump/{output}.json` |
1048
- | `--jsonl` | boolean | Write plots as JSONL (one row per line) to `<pine-dir>/dump/{output}.jsonl` |
1049
- | `--markdown` | boolean | Write Markdown table to `<pine-dir>/dump/{output}.md` |
1050
-
1051
- **Important:** `limit` must cover indicator warmup bars โ€” rows before warmup completes will show `N/A`
187
+ | `--walker` | boolean | Enable Walker comparison |
188
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
189
+ | `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
190
+ | `--noCache` | boolean | Skip candle cache warming |
191
+ | `--noFlush` | boolean | Skip removing output folders before the run |
192
+ | `--verbose` | boolean | Log each candle fetch and strategy progress |
193
+ | `--output` | string | Output file base name (default `walker_{SYMBOL}_{TIMESTAMP}`) |
194
+ | `--json` | boolean | Save `Walker.getData()` as JSON to `./dump/<output>.json` and exit |
195
+ | `--markdown` | boolean | Save `Walker.getReport()` as `./dump/<output>.md` and exit |
1052
196
 
1053
- **Positional argument:** path to the `.pine` file.
197
+ **Output:** no flag โ†’ print Markdown report to stdout; `--json` / `--markdown` โ†’ save and exit. **Module hook:** `./modules/walker.module` loads automatically before the comparison (`.ts`/`.mjs`/`.cjs` tried in order).
1054
198
 
1055
- ### Exchange via `pine.module`
199
+ </details>
1056
200
 
1057
- By default the CLI registers CCXT Binance automatically. To use a different exchange โ€” or to configure API keys, custom rate limits, or a non-spot market โ€” create a `modules/pine.module.ts` file. The CLI loads it automatically before running the script.
201
+ ### Main โ€” custom entry point, no trading harness
1058
202
 
1059
- The CLI looks for `modules/pine.module` in two locations (first match wins):
203
+ Runs a single entry point with the full CLI environment prepared (`.env`, `config/setup.config`, `config/loader.config`, `./modules/main.module`, cwd changed to the entry-point folder, graceful shutdown wired) โ€” but **never** starts a trading harness. Use it to bootstrap the environment for a quick action, e.g. calling a 3rd-party API with automatic `.env` import.
1060
204
 
1061
- 1. **Next to the `.pine` file** โ€” `<pine-file-dir>/modules/pine.module.ts`
1062
- 2. **Project root** โ€” `<cwd>/modules/pine.module.ts`
205
+ <details>
206
+ <summary>Main behavior & flags</summary>
1063
207
 
1064
- ```
1065
- my-project/
1066
- โ”œโ”€โ”€ math/
1067
- โ”‚ โ”œโ”€โ”€ impulse_trend_15m.pine โ† indicator
1068
- โ”‚ โ””โ”€โ”€ modules/
1069
- โ”‚ โ””โ”€โ”€ pine.module.ts โ† loaded first (next to .pine file)
1070
- โ”œโ”€โ”€ modules/
1071
- โ”‚ โ””โ”€โ”€ pine.module.ts โ† fallback (project root)
1072
- โ””โ”€โ”€ package.json
1073
- ```
208
+ Unlike the trading modes it does not call `Backtest/Live/Walker.background`, pick a symbol, warm the cache, or resolve a strategy/exchange/frame โ€” the entry point decides what to run. Exactly **one** positional entry point is required (`Entry point is required` otherwise). `process.cwd()` changes to the entry-point directory and its local `.env` overrides the root `.env`.
1074
209
 
1075
- Inside `pine.module.ts` call `addExchangeSchema` from `backtest-kit` and give the exchange a name:
210
+ Although the CLI starts nothing itself, any `Backtest`/`Live`/`Walker` run **your** entry point launches is still managed: the process exits once `listenDone*` reports completion, the first `Ctrl+C` stops every active run via `*.list()`/`*.stop()`, a second force-quits. `./modules/main.module` loads automatically before the entry point.
1076
211
 
1077
- ```typescript
1078
- // modules/pine.module.ts
1079
- import { addExchangeSchema } from "backtest-kit";
1080
- import ccxt from "ccxt";
1081
-
1082
- addExchangeSchema({
1083
- exchangeName: "my-exchange",
1084
- getCandles: async (symbol, interval, since, limit) => {
1085
- const exchange = new ccxt.bybit({ enableRateLimit: true });
1086
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
1087
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
1088
- timestamp, open, high, low, close, volume,
1089
- }));
1090
- },
1091
- formatPrice: (symbol, price) => price.toFixed(2),
1092
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
1093
- });
1094
- ```
1095
-
1096
- ### Environment variables (`.env`)
1097
-
1098
- Before loading `pine.module`, the CLI loads `.env` files in the same order as for strategy modules โ€” project root first, then the `.pine` file directory (overrides root):
1099
-
1100
- ```
1101
- my-project/
1102
- โ”œโ”€โ”€ math/
1103
- โ”‚ โ”œโ”€โ”€ .env โ† loaded second (overrides root)
1104
- โ”‚ โ””โ”€โ”€ impulse_trend_15m.pine
1105
- โ”œโ”€โ”€ .env โ† loaded first
1106
- โ””โ”€โ”€ package.json
1107
- ```
1108
-
1109
- Use this to store API keys without hardcoding them:
1110
-
1111
- ```env
1112
- # .env
1113
- BYBIT_API_KEY=xxx
1114
- BYBIT_API_SECRET=yyy
1115
- ```
1116
-
1117
- ```typescript
1118
- // modules/pine.module.ts
1119
- addExchangeSchema({
1120
- exchangeName: "my-exchange",
1121
- getCandles: async (symbol, interval, since, limit) => {
1122
- const exchange = new ccxt.bybit({
1123
- apiKey: process.env.BYBIT_API_KEY,
1124
- secret: process.env.BYBIT_API_SECRET,
1125
- enableRateLimit: true,
1126
- });
1127
- // ...
1128
- },
1129
- });
1130
- ```
1131
-
1132
- Then run:
1133
-
1134
- ```bash
1135
- npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine \
1136
- --exchange my-exchange \
1137
- --symbol BTCUSDT \
1138
- --timeframe 15m \
1139
- --limit 180 \
1140
- --when "2025-09-24T12:00:00.000Z"
1141
- ```
1142
-
1143
- Or add it to `package.json`:
1144
-
1145
- ```json
1146
- {
1147
- "scripts": {
1148
- "pine": "npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180"
1149
- }
1150
- }
1151
- ```
1152
-
1153
- ```bash
1154
- npm run pine
1155
- ```
1156
-
1157
- ### PineScript Requirements
1158
-
1159
- The CLI reads all `plot()` calls that use `display=display.data_window` as output columns. Every other `plot()` is ignored. Name each output plot explicitly:
1160
-
1161
- ```pine
1162
- //@version=5
1163
- indicator("MyIndicator", overlay=true)
1164
-
1165
- // ... computation ...
1166
-
1167
- plot(close, "Close", display=display.data_window)
1168
- plot(position, "Position", display=display.data_window)
1169
- ```
1170
-
1171
- The column names in the output Markdown table are taken directly from those plot names โ€” no manual schema definition needed.
1172
-
1173
- ### Output
1174
-
1175
- The CLI prints a Markdown table to stdout:
1176
-
1177
- ```
1178
- # PineScript Technical Analysis Dump
1179
-
1180
- **Signal ID**: CLI execution 2025-09-24T12:00:00.000Z
1181
-
1182
- | Close | Position | timestamp |
1183
- | --- | --- | --- |
1184
- | 112871.28 | -1.0000 | 2025-09-22T15:00:00.000Z |
1185
- | 112666.69 | -1.0000 | 2025-09-22T15:15:00.000Z |
1186
- | 112736.00 | 0.0000 | 2025-09-22T18:30:00.000Z |
1187
- | 112653.90 | 1.0000 | 2025-09-22T22:15:00.000Z |
1188
- ```
1189
-
1190
- Save to `./math/dump/impulse_trend_15m.md` (uses `.pine` file name automatically, dump is created next to the `.pine` file):
1191
-
1192
- ```bash
1193
- npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --markdown
1194
- ```
1195
-
1196
- Override the output name with `--output`:
1197
-
1198
- ```bash
1199
- npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --jsonl --output feb2026_bb
1200
- # โ†’ ./math/dump/feb2026_bb.jsonl
1201
- ```
1202
-
1203
- Print to stdout (no flag):
1204
-
1205
- ```bash
1206
- npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine
1207
- ```
1208
-
1209
- ## ๐ŸŽจ Visual Pine Script Editor
1210
-
1211
- ![pine](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot32.png)
1212
-
1213
- `@backtest-kit/cli` ships a browser-based Pine Script editor powered by `@backtest-kit/ui`. It lets you write, run, and iterate on indicators interactively โ€” with a live chart that updates as you hit **โ–ถ Run**
1214
-
1215
- ### Usage
212
+ | Flag | Type | Description |
213
+ |------|------|-------------|
214
+ | `--main` | boolean | Enable Main mode |
215
+ | `--noFlush` | boolean | Skip removing output folders before the run |
1216
216
 
1217
- ```bash
1218
- npx @backtest-kit/cli --editor
217
+ ```json
218
+ { "scripts": { "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts" } }
1219
219
  ```
1220
220
 
1221
- The CLI will:
221
+ </details>
1222
222
 
1223
- 1. Load `./modules/editor.module` (if it exists) โ€” use it to register your exchange schema, identical to `pine.module`
1224
- 2. Start the `@backtest-kit/ui` server on `http://localhost:60050` (or `CC_WWWROOT_PORT`)
1225
- 3. Open `http://localhost:{CC_WWWROOT_PORT}?pine=1` automatically in your default browser
223
+ ### Parallel multi-symbol (`--entry`)
1226
224
 
1227
- Press **Ctrl+C** to stop the server.
225
+ > **Power-user modifier โ€” skip unless needed.** The standard flow runs one symbol from `--symbol`. Use `--entry` to fan one strategy out across many symbols at once, or to drive `*.background()` from a UI / DB / API.
1228
226
 
1229
- ### Exchange via `editor.module`
227
+ `--entry` is a modifier โ€” combine it with exactly one of `--backtest`/`--live`/`--paper`/`--walker`, plus one positional entry file. The CLI does only the boilerplate (`Setup`, providers, the matching `./modules/<mode>.module`, SIGINT that stops every active run, `shutdown()` once `listenDone*` reports all runs complete); **you** pick the symbol set, warm cache, and call `*.background()`.
1230
228
 
1231
- Drop a `modules/editor.module.ts` next to your project to register the exchange that the editor's candle provider will use:
229
+ <details>
230
+ <summary>Example โ€” backtest one strategy across five symbols</summary>
1232
231
 
1233
- ```typescript
1234
- // modules/editor.module.ts
1235
- import { addExchangeSchema } from "backtest-kit";
232
+ ```javascript
233
+ // src/multi-symbol.mjs
234
+ import { addExchangeSchema, addFrameSchema, addStrategySchema, Backtest, warmCandles } from "backtest-kit";
1236
235
  import ccxt from "ccxt";
1237
236
 
1238
- addExchangeSchema({
1239
- exchangeName: "my-exchange",
237
+ addExchangeSchema({ exchangeName: "binance",
1240
238
  getCandles: async (symbol, interval, since, limit) => {
1241
- const exchange = new ccxt.bybit({ enableRateLimit: true });
239
+ const exchange = new ccxt.binance();
1242
240
  const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
1243
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
1244
- timestamp, open, high, low, close, volume,
1245
- }));
241
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
242
+ ({ timestamp, open, high, low, close, volume }));
1246
243
  },
1247
- formatPrice: (symbol, price) => price.toFixed(2),
1248
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
1249
- });
1250
- ```
244
+ formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8) });
1251
245
 
1252
- ### Environment Variables
246
+ addFrameSchema({ frameName: "feb-2026", interval: "1m",
247
+ startDate: new Date("2026-02-01"), endDate: new Date("2026-02-28") });
248
+ addStrategySchema({ strategyName: "my-strategy", interval: "15m", getSignal: async () => null });
1253
249
 
1254
- | Variable | Default | Description |
1255
- |-------------------|-----------|----------------------------------|
1256
- | `CC_WWWROOT_HOST` | `0.0.0.0` | UI server bind address |
1257
- | `CC_WWWROOT_PORT` | `60050` | UI server port |
1258
-
1259
- ### `package.json` script
1260
-
1261
- ```json
1262
- {
1263
- "scripts": {
1264
- "editor": "npx @backtest-kit/cli --editor"
1265
- }
250
+ // Decide the symbol set yourself โ€” UI, database, API, or a list.
251
+ for (const symbol of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
252
+ // optional: await warmCandles({ exchangeName: "binance", interval: "1m", symbol, from, to });
253
+ Backtest.background(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "feb-2026" });
1266
254
  }
1267
255
  ```
1268
256
 
1269
257
  ```bash
1270
- npm run editor
258
+ npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
1271
259
  ```
1272
260
 
1273
- ## ๐Ÿ’พ Dumping Raw Candles
1274
-
1275
- `@backtest-kit/cli` can fetch raw OHLCV candles from any registered exchange and save them to a file โ€” no strategy file required.
1276
-
1277
- ### CLI Flags
261
+ The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter).
1278
262
 
1279
- | Flag | Type | Description |
1280
- |------|------|-------------|
1281
- | `--dump` | boolean | Enable candle dump mode |
1282
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
1283
- | `--timeframe` | string | Candle interval (default: `"15m"`) |
1284
- | `--limit` | string | Number of candles to fetch (default: `250`) |
1285
- | `--when` | string | End date for candle window โ€” ISO 8601 or Unix ms (default: now) |
1286
- | `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
1287
- | `--output` | string | Output file base name without extension (default: `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}`) |
1288
- | `--json` | boolean | Write candles as a JSON array to `./dump/{output}.json` |
1289
- | `--jsonl` | boolean | Write candles as JSONL (one row per line) to `./dump/{output}.jsonl` |
263
+ </details>
1290
264
 
1291
- The `dump/` directory is created in the current working directory (where the CLI is invoked from).
265
+ ---
1292
266
 
1293
- ### Exchange via `dump.module`
267
+ ## ๐Ÿ› ๏ธ Tooling modes
1294
268
 
1295
- By default the CLI registers CCXT Binance automatically. To use a different exchange โ€” or to configure API keys, custom rate limits, or a non-spot market โ€” create a `modules/dump.module.ts` file. The CLI loads it automatically before fetching candles.
269
+ Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
1296
270
 
1297
- The CLI looks for `modules/dump.module` in the current working directory
1298
-
1299
- ```
1300
- my-project/
1301
- โ”œโ”€โ”€ modules/
1302
- โ”‚ โ””โ”€โ”€ dump.module.ts โ† exchange registration
1303
- โ”œโ”€โ”€ dump/ โ† auto-created: candle output files
1304
- โ””โ”€โ”€ package.json
1305
- ```
271
+ > **The `<mode>.module` convention.** By default the CLI auto-registers CCXT Binance. To use a different exchange (custom API keys, rate limits, a non-spot market), drop a `modules/<mode>.module.ts` that calls `addExchangeSchema` from `backtest-kit`. The CLI loads it automatically before running, trying `.ts`/`.mjs`/`.cjs`; it's searched **next to the target file first, then in the project root**. `.env` is loaded root-first then the target-file dir (override), so API keys stay out of code.
1306
272
 
1307
- Inside `dump.module.ts` call `addExchangeSchema` from `backtest-kit`:
273
+ <details>
274
+ <summary>The shared <code>&lt;mode&gt;.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
1308
275
 
1309
276
  ```typescript
1310
- // modules/dump.module.ts
277
+ // modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
1311
278
  import { addExchangeSchema } from "backtest-kit";
1312
279
  import ccxt from "ccxt";
1313
280
 
1314
281
  addExchangeSchema({
1315
282
  exchangeName: "my-exchange",
1316
283
  getCandles: async (symbol, interval, since, limit) => {
1317
- const exchange = new ccxt.bybit({ enableRateLimit: true });
284
+ const exchange = new ccxt.bybit({
285
+ apiKey: process.env.BYBIT_API_KEY, secret: process.env.BYBIT_API_SECRET, enableRateLimit: true,
286
+ });
1318
287
  const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
1319
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
1320
- timestamp, open, high, low, close, volume,
1321
- }));
288
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
289
+ ({ timestamp, open, high, low, close, volume }));
1322
290
  },
1323
- formatPrice: (symbol, price) => price.toFixed(2),
1324
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
291
+ formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8),
1325
292
  });
1326
293
  ```
1327
294
 
1328
- ### Output
295
+ ```env
296
+ # .env (loaded root-first, then next to the target file)
297
+ BYBIT_API_KEY=xxx
298
+ BYBIT_API_SECRET=yyy
299
+ ```
300
+
301
+ </details>
1329
302
 
1330
- Each candle row contains OHLCV fields. Print to stdout:
303
+ ### ๐ŸŒฒ Pine โ€” run local PineScript (`--pine`)
304
+
305
+ Executes any local `.pine` file against a real exchange and prints results as a Markdown table โ€” no TradingView account. Reads every `plot()` that uses `display=display.data_window` as an output column (others ignored); column names come straight from the plot names.
1331
306
 
1332
307
  ```bash
1333
- npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 100
308
+ npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180 --when "2025-09-24T12:00:00.000Z"
1334
309
  ```
1335
310
 
1336
- Save to `./dump/BTCUSDT_100_15m_{timestamp}.jsonl`:
311
+ <details>
312
+ <summary>Pine flags, requirements & output</summary>
1337
313
 
1338
- ```bash
1339
- npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 100 --jsonl
1340
- ```
314
+ | Flag | Type | Description |
315
+ |------|------|-------------|
316
+ | `--pine` | boolean | Enable PineScript mode |
317
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
318
+ | `--timeframe` | string | Candle interval (default `"15m"`) |
319
+ | `--limit` | string | Candles to fetch (default `250`) |
320
+ | `--when` | string | End date โ€” ISO 8601 or Unix ms (default now) |
321
+ | `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
322
+ | `--output` | string | Output base name (default: `.pine` file name) |
323
+ | `--json` | boolean | Write plots as JSON array to `<pine-dir>/dump/{output}.json` |
324
+ | `--jsonl` | boolean | Write plots as JSONL to `<pine-dir>/dump/{output}.jsonl` |
325
+ | `--markdown` | boolean | Write Markdown table to `<pine-dir>/dump/{output}.md` |
1341
326
 
1342
- Fetch candles up to a specific date with `--when` and override the file name with `--output`:
327
+ `--limit` must cover indicator warmup bars โ€” rows before warmup show `N/A`. Positional: path to the `.pine` file. Exchange via `pine.module` (see convention above). Required plot form:
1343
328
 
1344
- ```bash
1345
- npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 \
1346
- --when "2026-02-28T00:00:00.000Z" \
1347
- --jsonl --output feb2026_btc
1348
- # โ†’ ./dump/feb2026_btc.jsonl
329
+ ```pine
330
+ //@version=5
331
+ indicator("MyIndicator", overlay=true)
332
+ plot(close, "Close", display=display.data_window)
333
+ plot(position, "Position", display=display.data_window)
1349
334
  ```
1350
335
 
1351
- Or add it to `package.json`:
336
+ Output (stdout, or `--markdown`/`--json`/`--jsonl` to `<pine-dir>/dump/`):
1352
337
 
1353
- ```json
1354
- {
1355
- "scripts": {
1356
- "dump": "npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl"
1357
- }
1358
- }
1359
338
  ```
1360
-
1361
- ```bash
1362
- npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
339
+ | Close | Position | timestamp |
340
+ | --- | --- | --- |
341
+ | 112871.28 | -1.0000 | 2025-09-22T15:00:00.000Z |
342
+ | 112736.00 | 0.0000 | 2025-09-22T18:30:00.000Z |
343
+ | 112653.90 | 1.0000 | 2025-09-22T22:15:00.000Z |
1363
344
  ```
1364
345
 
1365
- ## ๐Ÿž PnL Debug (`--pnldebug`)
346
+ </details>
1366
347
 
1367
- `@backtest-kit/cli` can simulate a hypothetical position minute by minute and print running PnL, peak profit, and maximum drawdown for each candle โ€” without placing any trades or loading a strategy file.
348
+ ### ๐ŸŽจ Editor โ€” visual Pine Script editor (`--editor`)
1368
349
 
1369
- ### CLI Flags
350
+ ![pine](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot32.png)
1370
351
 
1371
- | Flag | Type | Description |
1372
- |------|------|-------------|
1373
- | `--pnldebug` | boolean | Enable PnL debug mode |
1374
- | `--priceopen` | number | Entry price (required) |
1375
- | `--direction` | string | `long` or `short` (default: `long`) |
1376
- | `--when` | string | Start timestamp โ€” ISO 8601 or Unix ms (default: now) |
1377
- | `--minutes` | string | Number of 1m candles to simulate (default: `60`) |
1378
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
1379
- | `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
1380
- | `--output` | string | Output file base name (default: `{SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP}`) |
1381
- | `--json` | boolean | Save results as JSON array to `./dump/<output>.json` |
1382
- | `--jsonl` | boolean | Save results as JSONL to `./dump/<output>.jsonl` |
1383
- | `--markdown` | boolean | Save results as Markdown table to `./dump/<output>.md` |
1384
-
1385
- ### Output columns
1386
-
1387
- | Column | Description |
1388
- |--------|-------------|
1389
- | `min` | Minute offset from start (1-based) |
1390
- | `timestamp` | Candle timestamp (ISO 8601) |
1391
- | `close` | Candle close price |
1392
- | `pnl%` | Running PnL vs entry price (signed %) |
1393
- | `peak%` | Highest PnL reached so far (always โ‰ฅ 0) |
1394
- | `drawdown%` | Lowest PnL reached so far (always โ‰ค 0) |
352
+ A browser-based Pine Script editor (powered by `@backtest-kit/ui`) with a live chart that updates on **โ–ถ Run**.
1395
353
 
1396
- ### Exchange via `pnldebug.module`
354
+ ```bash
355
+ npx @backtest-kit/cli --editor # โ†’ http://localhost:60050?pine=1 opens automatically
356
+ ```
1397
357
 
1398
- By default the CLI registers CCXT Binance automatically. To use a different exchange, create a `modules/pnldebug.module.ts` file in the current working directory โ€” the CLI loads it automatically before fetching candles.
358
+ <details>
359
+ <summary>Editor behavior & exchange</summary>
1399
360
 
1400
- ```typescript
1401
- // modules/pnldebug.module.ts
1402
- import { addExchangeSchema } from "backtest-kit";
1403
- import ccxt from "ccxt";
361
+ The CLI loads `./modules/editor.module` if present (register your exchange, same as `pine.module`), starts the `@backtest-kit/ui` server on `CC_WWWROOT_PORT` (default `60050`), and opens the editor in your browser. **Ctrl+C** stops it. Env: `CC_WWWROOT_HOST` (default `0.0.0.0`), `CC_WWWROOT_PORT` (default `60050`).
1404
362
 
1405
- addExchangeSchema({
1406
- exchangeName: "my-exchange",
1407
- getCandles: async (symbol, interval, since, limit) => {
1408
- const exchange = new ccxt.bybit({ enableRateLimit: true });
1409
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
1410
- return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
1411
- timestamp, open, high, low, close, volume,
1412
- }));
1413
- },
1414
- formatPrice: (symbol, price) => price.toFixed(2),
1415
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
1416
- });
1417
- ```
363
+ </details>
1418
364
 
1419
- ### Usage
365
+ ### ๐Ÿ’พ Candle Dump (`--dump`)
1420
366
 
1421
- Print to stdout (default table format):
367
+ Fetch raw OHLCV candles from any registered exchange and save them โ€” no strategy file required. `dump/` is created in the current working directory.
1422
368
 
1423
369
  ```bash
1424
- npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
370
+ npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --when "2026-02-28T00:00:00.000Z" --jsonl --output feb2026_btc
371
+ # โ†’ ./dump/feb2026_btc.jsonl
1425
372
  ```
1426
373
 
1427
- Save as Markdown:
374
+ <details>
375
+ <summary>Dump flags</summary>
1428
376
 
1429
- ```bash
1430
- npx @backtest-kit/cli --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
1431
- # โ†’ ./dump/BTCUSDT_long_67956.73_{timestamp}.md
1432
- ```
377
+ | Flag | Type | Description |
378
+ |------|------|-------------|
379
+ | `--dump` | boolean | Enable candle dump |
380
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
381
+ | `--timeframe` | string | Candle interval (default `"15m"`) |
382
+ | `--limit` | string | Candles to fetch (default `250`) |
383
+ | `--when` | string | End date โ€” ISO 8601 or Unix ms (default now) |
384
+ | `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
385
+ | `--output` | string | Output base name (default `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}`) |
386
+ | `--json` | boolean | Write candles as JSON array to `./dump/{output}.json` |
387
+ | `--jsonl` | boolean | Write candles as JSONL to `./dump/{output}.jsonl` |
1433
388
 
1434
- Override the output file name with `--output`:
389
+ Exchange via `dump.module` (see convention above), searched in the current working directory. No flag โ†’ print to stdout.
1435
390
 
1436
- ```bash
1437
- npx @backtest-kit/cli --pnldebug --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120 \
1438
- --jsonl --output feb25_short_debug
1439
- # โ†’ ./dump/feb25_short_debug.jsonl
1440
- ```
391
+ </details>
1441
392
 
1442
- Or add it to `package.json`:
393
+ ### ๐Ÿž PnL Debug (`--pnldebug`)
1443
394
 
1444
- ```json
1445
- {
1446
- "scripts": {
1447
- "pnldebug": "npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when \"2025-02-25\" --minutes 120"
1448
- }
1449
- }
1450
- ```
395
+ Simulate a hypothetical position minute by minute โ€” running PnL, peak profit, max drawdown per candle โ€” without placing trades or loading a strategy.
1451
396
 
1452
397
  ```bash
1453
- npm run pnldebug
398
+ npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
1454
399
  ```
1455
400
 
1456
- ### Example stdout output
401
+ <details>
402
+ <summary>PnL Debug flags, columns & sample output</summary>
403
+
404
+ | Flag | Type | Description |
405
+ |------|------|-------------|
406
+ | `--pnldebug` | boolean | Enable PnL debug |
407
+ | `--priceopen` | number | Entry price (**required**) |
408
+ | `--direction` | string | `long` or `short` (default `long`) |
409
+ | `--when` | string | Start timestamp โ€” ISO 8601 or Unix ms (default now) |
410
+ | `--minutes` | string | Number of 1m candles to simulate (default `60`) |
411
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
412
+ | `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
413
+ | `--output` | string | Output base name (default `{SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP}`) |
414
+ | `--json` / `--jsonl` / `--markdown` | boolean | Save to `./dump/<output>.{json,jsonl,md}` |
415
+
416
+ Columns: `min` (1-based offset), `timestamp`, `close`, `pnl%` (signed, vs entry), `peak%` (highest so far, โ‰ฅ0), `drawdown%` (lowest so far, โ‰ค0). Exchange via `pnldebug.module` (convention above).
1457
417
 
1458
418
  ```
1459
419
  Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
1460
-
1461
420
  min | timestamp | close | pnl% | peak% | drawdown%
1462
- -----------------------------------------------------------------------------------
1463
421
  1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
1464
422
  2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
1465
- ...
1466
423
  120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
1467
424
  ```
1468
425
 
1469
- ## ๐Ÿ› Broker Debug (`--brokerdebug`)
426
+ </details>
427
+
428
+ ### ๐Ÿ› Broker Debug (`--brokerdebug`)
1470
429
 
1471
- `@backtest-kit/cli` can fire a single broker commit against your live broker adapter without running a full strategy โ€” useful for verifying that your `brokerdebug.module` correctly wires up exchange calls.
430
+ Fire a single broker commit against your live adapter without a full strategy โ€” verify your `brokerdebug.module` wires exchange calls correctly before waiting hours for a real signal.
1472
431
 
1473
- ### CLI Flags
432
+ ```bash
433
+ npx @backtest-kit/cli --brokerdebug --commit signal-open --symbol BTCUSDT
434
+ ```
435
+
436
+ <details>
437
+ <summary>Broker Debug flags, commit types & how it works</summary>
1474
438
 
1475
439
  | Flag | Type | Description |
1476
440
  |------|------|-------------|
1477
- | `--brokerdebug` | boolean | Enable broker debug mode |
1478
- | `--commit` | string | Commit type to fire (default: `"signal-open"`) |
1479
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
1480
- | `--exchange` | string | Exchange name (default: first registered) |
441
+ | `--brokerdebug` | boolean | Enable broker debug |
442
+ | `--commit` | string | Commit type to fire (default `"signal-open"`) |
443
+ | `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
444
+ | `--exchange` | string | Exchange (default: first registered) |
1481
445
 
1482
- **Available `--commit` values:**
446
+ `--commit` values โ†’ hook: `signal-open`โ†’`onSignalOpenCommit`, `signal-close`โ†’`onSignalCloseCommit`, `partial-profit`โ†’`onPartialProfitCommit`, `partial-loss`โ†’`onPartialLossCommit`, `average-buy`โ†’`onAverageBuyCommit`, `trailing-stop`โ†’`onTrailingStopCommit`, `trailing-take`โ†’`onTrailingTakeCommit`, `breakeven`โ†’`onBreakevenCommit`.
1483
447
 
1484
- | Value | Broker hook |
1485
- |-------|-------------|
1486
- | `signal-open` | `onSignalOpenCommit` |
1487
- | `signal-close` | `onSignalCloseCommit` |
1488
- | `partial-profit` | `onPartialProfitCommit` |
1489
- | `partial-loss` | `onPartialLossCommit` |
1490
- | `average-buy` | `onAverageBuyCommit` |
1491
- | `trailing-stop` | `onTrailingStopCommit` |
1492
- | `trailing-take` | `onTrailingTakeCommit` |
1493
- | `breakeven` | `onBreakevenCommit` |
448
+ The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--symbol`, derives a synthetic payload from `currentPrice` (TP = +2%, SL = โˆ’2%), and calls the selected hook once; exits `0` on success. The module registers a `Broker` adapter (`Broker.useBrokerAdapter(...)` + `Broker.enable()`), not an exchange.
1494
449
 
1495
- ### How It Works
450
+ </details>
1496
451
 
1497
- The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--symbol` / `--timeframe`, derives synthetic payload values from `currentPrice` (TP = +2%, SL = -2%), and calls the selected broker hook once. Exits with code `0` on success.
452
+ ### ๐Ÿ—‘๏ธ Flush (`--flush`)
1498
453
 
1499
- ### Broker via `brokerdebug.module`
454
+ Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
1500
455
 
1501
- Create a `modules/brokerdebug.module.ts` file and register your broker adapter:
456
+ ```bash
457
+ npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts ./content/mar_2026.strategy/modules/backtest.module.ts
458
+ ```
1502
459
 
1503
- ```typescript
1504
- // modules/brokerdebug.module.ts
1505
- import { Broker } from 'backtest-kit';
1506
- import { myExchange } from './exchange.mjs';
460
+ <details>
461
+ <summary>What flush removes</summary>
1507
462
 
1508
- class MyBroker {
1509
- async onSignalOpenCommit({ symbol, priceOpen, position }) {
1510
- await myExchange.openPosition(symbol, position, priceOpen);
1511
- }
1512
- // ... other hooks
1513
- }
463
+ For each positional entry point the CLI resolves its directory and removes from `<entry-dir>/dump/`: `report` (backtest `.jsonl`), `log` (`log.jsonl`), `markdown` (exported reports), `agent` (agent outlines). Candle cache (`dump/data/`) and AI forecast outlines (`dump/outline/`) are **not** removed.
1514
464
 
1515
- Broker.useBrokerAdapter(MyBroker);
1516
- Broker.enable();
1517
- ```
465
+ </details>
466
+
467
+ ---
1518
468
 
1519
- ### Usage
469
+ ## ๐Ÿ—‚๏ธ Project & monorepo
470
+
471
+ ### Scaffolding (`--init`)
472
+
473
+ Bootstraps a ready-to-use project with an example strategy, an example Pine indicator, an AI-agent `CLAUDE.md`, and documentation fetched automatically. The target dir must not exist or be empty.
1520
474
 
1521
475
  ```bash
1522
- npx @backtest-kit/cli --brokerdebug --commit signal-open --symbol BTCUSDT
1523
- npx @backtest-kit/cli --brokerdebug --commit partial-profit --symbol ETHUSDT --timeframe 1h
476
+ npx @backtest-kit/cli --init --output my-trading-bot # โ†’ ./my-trading-bot/
1524
477
  ```
1525
478
 
1526
- ## ๐Ÿ—‘๏ธ Flushing Strategy Output (`--flush`)
479
+ <details>
480
+ <summary>Generated structure & automatic docs fetch</summary>
1527
481
 
1528
- `@backtest-kit/cli` can delete generated output folders from one or more strategy dump directories without touching cached candle data.
482
+ ```
483
+ backtest-kit-project/
484
+ โ”œโ”€โ”€ package.json # pre-configured with all backtest-kit deps
485
+ โ”œโ”€โ”€ CLAUDE.md # AI-agent guide for writing strategies
486
+ โ”œโ”€โ”€ content/feb_2026.strategy.ts # example strategy entry point
487
+ โ”œโ”€โ”€ math/feb_2026.pine # example PineScript indicator
488
+ โ”œโ”€โ”€ modules/{dump,pine}.module.ts # exchange schemas for --dump / --pine
489
+ โ”œโ”€โ”€ report/feb_2026.md # example research report
490
+ โ”œโ”€โ”€ docs/{...}.md + docs/lib/ # guides + fetched library READMEs
491
+ โ””โ”€โ”€ scripts/fetch_docs.mjs # downloads library READMEs into docs/lib/
492
+ ```
1529
493
 
1530
- ### CLI Flags
494
+ After scaffolding the CLI runs `scripts/fetch_docs.mjs`, downloading the latest READMEs for `backtest-kit`, `@backtest-kit/graph`, `@backtest-kit/pinets`, `@backtest-kit/cli`, `garch`, `volume-anomaly`, `agent-swarm-kit`, `functools-kit` into `docs/lib/`. Re-run anytime with `node ./scripts/fetch_docs.mjs` or `npm run sync:lib`.
1531
495
 
1532
- | Flag | Type | Description |
1533
- |------|------|-------------|
1534
- | `--flush` | boolean | Enable flush mode |
496
+ </details>
1535
497
 
1536
- **Positional arguments (required):** one or more strategy entry point files. For each entry point the CLI resolves its directory and removes the following subdirectories from `<entry-dir>/dump/`:
498
+ ### Docker (`--docker`)
1537
499
 
1538
- | Folder | Contents |
1539
- |--------|----------|
1540
- | `report` | Backtest report files (`.jsonl`) |
1541
- | `log` | Run logs (`log.jsonl`) |
1542
- | `markdown` | Exported Markdown reports |
1543
- | `agent` | Agent outline files |
500
+ Scaffolds a self-contained Docker workspace with `docker-compose.yaml` and a strategy entry point, for zero-downtime live trading.
1544
501
 
1545
- Candle cache (`dump/data/`) and AI forecast outlines (`dump/outline/`) are **not** removed.
502
+ ```bash
503
+ npx @backtest-kit/cli --docker && cd backtest-kit-docker
504
+ MODE=live SYMBOL=TRXUSDT STRATEGY_FILE=./content/feb_2026/feb_2026.strategy.ts docker-compose up -d
505
+ ```
1546
506
 
1547
- ### Usage
507
+ <details>
508
+ <summary>Two launch modes & environment variables</summary>
1548
509
 
1549
- Flush a single strategy:
510
+ **1. `command:` in `docker-compose.yaml`** โ€” pin mode and flags directly; the entrypoint forwards all args to the CLI unchanged:
1550
511
 
1551
- ```bash
1552
- npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts
512
+ ```yaml
513
+ command: [--live, --symbol, TRXUSDT, --strategy, feb_2026_strategy, --exchange, ccxt-exchange, ./content/feb_2026/feb_2026.strategy.ts, --ui]
1553
514
  ```
1554
515
 
1555
- Flush multiple strategies at once:
516
+ **2. Inline env vars** โ€” `MODE` + `STRATEGY_FILE` on the command line, no file edits:
1556
517
 
1557
- ```bash
1558
- npx @backtest-kit/cli --flush \
1559
- ./content/feb_2026.strategy/modules/backtest.module.ts \
1560
- ./content/mar_2026.strategy/modules/backtest.module.ts
518
+ | Variable | Required | Default | Description |
519
+ |----------|----------|---------|-------------|
520
+ | `MODE` | yes | โ€” | `backtest` \| `live` \| `paper` \| `walker` |
521
+ | `STRATEGY_FILE` | yes | โ€” | Path to entry point (relative to `working_dir`) |
522
+ | `SYMBOL` | no | `BTCUSDT` | Trading pair |
523
+ | `STRATEGY` / `EXCHANGE` / `FRAME` | no | first registered | Names |
524
+ | `UI` / `TELEGRAM` / `VERBOSE` / `NO_CACHE` / `NO_FLUSH` / `ENTRY` | no | โ€” | Any non-empty value enables the matching flag |
525
+
526
+ </details>
527
+
528
+ ### Monorepo cwd resolution
529
+
530
+ When the CLI loads an entry point it **changes the working directory to that file's location**, so every relative path (`dump/`, `modules/`, `template/`) resolves inside that strategy's folder. Each strategy gets its own `.env`, broker modules, templates, and dump dir โ€” so the same tool scales from one strategy to a desk of them.
531
+
532
+ <details>
533
+ <summary>How it works + isolated resources</summary>
534
+
535
+ `ResolveService` runs, before executing your entry point:
536
+
537
+ ```
538
+ process.chdir(path.dirname(entryPoint)) // cwd โ†’ strategy directory
539
+ dotenv.config({ path: rootDir + '/.env' }) // root .env first
540
+ dotenv.config({ path: strategyDir + '/.env', override: true }) // strategy .env overrides
541
+ ```
542
+
543
+ ```
544
+ monorepo/
545
+ โ”œโ”€โ”€ package.json # root scripts (one per strategy)
546
+ โ”œโ”€โ”€ .env # shared API keys
547
+ โ””โ”€โ”€ strategies/
548
+ โ”œโ”€โ”€ oct_2025/
549
+ โ”‚ โ”œโ”€โ”€ index.mjs # registers exchange/frame/strategy schemas
550
+ โ”‚ โ”œโ”€โ”€ .env # overrides root .env for this strategy
551
+ โ”‚ โ”œโ”€โ”€ modules/{live,paper,backtest}.module.mjs # broker adapters (optional)
552
+ โ”‚ โ”œโ”€โ”€ template/ # custom Mustache templates (optional)
553
+ โ”‚ โ””โ”€โ”€ dump/ # auto-created: candle cache + reports
554
+ โ””โ”€โ”€ dec_2025/ โ€ฆ
1561
555
  ```
1562
556
 
1563
- Or add it to `package.json`:
557
+ | Resource | Path (relative to strategy dir) | Isolated |
558
+ |----------|----------------------------------|----------|
559
+ | Candle cache | `./dump/data/candle/` | โœ… per-strategy |
560
+ | Backtest reports | `./dump/` | โœ… per-strategy |
561
+ | Broker module (live/paper/backtest) | `./modules/{live,paper,backtest}.module.mjs` | โœ… per-strategy |
562
+ | Config module (walker) | `./modules/walker.module.mjs` | โœ… loaded once |
563
+ | Telegram templates | `./template/*.mustache` | โœ… per-strategy |
564
+ | Environment variables | `./.env` (overrides root) | โœ… per-strategy |
565
+
566
+ Each run produces its own `dump/` โ€” easy to compare results across periods, by inspection or by pointing an AI agent at a specific folder.
567
+
568
+ </details>
569
+
570
+ ### Folder-based import aliases
571
+
572
+ Every **top-level folder** in `process.cwd()` automatically becomes a bare import alias inside any strategy file โ€” no config, just create the folder. Extract shared utilities, indicators, or AI-agent logic into named folders and reuse them across strategies without relative-path hell.
573
+
574
+ <details>
575
+ <summary>Resolution table, structure & tsconfig</summary>
576
+
577
+ | Import | Resolves to |
578
+ |--------|-------------|
579
+ | `import { fn } from "utils"` | `<cwd>/utils/index.ts` (or `.js`/`.mjs`/`.cjs`) |
580
+ | `import { calcRSI } from "math/rsi"` | `<cwd>/math/rsi.ts` |
581
+ | `import { research } from "logic"` | `<cwd>/logic/index.ts` |
582
+ | `import { X } from "logic/contract/ResearchResponse.contract"` | `<cwd>/logic/contract/ResearchResponse.contract.ts` |
583
+
584
+ Both barrel and deep-subpath imports are supported. Add a matching `paths` entry to `tsconfig.json` so the editor resolves them:
1564
585
 
1565
586
  ```json
1566
587
  {
1567
- "scripts": {
1568
- "flush": "npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts"
1569
- }
588
+ "compilerOptions": {
589
+ "moduleResolution": "bundler",
590
+ "paths": { "logic": ["./logic/index.ts"], "logic/*": ["./logic/*"], "math": ["./math/index.ts"], "math/*": ["./math/*"], "utils": ["./utils/index.ts"], "utils/*": ["./utils/*"] }
591
+ },
592
+ "include": ["./logic", "./math", "./utils", "./content", "./modules"]
1570
593
  }
1571
594
  ```
1572
595
 
1573
- ```bash
1574
- npm run flush
1575
- ```
596
+ </details>
1576
597
 
1577
- ## ๐Ÿ—‚๏ธ Scaffolding a New Project (`--init`)
598
+ ### Entry point formats
1578
599
 
1579
- `@backtest-kit/cli` can bootstrap a ready-to-use project directory with a pre-configured layout, example strategy files, and all documentation fetched automatically.
600
+ The CLI auto-detects the format and loads it with the right runtime โ€” no flags. `.ts` via [`tsx`](https://tsx.is/) `tsImport()` (handles ESMโ†”CJS cross-imports, no `tsc` step), `.mjs` via native `import()` (top-level `await`, ESM), `.cjs` via native `require()` (legacy/dual-package). Add `tsx` to deps for `.ts` strategies.
1580
601
 
1581
- ### CLI Flags
602
+ ---
1582
603
 
1583
- | Flag | Type | Description |
1584
- |------|------|-------------|
1585
- | `--init` | boolean | Scaffold a new project |
1586
- | `--output` | string | Target directory name (default: `backtest-kit-project`) |
604
+ ## ๐Ÿ”Œ Broker adapter โ€” transactional live orders
1587
605
 
1588
- ### Usage
606
+ Mode-specific module files register a `Broker` adapter via side-effect import before the strategy starts. From then 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 (atomic rollback, retried next tick). No manual wiring; in backtest mode no adapter is called at all.
1589
607
 
1590
- ```bash
1591
- npx @backtest-kit/cli --init
1592
- ```
608
+ | Mode flag | Module file | Loaded before |
609
+ |-----------|-------------|----------------|
610
+ | `--live` | `./modules/live.module.mjs` | `Live.background()` |
611
+ | `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
612
+ | `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
613
+ | `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
614
+ | `--main` | `./modules/main.module.mjs` | the custom entry point |
615
+ | `--brokerdebug` | `./modules/brokerdebug.module.mjs` | the broker commit test |
1593
616
 
1594
- Creates `./backtest-kit-project/` in the current working directory.
617
+ > Resolved relative to `cwd` (the strategy dir); `.mjs`/`.cjs`/`.ts` tried automatically. A missing module is a soft warning, not an error.
1595
618
 
1596
- Override the directory name with `--output`:
619
+ <details>
620
+ <summary>Adapter example & hook reference</summary>
1597
621
 
1598
- ```bash
1599
- npx @backtest-kit/cli --init --output my-trading-bot
622
+ ```javascript
623
+ // live.module.mjs
624
+ import { Broker } from 'backtest-kit';
625
+ import { myExchange } from './exchange.mjs';
626
+
627
+ class MyBroker {
628
+ async onSignalOpenCommit({ symbol, priceOpen, direction }) { await myExchange.openPosition(symbol, direction, priceOpen); }
629
+ async onSignalCloseCommit({ symbol, priceClosed }) { await myExchange.closePosition(symbol, priceClosed); }
630
+ async onPartialProfitCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'sell', quantity: cost / currentPrice }); }
631
+ async onAverageBuyCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'buy', quantity: cost / currentPrice }); }
632
+ }
633
+
634
+ Broker.useBrokerAdapter(MyBroker);
635
+ Broker.enable();
1600
636
  ```
1601
637
 
1602
- Creates `./my-trading-bot/`.
638
+ | Method | Payload type | Triggered on |
639
+ |--------|--------------|--------------|
640
+ | `onSignalOpenCommit` | `BrokerSignalOpenPayload` | Position activation |
641
+ | `onSignalCloseCommit` | `BrokerSignalClosePayload` | SL / TP / manual close |
642
+ | `onPartialProfitCommit` | `BrokerPartialProfitPayload` | Partial profit |
643
+ | `onPartialLossCommit` | `BrokerPartialLossPayload` | Partial loss |
644
+ | `onTrailingStopCommit` | `BrokerTrailingStopPayload` | SL adjustment |
645
+ | `onTrailingTakeCommit` | `BrokerTrailingTakePayload` | TP adjustment |
646
+ | `onBreakevenCommit` | `BrokerBreakevenPayload` | SL moved to entry |
647
+ | `onAverageBuyCommit` | `BrokerAverageBuyPayload` | DCA entry |
1603
648
 
1604
- The target directory must not exist or must be empty โ€” the command aborts if it contains any files.
649
+ All methods are optional; unimplemented hooks are silently skipped. TypeScript: implement `Partial<IBroker>` with typed payloads (`BrokerSignalOpenPayload`, etc.).
1605
650
 
1606
- ### Generated Project Structure
651
+ </details>
1607
652
 
1608
- ```
1609
- backtest-kit-project/
1610
- โ”œโ”€โ”€ package.json # pre-configured with all backtest-kit dependencies
1611
- โ”œโ”€โ”€ .gitignore
1612
- โ”œโ”€โ”€ CLAUDE.md # AI-agent guide for writing strategies
1613
- โ”œโ”€โ”€ content/
1614
- โ”‚ โ””โ”€โ”€ feb_2026.strategy.ts # example strategy entry point
1615
- โ”œโ”€โ”€ docs/
1616
- โ”‚ โ”œโ”€โ”€ lib/ # fetched automatically (see below)
1617
- โ”‚ โ”œโ”€โ”€ backtest_actions.md
1618
- โ”‚ โ”œโ”€โ”€ backtest_graph_pattern.md
1619
- โ”‚ โ”œโ”€โ”€ backtest_logging_jsonl.md
1620
- โ”‚ โ”œโ”€โ”€ backtest_pinets_usage.md
1621
- โ”‚ โ”œโ”€โ”€ backtest_risk_async.md
1622
- โ”‚ โ”œโ”€โ”€ backtest_strategy_structure.md
1623
- โ”‚ โ”œโ”€โ”€ pine_debug.md
1624
- โ”‚ โ””โ”€โ”€ pine_indicator_warmup.md
1625
- โ”œโ”€โ”€ math/
1626
- โ”‚ โ””โ”€โ”€ feb_2026.pine # example PineScript indicator
1627
- โ”œโ”€โ”€ modules/
1628
- โ”‚ โ”œโ”€โ”€ dump.module.ts # exchange schema for --dump mode
1629
- โ”‚ โ””โ”€โ”€ pine.module.ts # exchange schema for --pine mode
1630
- โ”œโ”€โ”€ report/
1631
- โ”‚ โ””โ”€โ”€ feb_2026.md # example strategy research report
1632
- โ””โ”€โ”€ scripts/
1633
- โ””โ”€โ”€ fetch_docs.mjs # utility: downloads library READMEs into docs/lib/
1634
- ```
653
+ ---
1635
654
 
1636
- ### Automatic Documentation Fetch
655
+ ## โš™๏ธ Configuration files (`config/*`)
1637
656
 
1638
- After scaffolding, the CLI immediately runs `scripts/fetch_docs.mjs` inside the new project, which downloads the latest README files for all bundled libraries into `docs/lib/`:
657
+ Loaded from `{projectRoot}/config/`. The three runtime configs load in order โ€” **`setup.config` โ†’ `loader.config` โ†’ `alias.config`** โ€” before any strategy or module code. The UI/Telegram configs resolve **strategy dir โ†’ project root โ†’ package default** (first match wins) and accept `.ts`/`.cjs`/`.mjs`/`.js`.
1639
658
 
1640
- | File | Source |
1641
- |------|--------|
1642
- | `backtest-kit.md` | `backtest-kit` README |
1643
- | `backtest-kit__graph.md` | `@backtest-kit/graph` README |
1644
- | `backtest-kit__pinets.md` | `@backtest-kit/pinets` README |
1645
- | `backtest-kit__cli.md` | `@backtest-kit/cli` README |
1646
- | `garch.md` | `garch` README |
1647
- | `volume-anomaly.md` | `volume-anomaly` README |
1648
- | `agent-swarm-kit.md` | `agent-swarm-kit` README |
1649
- | `functools-kit.md` | `functools-kit` README |
659
+ ### `setup.config` โ€” persistence & one-time init
1650
660
 
1651
- You can re-run this script at any time to refresh the docs:
661
+ Loaded once before any persistence call. **When present, the CLI skips its own default adapter registration** โ€” your config takes full ownership of the persistence layer.
1652
662
 
1653
- ```bash
1654
- cd backtest-kit-project
1655
- node ./scripts/fetch_docs.mjs
1656
- ```
663
+ <details>
664
+ <summary>MongoDB + Redis via @backtest-kit/mongo</summary>
1657
665
 
1658
- Or via the pre-configured npm script:
666
+ `setup()` registers all 15 persistence adapters in one call, reading connection params from env (or passed explicitly):
1659
667
 
1660
- ```bash
1661
- npm run sync:lib
668
+ ```ts
669
+ // config/setup.config.ts
670
+ import { setup } from '@backtest-kit/mongo';
671
+ setup(); // or setup({ CC_MONGO_CONNECTION_STRING, CC_REDIS_HOST, CC_REDIS_PORT, CC_REDIS_PASSWORD })
1662
672
  ```
1663
673
 
1664
- ## ๐Ÿณ Running in Docker (`--docker`)
674
+ ```env
675
+ CC_MONGO_CONNECTION_STRING=mongodb://localhost:27017/backtest-kit
676
+ CC_REDIS_HOST=127.0.0.1
677
+ CC_REDIS_PORT=6379
678
+ ```
1665
679
 
1666
- CLI can create a ready-to-use Docker workspace: self-contained directory with `docker-compose.yaml` and a strategy entry point.
680
+ No strategy-code changes โ€” adapters are wired transparently before the first persistence call.
1667
681
 
1668
- ### CLI Flags
682
+ </details>
1669
683
 
1670
- | Flag | Type | Description |
1671
- |------|------|-------------|
1672
- | `--docker` | boolean | Scaffold a Docker workspace |
1673
- | `--output` | string | Target directory name (default: `backtest-kit-docker`) |
684
+ ### `loader.config` โ€” async startup gate
1674
685
 
1675
- ### Usage
686
+ Loaded **after** `setup.config`, **before** strategy/module code. Unlike `setup.config` (side-effect import), it exports a function the CLI `await`s โ€” use it to wait for an async dependency before the run starts.
1676
687
 
1677
- ```bash
1678
- npx @backtest-kit/cli --docker
1679
- ```
688
+ <details>
689
+ <summary>When to use it, export styles & examples</summary>
1680
690
 
1681
- Creates `./backtest-kit-docker/` in the current working directory.
691
+ **Use it to:** wire microfrontends in a monorepo (pre-load sibling packages, hydrate a shared DI container); wait for a DB connection so the backtest fails fast instead of mid-run; warm caches / external APIs (instruments, calendar, fee tables); run schema migrations before signals flow.
1682
692
 
1683
- Override the directory name with `--output`:
693
+ Exactly one export style โ€” **never both** (if both present, `default` wins):
1684
694
 
1685
- ```bash
1686
- npx @backtest-kit/cli --docker --output my-docker-workspace
695
+ ```ts
696
+ // config/loader.config.ts โ€” default export (preferred)
697
+ export default async () => { await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!); await redis.ping(); };
698
+ // โ€” or named export
699
+ export const loader = async () => { /* โ€ฆ */ };
1687
700
  ```
1688
701
 
1689
- The target directory must not exist or must be empty โ€” the command aborts if it contains any files.
702
+ `@backtest-kit/mongo`'s `setup()` registers adapters synchronously but doesn't block on the connection; gate the run on a real connection here. To stitch microfrontends: `import "@my-org/brokers"; import "@my-org/signals";` (the `@my-org` alias is declared in `alias.config`).
1690
703
 
1691
- ### Two Launch Modes
704
+ </details>
1692
705
 
1693
- The Docker image entrypoint supports two ways to run a strategy:
706
+ ### `alias.config` โ€” override any module import
1694
707
 
1695
- #### 1. `command:` in `docker-compose.yaml`
708
+ Override any Node module import without touching strategy code. Loaded once on the first `import` and applied globally โ€” e.g. replace a heavy dependency with a stub for backtesting, or swap an external API for a mock in CI.
1696
709
 
1697
- Pin mode and flags directly in the compose file. The entrypoint forwards all arguments to the CLI unchanged:
710
+ <details>
711
+ <summary>Formats & async factory</summary>
1698
712
 
1699
- ```yaml
1700
- command:
1701
- - --live
1702
- - --symbol
1703
- - TRXUSDT
1704
- - --strategy
1705
- - feb_2026_strategy
1706
- - --exchange
1707
- - ccxt-exchange
1708
- - ./content/feb_2026/feb_2026.strategy.ts
1709
- - --ui
713
+ ```ts
714
+ // config/alias.config.ts โ€” named export
715
+ export const ccxt = require("./stubs/ccxt.stub.cjs");
716
+ // config/alias.config.cjs โ€” default export
717
+ module.exports = { ccxt: require("./stubs/ccxt.stub.cjs") };
1710
718
  ```
1711
719
 
1712
- #### 2. Inline environment variables
720
+ It may also export an **async factory** the CLI `await`s before strategy code runs โ€” handy for ESM-only modules that `require()` would throw on:
1713
721
 
1714
- Pass `MODE` and `STRATEGY_FILE` on the command line โ€” no file edits needed:
1715
-
1716
- ```bash
1717
- MODE=live SYMBOL=TRXUSDT STRATEGY_FILE=./content/feb_2026/feb_2026.strategy.ts docker-compose up -d
722
+ ```ts
723
+ // async factory (default export); or `export const loader = async () => ({...})`
724
+ export default async () => ({ nanoid: await import("nanoid"), "p-limit": await import("p-limit") });
1718
725
  ```
1719
726
 
1720
- | Variable | Required | Default | Description |
1721
- |----------|----------|---------|-------------|
1722
- | `MODE` | yes | โ€” | `backtest` \| `live` \| `paper` \| `walker` |
1723
- | `STRATEGY_FILE` | yes | โ€” | Path to strategy entry point (relative to `working_dir`) |
1724
- | `SYMBOL` | no | `BTCUSDT` | Trading pair |
1725
- | `STRATEGY` | no | first registered | Strategy name |
1726
- | `EXCHANGE` | no | first registered | Exchange name |
1727
- | `FRAME` | no | first registered | Frame name (backtest only) |
1728
- | `UI` | no | โ€” | Any non-empty value enables `--ui` |
1729
- | `TELEGRAM` | no | โ€” | Any non-empty value enables `--telegram` |
1730
- | `VERBOSE` | no | โ€” | Any non-empty value enables `--verbose` |
1731
- | `NO_CACHE` | no | โ€” | Any non-empty value enables `--noCache` |
1732
- | `NO_FLUSH` | no | โ€” | Any non-empty value enables `--noFlush` |
1733
- | `ENTRY` | no | โ€” | Any non-empty value enables multiple symbols from userspace |
727
+ Both styles supported, never both at once (`default` wins). When strategy code calls `require("ccxt")`, the loader checks the alias table first โ€” no monkey-patching of `node_modules`. Applies to **all** modules in the process (not per-strategy).
1734
728
 
1735
- ## ๐ŸŒ Environment Variables
729
+ </details>
1736
730
 
1737
- Create a `.env` file in your project root:
731
+ ### `symbol.config` & `notification.config` โ€” UI dashboard
1738
732
 
1739
- ```env
1740
- # Telegram notifications (required for --telegram)
1741
- CC_TELEGRAM_TOKEN=your_bot_token_here
1742
- CC_TELEGRAM_CHANNEL=-100123456789
733
+ <details>
734
+ <summary>symbol.config โ€” restrict/reorder the UI symbol list</summary>
1743
735
 
1744
- # Web UI server (optional, defaults shown)
1745
- CC_WWWROOT_HOST=0.0.0.0
1746
- CC_WWWROOT_PORT=60050
736
+ By default the UI shows all exchange symbols. Override with a `config/symbol.config` (resolution: strategy dir โ†’ project root โ†’ package default):
1747
737
 
1748
- # Custom QuickChart service URL (optional)
1749
- CC_QUICKCHART_HOST=
738
+ ```ts
739
+ // config/symbol.config.ts
740
+ export const symbol_list = [
741
+ { icon: "/icon/btc.png", logo: "/icon/128/btc.png", symbol: "BTCUSDT", displayName: "Bitcoin", color: "#F7931A", priority: 50, description: "Bitcoin โ€” the first and most popular cryptocurrency" },
742
+ { icon: "/icon/eth.png", logo: "/icon/128/eth.png", symbol: "ETHUSDT", displayName: "Ethereum", color: "#6F42C1", priority: 50, description: "Ethereum โ€” a blockchain platform for smart contracts" },
743
+ ];
1750
744
  ```
1751
745
 
1752
- | Variable | Default | Description |
1753
- |------------------------|-------------|---------------------------------------|
1754
- | `CC_TELEGRAM_TOKEN` | โ€” | Telegram bot token (from @BotFather) |
1755
- | `CC_TELEGRAM_CHANNEL` | โ€” | Telegram channel or chat ID |
1756
- | `CC_WWWROOT_HOST` | `0.0.0.0` | UI server bind address |
1757
- | `CC_WWWROOT_PORT` | `60050` | UI server port |
1758
- | `CC_QUICKCHART_HOST` | โ€” | Self-hosted QuickChart instance URL |
746
+ </details>
1759
747
 
1760
- ## โš™๏ธ Default Behaviors
748
+ <details>
749
+ <summary>notification.config โ€” which notification categories the UI shows</summary>
1761
750
 
1762
- 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:
751
+ Defaults (override per strategy):
1763
752
 
1764
- | Component | Default | Warning |
1765
- |--------------|--------------------------------|---------------------------------------------------------------------------|
1766
- | **Exchange** | CCXT Binance (`default_exchange`) | `Warning: The default exchange schema is set to CCXT Binance...` |
1767
- | **Frame** | February 2024 (`default_frame`) | `Warning: The default frame schema is set to February 2024...` |
1768
- | **Symbol** | `BTCUSDT` | โ€” |
1769
- | **Cache intervals** | `1m, 15m, 30m, 4h` | Used if `--cacheInterval` not provided; skip entirely with `--noCache` |
753
+ | Key | Default | Description |
754
+ |-----|---------|-------------|
755
+ | `signal` | `true` | Signal lifecycle: opened, scheduled, closed, cancelled |
756
+ | `risk` | `true` | Risk manager rejections |
757
+ | `info` | `true` | Informational messages on an active signal |
758
+ | `breakeven` | `true` | Breakeven level reached |
759
+ | `common_error` | `true` | Non-fatal runtime errors |
760
+ | `critical_error` | `true` | Fatal errors that terminate the session |
761
+ | `validation_error` | `true` | Config / input validation errors |
762
+ | `strategy_commit` | `true` | All committed actions (partial close, DCA, trailing, โ€ฆ) |
763
+ | `partial_loss` | `false` | Partial loss level reached (before commit) |
764
+ | `partial_profit` | `false` | Partial profit level reached (before commit) |
765
+ | `signal_sync` | `false` | Live order fill / exit confirmations from exchange sync |
1770
766
 
1771
- > **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.
767
+ ```js
768
+ // config/notification.config.ts
769
+ export default { signal: true, risk: true, info: true, breakeven: true, common_error: true, critical_error: true, validation_error: true, strategy_commit: true, partial_loss: false, partial_profit: false, signal_sync: false };
770
+ ```
1772
771
 
1773
- ## ๐Ÿ”ง Programmatic API
772
+ </details>
1774
773
 
1775
- 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.
774
+ ### `telegram.config` โ€” programmatic message rendering
1776
775
 
1777
- ### `run(mode, args)`
776
+ <details>
777
+ <summary>Override Mustache rendering with get*Markdown methods</summary>
1778
778
 
1779
- ```typescript
1780
- import { run } from '@backtest-kit/cli';
779
+ By default messages render from Mustache templates (`template/*.mustache`). Export an object with any subset of `get*Markdown` methods (each gets the event payload, returns `Promise<string>`); unimplemented ones fall back to the template.
1781
780
 
1782
- await run(mode, args);
781
+ ```ts
782
+ // config/telegram.config.ts
783
+ import { IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract } from "backtest-kit";
784
+ export default {
785
+ async getOpenedMarkdown(e: IStrategyTickResultOpened) { return `**Opened** ${e.symbol} at ${e.priceOpen}`; },
786
+ async getClosedMarkdown(e: IStrategyTickResultClosed) { return `**Closed** ${e.symbol} at ${e.priceClosed}`; },
787
+ async getRiskMarkdown(e: RiskContract) { return `**Risk rejected** ${e.symbol}`; },
788
+ };
1783
789
  ```
1784
790
 
1785
- | Parameter | Description |
1786
- |-----------|-------------|
1787
- | `mode` | `"backtest" \| "paper" \| "live"` โ€” Execution mode |
1788
- | `args` | Mode-specific options (all optional โ€” same defaults as CLI) |
791
+ Methods โ†’ event types: `getOpenedMarkdown`/`getClosedMarkdown`/`getScheduledMarkdown`/`getCancelledMarkdown` (`IStrategyTickResult*`), `getRiskMarkdown` (`RiskContract`), `getPartialProfitMarkdown`/`getPartialLossMarkdown`/`getBreakevenMarkdown`/`getTrailingTakeMarkdown`/`getTrailingStopMarkdown`/`getAverageBuyMarkdown` (the matching `*Commit`), `getSignalOpenMarkdown`/`getSignalCloseMarkdown` (`SignalOpen/CloseContract`), `getCancelScheduledMarkdown`/`getClosePendingMarkdown` (`*Commit`), `getSignalInfoMarkdown` (`SignalInfoContract`).
792
+
793
+ </details>
1789
794
 
1790
- `run()` can be called **only once per process**. A second call throws `"Should be called only once"`.
795
+ ---
1791
796
 
1792
- ### Payload fields
797
+ ## ๐Ÿ”” Integrations
798
+
799
+ ### Web dashboard (`--ui`)
1793
800
 
1794
- **Backtest** (`mode: "backtest"`):
801
+ Starts the `@backtest-kit/ui` server at `http://localhost:60050` (host/port via `CC_WWWROOT_HOST` / `CC_WWWROOT_PORT`). Restrict the symbol list with `symbol.config` and notification categories with `notification.config` (above).
1795
802
 
1796
- | Field | Type | Description |
1797
- |-------|------|-------------|
1798
- | `entryPoint` | `string` | Path to strategy entry point file |
1799
- | `symbol` | `string` | Trading pair (default: `"BTCUSDT"`) |
1800
- | `strategy` | `string` | Strategy name (default: first registered) |
1801
- | `exchange` | `string` | Exchange name (default: first registered) |
1802
- | `frame` | `string` | Frame name (default: first registered) |
1803
- | `cacheInterval` | `CandleInterval[]` | Intervals to pre-cache (default: `["1m","15m","30m","1h","4h"]`) |
1804
- | `noCache` | `boolean` | Skip candle cache warming (default: `false`) |
1805
- | `noFlush` | `boolean` | Skip removing report/log/markdown/agent folders before the run (default: `false`) |
1806
- | `verbose` | `boolean` | Log each candle fetch (default: `false`) |
803
+ ### Telegram (`--telegram`)
1807
804
 
1808
- **Paper** and **Live** (`mode: "paper"` / `mode: "live"`):
805
+ Sends formatted HTML messages with 1m / 15m / 1h price charts for every position event โ€” opened, closed, scheduled, cancelled, risk rejection, partial profit/loss, trailing stop/take, breakeven. Requires `CC_TELEGRAM_TOKEN` and `CC_TELEGRAM_CHANNEL`. Customize per-event rendering with `telegram.config` (above).
1809
806
 
1810
- | Field | Type | Description |
1811
- |-------|------|-------------|
1812
- | `entryPoint` | `string` | Path to strategy entry point file |
1813
- | `symbol` | `string` | Trading pair (default: `"BTCUSDT"`) |
1814
- | `strategy` | `string` | Strategy name (default: first registered) |
1815
- | `exchange` | `string` | Exchange name (default: first registered) |
1816
- | `verbose` | `boolean` | Log each candle fetch (default: `false`) |
807
+ ---
1817
808
 
1818
- ### Examples
809
+ ## ๐Ÿงช Programmatic API โ€” `run(mode, args)`
1819
810
 
1820
- **Backtest:**
811
+ Use the CLI as a library โ€” call `run()` from your own script, no child process or flag parsing.
1821
812
 
1822
813
  ```typescript
1823
814
  import { run } from '@backtest-kit/cli';
1824
815
 
1825
- await run('backtest', {
1826
- entryPoint: './src/index.mjs',
1827
- symbol: 'ETHUSDT',
1828
- frame: 'feb-2024',
1829
- cacheInterval: ['1m', '15m', '1h'],
1830
- verbose: true,
1831
- });
816
+ await run('backtest', { entryPoint: './src/index.mjs', symbol: 'ETHUSDT', frame: 'feb-2024', cacheInterval: ['1m','15m','1h'], verbose: true });
817
+ await run('paper', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT' });
818
+ await run('live', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT', verbose: true });
1832
819
  ```
1833
820
 
1834
- **Paper trading:**
821
+ <details>
822
+ <summary>Payload fields (call once per process)</summary>
1835
823
 
1836
- ```typescript
1837
- import { run } from '@backtest-kit/cli';
824
+ `run()` can be called **only once per process** โ€” a second call throws `"Should be called only once"`. `mode`: `"backtest" | "paper" | "live"`.
1838
825
 
1839
- await run('paper', {
1840
- entryPoint: './src/index.mjs',
1841
- symbol: 'BTCUSDT',
1842
- });
1843
- ```
826
+ **Backtest:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `frame` (first registered), `cacheInterval` (`["1m","15m","30m","1h","4h"]`), `noCache` (`false`), `noFlush` (`false`), `verbose` (`false`).
1844
827
 
1845
- **Live trading:**
828
+ **Paper / Live:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `verbose` (`false`).
1846
829
 
1847
- ```typescript
1848
- import { run } from '@backtest-kit/cli';
830
+ </details>
1849
831
 
1850
- await run('live', {
1851
- entryPoint: './src/index.mjs',
1852
- symbol: 'BTCUSDT',
1853
- verbose: true,
1854
- });
832
+ ---
833
+
834
+ ## ๐ŸŒ Environment variables
835
+
836
+ ```env
837
+ CC_TELEGRAM_TOKEN=your_bot_token_here # required for --telegram (from @BotFather)
838
+ CC_TELEGRAM_CHANNEL=-100123456789 # required for --telegram (channel/chat ID)
839
+ CC_WWWROOT_HOST=0.0.0.0 # UI bind address (default 0.0.0.0)
840
+ CC_WWWROOT_PORT=60050 # UI port (default 60050)
841
+ CC_QUICKCHART_HOST= # optional self-hosted QuickChart URL
1855
842
  ```
1856
843
 
1857
- ## ๐Ÿ’ก Why Use @backtest-kit/cli?
844
+ <details>
845
+ <summary>Default behaviors (when a schema isn't registered)</summary>
1858
846
 
1859
- Instead of writing infrastructure code for every project:
847
+ | Component | Default | Note |
848
+ |-----------|---------|------|
849
+ | **Exchange** | CCXT Binance (`default_exchange`) | warns; **does not support order book in backtest** โ€” register a custom exchange with snapshot storage if your strategy calls `getOrderBook()` in backtest |
850
+ | **Frame** | February 2024 (`default_frame`) | warns |
851
+ | **Symbol** | `BTCUSDT` | โ€” |
852
+ | **Cache intervals** | `1m, 15m, 30m, 4h` | used if `--cacheInterval` not given; skip with `--noCache` |
1860
853
 
1861
- **โŒ Without @backtest-kit/cli (manual setup)**
854
+ </details>
1862
855
 
1863
- ```typescript
1864
- // index.ts
1865
- import { setLogger, setConfig, Storage, Notification, Report, Markdown } from 'backtest-kit';
1866
- import { serve } from '@backtest-kit/ui';
1867
-
1868
- setLogger({ log: console.log, ... });
1869
- Storage.enable();
1870
- Notification.enable();
1871
- Report.enable();
1872
- Markdown.disable();
1873
-
1874
- // ... parse CLI args manually
1875
- // ... register exchange schema
1876
- // ... warm candle cache
1877
- // ... set up Telegram bot
1878
- // ... handle SIGINT gracefully
1879
- // ... load and run backtest
1880
- ```
856
+ ---
857
+
858
+ ## ๐Ÿ’ก Why @backtest-kit/cli
1881
859
 
1882
- **โœ… With @backtest-kit/cli (one script)**
860
+ Instead of writing infrastructure for every project โ€” manual logger/storage/notification setup, CLI arg parsing, exchange registration, cache warming, Telegram bot, SIGINT handling, run wiring โ€” the whole thing is one script:
1883
861
 
1884
862
  ```json
1885
863
  { "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
1886
864
  ```
1887
865
 
1888
- ```bash
1889
- npm run backtest
1890
- ```
1891
-
1892
- **Benefits:**
1893
-
1894
- - ๐Ÿš€ From zero to running backtest in seconds
1895
- - ๐Ÿ’พ Automatic candle cache warming with retry logic
1896
- - ๐ŸŒ Production-ready web dashboard out of the box
1897
- - ๐Ÿ“ฌ Telegram notifications with price charts โ€” no chart code needed
1898
- - ๐Ÿ›‘ Graceful shutdown on SIGINT โ€” no hanging processes
1899
- - ๐Ÿ”Œ Works with any `backtest-kit` strategy file as-is
1900
- - ๐Ÿงฉ Broker adapter hooks via side-effect module files โ€” no CLI internals to touch
866
+ Zero to running backtest in seconds ยท automatic candle-cache warming with retry ยท production web dashboard out of the box ยท Telegram alerts with charts (no chart code) ยท graceful SIGINT shutdown (no hanging processes) ยท pluggable logger โ€” override the built-in one with `setLogger()` from your strategy module ยท works with any `backtest-kit` strategy as-is ยท broker hooks via side-effect modules (no CLI internals to touch).
1901
867
 
1902
868
  ## ๐Ÿค Contribute
1903
869
 
1904
- Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
870
+ Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
1905
871
 
1906
872
  ## ๐Ÿ“œ License
1907
873