@backtest-kit/cli 14.1.0 โ†’ 15.0.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
@@ -1,874 +1,874 @@
1
- <img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/square_compasses.svg" height="45px" align="right">
2
-
3
- # ๐Ÿ“Ÿ @backtest-kit/cli
4
-
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
-
7
- ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
8
-
9
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
10
- [![npm](https://img.shields.io/npm/v/@backtest-kit/cli.svg?style=flat-square)](https://npmjs.org/package/@backtest-kit/cli)
11
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
12
-
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
-
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
-
17
- ---
18
-
19
- ## ๐Ÿš€ Quick Start
20
-
21
- ```bash
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
25
- ```
26
-
27
- The whole onboarding is: write a strategy file that registers schemas via `backtest-kit`, point the CLI at it, choose a flag.
28
-
29
- ```bash
30
- npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts --symbol BTCUSDT
31
- ```
32
-
33
- <details>
34
- <summary>The strategy entry point (the CLI is only the runner)</summary>
35
-
36
- ```javascript
37
- // src/index.mjs โ€” registers schemas via backtest-kit; @backtest-kit/cli just runs it
38
- import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
39
- import ccxt from 'ccxt';
40
-
41
- addExchangeSchema({
42
- exchangeName: 'binance',
43
- getCandles: async (symbol, interval, since, limit) => {
44
- const exchange = new ccxt.binance();
45
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
46
- return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
47
- ({ timestamp, open, high, low, close, volume }));
48
- },
49
- formatPrice: (symbol, price) => price.toFixed(2),
50
- formatQuantity: (symbol, quantity) => quantity.toFixed(8),
51
- });
52
-
53
- addFrameSchema({ frameName: 'feb-2024', interval: '1m',
54
- startDate: new Date('2024-02-01'), endDate: new Date('2024-02-29') });
55
-
56
- addStrategySchema({ strategyName: 'my-strategy', interval: '15m',
57
- getSignal: async (symbol) => null }); // return a signal or null
58
- ```
59
-
60
- Wire it into `package.json` once and the positional path never changes:
61
-
62
- ```json
63
- {
64
- "scripts": {
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" }
70
- }
71
- ```
72
-
73
- ```bash
74
- npm run backtest -- --symbol BTCUSDT --ui --telegram # add integrations with flags
75
- ```
76
-
77
- </details>
78
-
79
- ---
80
-
81
- ## ๐Ÿค” Philosophy โ€” React vs Next.js, but for trading
82
-
83
- `@backtest-kit/cli` does **two things well with one tool**.
84
-
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.
86
-
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.
88
-
89
- ---
90
-
91
- ## ๐Ÿ—บ๏ธ Mode matrix
92
-
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.
94
-
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 |
111
-
112
- <details>
113
- <summary>Complete core flag reference</summary>
114
-
115
- | Flag | Type | Description |
116
- |------|------|-------------|
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"`) |
134
-
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.
136
-
137
- </details>
138
-
139
- ---
140
-
141
- ## ๐Ÿ“ˆ Trading modes
142
-
143
- The four modes that actually run strategies share one engine and one set of guarantees โ€” only the clock and the order routing differ.
144
-
145
- ### Backtest ยท Paper ยท Live
146
-
147
- <details>
148
- <summary>How each behaves</summary>
149
-
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.
151
-
152
- ```json
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" } }
154
- ```
155
-
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.
157
-
158
- ```json
159
- { "scripts": { "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs" } }
160
- ```
161
-
162
- **Live** (`--live`) โ€” deploys a real bot. Requires exchange API keys in `.env`. Combine with `--ui --telegram` for a monitored deployment.
163
-
164
- ```json
165
- { "scripts": { "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs" } }
166
- ```
167
-
168
- </details>
169
-
170
- ### Walker โ€” A/B strategy comparison
171
-
172
- Runs the same historical period against multiple strategy files and prints a ranked report. Use it to pick the best variant before deploying.
173
-
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
178
- ```
179
-
180
- <details>
181
- <summary>Walker flags, output modes & behavior</summary>
182
-
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.
184
-
185
- | Flag | Type | Description |
186
- |------|------|-------------|
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 |
196
-
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).
198
-
199
- </details>
200
-
201
- ### Main โ€” custom entry point, no trading harness
202
-
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.
204
-
205
- <details>
206
- <summary>Main behavior & flags</summary>
207
-
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`.
209
-
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.
211
-
212
- | Flag | Type | Description |
213
- |------|------|-------------|
214
- | `--main` | boolean | Enable Main mode |
215
- | `--noFlush` | boolean | Skip removing output folders before the run |
216
-
217
- ```json
218
- { "scripts": { "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts" } }
219
- ```
220
-
221
- </details>
222
-
223
- ### Parallel multi-symbol (`--entry`)
224
-
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.
226
-
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()`.
228
-
229
- <details>
230
- <summary>Example โ€” backtest one strategy across five symbols</summary>
231
-
232
- ```javascript
233
- // src/multi-symbol.mjs
234
- import { addExchangeSchema, addFrameSchema, addStrategySchema, Backtest, warmCandles } from "backtest-kit";
235
- import ccxt from "ccxt";
236
-
237
- addExchangeSchema({ exchangeName: "binance",
238
- getCandles: async (symbol, interval, since, limit) => {
239
- const exchange = new ccxt.binance();
240
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
241
- return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
242
- ({ timestamp, open, high, low, close, volume }));
243
- },
244
- formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8) });
245
-
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 });
249
-
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" });
254
- }
255
- ```
256
-
257
- ```bash
258
- npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
259
- ```
260
-
261
- The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter).
262
-
263
- </details>
264
-
265
- ---
266
-
267
- ## ๐Ÿ› ๏ธ Tooling modes
268
-
269
- Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
270
-
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.
272
-
273
- <details>
274
- <summary>The shared <code>&lt;mode&gt;.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
275
-
276
- ```typescript
277
- // modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
278
- import { addExchangeSchema } from "backtest-kit";
279
- import ccxt from "ccxt";
280
-
281
- addExchangeSchema({
282
- exchangeName: "my-exchange",
283
- getCandles: async (symbol, interval, since, limit) => {
284
- const exchange = new ccxt.bybit({
285
- apiKey: process.env.BYBIT_API_KEY, secret: process.env.BYBIT_API_SECRET, enableRateLimit: true,
286
- });
287
- const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
288
- return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
289
- ({ timestamp, open, high, low, close, volume }));
290
- },
291
- formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8),
292
- });
293
- ```
294
-
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>
302
-
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.
306
-
307
- ```bash
308
- npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180 --when "2025-09-24T12:00:00.000Z"
309
- ```
310
-
311
- <details>
312
- <summary>Pine flags, requirements & output</summary>
313
-
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` |
326
-
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:
328
-
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)
334
- ```
335
-
336
- Output (stdout, or `--markdown`/`--json`/`--jsonl` to `<pine-dir>/dump/`):
337
-
338
- ```
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 |
344
- ```
345
-
346
- </details>
347
-
348
- ### ๐ŸŽจ Editor โ€” visual Pine Script editor (`--editor`)
349
-
350
- ![pine](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot32.png)
351
-
352
- A browser-based Pine Script editor (powered by `@backtest-kit/ui`) with a live chart that updates on **โ–ถ Run**.
353
-
354
- ```bash
355
- npx @backtest-kit/cli --editor # โ†’ http://localhost:60050?pine=1 opens automatically
356
- ```
357
-
358
- <details>
359
- <summary>Editor behavior & exchange</summary>
360
-
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`).
362
-
363
- </details>
364
-
365
- ### ๐Ÿ’พ Candle Dump (`--dump`)
366
-
367
- Fetch raw OHLCV candles from any registered exchange and save them โ€” no strategy file required. `dump/` is created in the current working directory.
368
-
369
- ```bash
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
372
- ```
373
-
374
- <details>
375
- <summary>Dump flags</summary>
376
-
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` |
388
-
389
- Exchange via `dump.module` (see convention above), searched in the current working directory. No flag โ†’ print to stdout.
390
-
391
- </details>
392
-
393
- ### ๐Ÿž PnL Debug (`--pnldebug`)
394
-
395
- Simulate a hypothetical position minute by minute โ€” running PnL, peak profit, max drawdown per candle โ€” without placing trades or loading a strategy.
396
-
397
- ```bash
398
- npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
399
- ```
400
-
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).
417
-
418
- ```
419
- Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
420
- min | timestamp | close | pnl% | peak% | drawdown%
421
- 1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
422
- 2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
423
- 120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
424
- ```
425
-
426
- </details>
427
-
428
- ### ๐Ÿ› Broker Debug (`--brokerdebug`)
429
-
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.
431
-
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>
438
-
439
- | Flag | Type | Description |
440
- |------|------|-------------|
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) |
445
-
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`.
447
-
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.
449
-
450
- </details>
451
-
452
- ### ๐Ÿ—‘๏ธ Flush (`--flush`)
453
-
454
- Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
455
-
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
- ```
459
-
460
- <details>
461
- <summary>What flush removes</summary>
462
-
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.
464
-
465
- </details>
466
-
467
- ---
468
-
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.
474
-
475
- ```bash
476
- npx @backtest-kit/cli --init --output my-trading-bot # โ†’ ./my-trading-bot/
477
- ```
478
-
479
- <details>
480
- <summary>Generated structure & automatic docs fetch</summary>
481
-
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
- ```
493
-
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`.
495
-
496
- </details>
497
-
498
- ### Docker (`--docker`)
499
-
500
- Scaffolds a self-contained Docker workspace with `docker-compose.yaml` and a strategy entry point, for zero-downtime live trading.
501
-
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
- ```
506
-
507
- <details>
508
- <summary>Two launch modes & environment variables</summary>
509
-
510
- **1. `command:` in `docker-compose.yaml`** โ€” pin mode and flags directly; the entrypoint forwards all args to the CLI unchanged:
511
-
512
- ```yaml
513
- command: [--live, --symbol, TRXUSDT, --strategy, feb_2026_strategy, --exchange, ccxt-exchange, ./content/feb_2026/feb_2026.strategy.ts, --ui]
514
- ```
515
-
516
- **2. Inline env vars** โ€” `MODE` + `STRATEGY_FILE` on the command line, no file edits:
517
-
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/ โ€ฆ
555
- ```
556
-
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:
585
-
586
- ```json
587
- {
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"]
593
- }
594
- ```
595
-
596
- </details>
597
-
598
- ### Entry point formats
599
-
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.
601
-
602
- ---
603
-
604
- ## ๐Ÿ”Œ Broker adapter โ€” transactional live orders
605
-
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.
607
-
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 |
616
-
617
- > Resolved relative to `cwd` (the strategy dir); `.mjs`/`.cjs`/`.ts` tried automatically. A missing module is a soft warning, not an error.
618
-
619
- <details>
620
- <summary>Adapter example & hook reference</summary>
621
-
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();
636
- ```
637
-
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 |
648
-
649
- All methods are optional; unimplemented hooks are silently skipped. TypeScript: implement `Partial<IBroker>` with typed payloads (`BrokerSignalOpenPayload`, etc.).
650
-
651
- </details>
652
-
653
- ---
654
-
655
- ## โš™๏ธ Configuration files (`config/*`)
656
-
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`.
658
-
659
- ### `setup.config` โ€” persistence & one-time init
660
-
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.
662
-
663
- <details>
664
- <summary>MongoDB + Redis via @backtest-kit/mongo</summary>
665
-
666
- `setup()` registers all 15 persistence adapters in one call, reading connection params from env (or passed explicitly):
667
-
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 })
672
- ```
673
-
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
- ```
679
-
680
- No strategy-code changes โ€” adapters are wired transparently before the first persistence call.
681
-
682
- </details>
683
-
684
- ### `loader.config` โ€” async startup gate
685
-
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.
687
-
688
- <details>
689
- <summary>When to use it, export styles & examples</summary>
690
-
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.
692
-
693
- Exactly one export style โ€” **never both** (if both present, `default` wins):
694
-
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 () => { /* โ€ฆ */ };
700
- ```
701
-
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`).
703
-
704
- </details>
705
-
706
- ### `alias.config` โ€” override any module import
707
-
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.
709
-
710
- <details>
711
- <summary>Formats & async factory</summary>
712
-
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") };
718
- ```
719
-
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:
721
-
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") });
725
- ```
726
-
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).
728
-
729
- </details>
730
-
731
- ### `symbol.config` & `notification.config` โ€” UI dashboard
732
-
733
- <details>
734
- <summary>symbol.config โ€” restrict/reorder the UI symbol list</summary>
735
-
736
- By default the UI shows all exchange symbols. Override with a `config/symbol.config` (resolution: strategy dir โ†’ project root โ†’ package default):
737
-
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
- ];
744
- ```
745
-
746
- </details>
747
-
748
- <details>
749
- <summary>notification.config โ€” which notification categories the UI shows</summary>
750
-
751
- Defaults (override per strategy):
752
-
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 |
766
-
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
- ```
771
-
772
- </details>
773
-
774
- ### `telegram.config` โ€” programmatic message rendering
775
-
776
- <details>
777
- <summary>Override Mustache rendering with get*Markdown methods</summary>
778
-
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.
780
-
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
- };
789
- ```
790
-
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>
794
-
795
- ---
796
-
797
- ## ๐Ÿ”” Integrations
798
-
799
- ### Web dashboard (`--ui`)
800
-
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).
802
-
803
- ### Telegram (`--telegram`)
804
-
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).
806
-
807
- ---
808
-
809
- ## ๐Ÿงช Programmatic API โ€” `run(mode, args)`
810
-
811
- Use the CLI as a library โ€” call `run()` from your own script, no child process or flag parsing.
812
-
813
- ```typescript
814
- import { run } from '@backtest-kit/cli';
815
-
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 });
819
- ```
820
-
821
- <details>
822
- <summary>Payload fields (call once per process)</summary>
823
-
824
- `run()` can be called **only once per process** โ€” a second call throws `"Should be called only once"`. `mode`: `"backtest" | "paper" | "live"`.
825
-
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`).
827
-
828
- **Paper / Live:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `verbose` (`false`).
829
-
830
- </details>
831
-
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
842
- ```
843
-
844
- <details>
845
- <summary>Default behaviors (when a schema isn't registered)</summary>
846
-
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` |
853
-
854
- </details>
855
-
856
- ---
857
-
858
- ## ๐Ÿ’ก Why @backtest-kit/cli
859
-
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:
861
-
862
- ```json
863
- { "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
864
- ```
865
-
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).
867
-
868
- ## ๐Ÿค Contribute
869
-
870
- Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
871
-
872
- ## ๐Ÿ“œ License
873
-
874
- MIT ยฉ [tripolskypetr](https://github.com/tripolskypetr)
1
+ <img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/square_compasses.svg" height="45px" align="right">
2
+
3
+ # ๐Ÿ“Ÿ @backtest-kit/cli
4
+
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
+
7
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
8
+
9
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
10
+ [![npm](https://img.shields.io/npm/v/@backtest-kit/cli.svg?style=flat-square)](https://npmjs.org/package/@backtest-kit/cli)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
12
+
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
+
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
+
17
+ ---
18
+
19
+ ## ๐Ÿš€ Quick Start
20
+
21
+ ```bash
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
25
+ ```
26
+
27
+ The whole onboarding is: write a strategy file that registers schemas via `backtest-kit`, point the CLI at it, choose a flag.
28
+
29
+ ```bash
30
+ npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts --symbol BTCUSDT
31
+ ```
32
+
33
+ <details>
34
+ <summary>The strategy entry point (the CLI is only the runner)</summary>
35
+
36
+ ```javascript
37
+ // src/index.mjs โ€” registers schemas via backtest-kit; @backtest-kit/cli just runs it
38
+ import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
39
+ import ccxt from 'ccxt';
40
+
41
+ addExchangeSchema({
42
+ exchangeName: 'binance',
43
+ getCandles: async (symbol, interval, since, limit) => {
44
+ const exchange = new ccxt.binance();
45
+ const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
46
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
47
+ ({ timestamp, open, high, low, close, volume }));
48
+ },
49
+ formatPrice: (symbol, price) => price.toFixed(2),
50
+ formatQuantity: (symbol, quantity) => quantity.toFixed(8),
51
+ });
52
+
53
+ addFrameSchema({ frameName: 'feb-2024', interval: '1m',
54
+ startDate: new Date('2024-02-01'), endDate: new Date('2024-02-29') });
55
+
56
+ addStrategySchema({ strategyName: 'my-strategy', interval: '15m',
57
+ getSignal: async (symbol) => null }); // return a signal or null
58
+ ```
59
+
60
+ Wire it into `package.json` once and the positional path never changes:
61
+
62
+ ```json
63
+ {
64
+ "scripts": {
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" }
70
+ }
71
+ ```
72
+
73
+ ```bash
74
+ npm run backtest -- --symbol BTCUSDT --ui --telegram # add integrations with flags
75
+ ```
76
+
77
+ </details>
78
+
79
+ ---
80
+
81
+ ## ๐Ÿค” Philosophy โ€” React vs Next.js, but for trading
82
+
83
+ `@backtest-kit/cli` does **two things well with one tool**.
84
+
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.
86
+
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.
88
+
89
+ ---
90
+
91
+ ## ๐Ÿ—บ๏ธ Mode matrix
92
+
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.
94
+
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 |
111
+
112
+ <details>
113
+ <summary>Complete core flag reference</summary>
114
+
115
+ | Flag | Type | Description |
116
+ |------|------|-------------|
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"`) |
134
+
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.
136
+
137
+ </details>
138
+
139
+ ---
140
+
141
+ ## ๐Ÿ“ˆ Trading modes
142
+
143
+ The four modes that actually run strategies share one engine and one set of guarantees โ€” only the clock and the order routing differ.
144
+
145
+ ### Backtest ยท Paper ยท Live
146
+
147
+ <details>
148
+ <summary>How each behaves</summary>
149
+
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.
151
+
152
+ ```json
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" } }
154
+ ```
155
+
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.
157
+
158
+ ```json
159
+ { "scripts": { "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs" } }
160
+ ```
161
+
162
+ **Live** (`--live`) โ€” deploys a real bot. Requires exchange API keys in `.env`. Combine with `--ui --telegram` for a monitored deployment.
163
+
164
+ ```json
165
+ { "scripts": { "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs" } }
166
+ ```
167
+
168
+ </details>
169
+
170
+ ### Walker โ€” A/B strategy comparison
171
+
172
+ Runs the same historical period against multiple strategy files and prints a ranked report. Use it to pick the best variant before deploying.
173
+
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
178
+ ```
179
+
180
+ <details>
181
+ <summary>Walker flags, output modes & behavior</summary>
182
+
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.
184
+
185
+ | Flag | Type | Description |
186
+ |------|------|-------------|
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 |
196
+
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).
198
+
199
+ </details>
200
+
201
+ ### Main โ€” custom entry point, no trading harness
202
+
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.
204
+
205
+ <details>
206
+ <summary>Main behavior & flags</summary>
207
+
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`.
209
+
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.
211
+
212
+ | Flag | Type | Description |
213
+ |------|------|-------------|
214
+ | `--main` | boolean | Enable Main mode |
215
+ | `--noFlush` | boolean | Skip removing output folders before the run |
216
+
217
+ ```json
218
+ { "scripts": { "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts" } }
219
+ ```
220
+
221
+ </details>
222
+
223
+ ### Parallel multi-symbol (`--entry`)
224
+
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.
226
+
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()`.
228
+
229
+ <details>
230
+ <summary>Example โ€” backtest one strategy across five symbols</summary>
231
+
232
+ ```javascript
233
+ // src/multi-symbol.mjs
234
+ import { addExchangeSchema, addFrameSchema, addStrategySchema, Backtest, warmCandles } from "backtest-kit";
235
+ import ccxt from "ccxt";
236
+
237
+ addExchangeSchema({ exchangeName: "binance",
238
+ getCandles: async (symbol, interval, since, limit) => {
239
+ const exchange = new ccxt.binance();
240
+ const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
241
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
242
+ ({ timestamp, open, high, low, close, volume }));
243
+ },
244
+ formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8) });
245
+
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 });
249
+
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" });
254
+ }
255
+ ```
256
+
257
+ ```bash
258
+ npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
259
+ ```
260
+
261
+ The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter).
262
+
263
+ </details>
264
+
265
+ ---
266
+
267
+ ## ๐Ÿ› ๏ธ Tooling modes
268
+
269
+ Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
270
+
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.
272
+
273
+ <details>
274
+ <summary>The shared <code>&lt;mode&gt;.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
275
+
276
+ ```typescript
277
+ // modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
278
+ import { addExchangeSchema } from "backtest-kit";
279
+ import ccxt from "ccxt";
280
+
281
+ addExchangeSchema({
282
+ exchangeName: "my-exchange",
283
+ getCandles: async (symbol, interval, since, limit) => {
284
+ const exchange = new ccxt.bybit({
285
+ apiKey: process.env.BYBIT_API_KEY, secret: process.env.BYBIT_API_SECRET, enableRateLimit: true,
286
+ });
287
+ const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
288
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
289
+ ({ timestamp, open, high, low, close, volume }));
290
+ },
291
+ formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8),
292
+ });
293
+ ```
294
+
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>
302
+
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.
306
+
307
+ ```bash
308
+ npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180 --when "2025-09-24T12:00:00.000Z"
309
+ ```
310
+
311
+ <details>
312
+ <summary>Pine flags, requirements & output</summary>
313
+
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` |
326
+
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:
328
+
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)
334
+ ```
335
+
336
+ Output (stdout, or `--markdown`/`--json`/`--jsonl` to `<pine-dir>/dump/`):
337
+
338
+ ```
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 |
344
+ ```
345
+
346
+ </details>
347
+
348
+ ### ๐ŸŽจ Editor โ€” visual Pine Script editor (`--editor`)
349
+
350
+ ![pine](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot32.png)
351
+
352
+ A browser-based Pine Script editor (powered by `@backtest-kit/ui`) with a live chart that updates on **โ–ถ Run**.
353
+
354
+ ```bash
355
+ npx @backtest-kit/cli --editor # โ†’ http://localhost:60050?pine=1 opens automatically
356
+ ```
357
+
358
+ <details>
359
+ <summary>Editor behavior & exchange</summary>
360
+
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`).
362
+
363
+ </details>
364
+
365
+ ### ๐Ÿ’พ Candle Dump (`--dump`)
366
+
367
+ Fetch raw OHLCV candles from any registered exchange and save them โ€” no strategy file required. `dump/` is created in the current working directory.
368
+
369
+ ```bash
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
372
+ ```
373
+
374
+ <details>
375
+ <summary>Dump flags</summary>
376
+
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` |
388
+
389
+ Exchange via `dump.module` (see convention above), searched in the current working directory. No flag โ†’ print to stdout.
390
+
391
+ </details>
392
+
393
+ ### ๐Ÿž PnL Debug (`--pnldebug`)
394
+
395
+ Simulate a hypothetical position minute by minute โ€” running PnL, peak profit, max drawdown per candle โ€” without placing trades or loading a strategy.
396
+
397
+ ```bash
398
+ npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
399
+ ```
400
+
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).
417
+
418
+ ```
419
+ Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
420
+ min | timestamp | close | pnl% | peak% | drawdown%
421
+ 1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
422
+ 2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
423
+ 120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
424
+ ```
425
+
426
+ </details>
427
+
428
+ ### ๐Ÿ› Broker Debug (`--brokerdebug`)
429
+
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.
431
+
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>
438
+
439
+ | Flag | Type | Description |
440
+ |------|------|-------------|
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) |
445
+
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`.
447
+
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.
449
+
450
+ </details>
451
+
452
+ ### ๐Ÿ—‘๏ธ Flush (`--flush`)
453
+
454
+ Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
455
+
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
+ ```
459
+
460
+ <details>
461
+ <summary>What flush removes</summary>
462
+
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.
464
+
465
+ </details>
466
+
467
+ ---
468
+
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.
474
+
475
+ ```bash
476
+ npx @backtest-kit/cli --init --output my-trading-bot # โ†’ ./my-trading-bot/
477
+ ```
478
+
479
+ <details>
480
+ <summary>Generated structure & automatic docs fetch</summary>
481
+
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
+ ```
493
+
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`.
495
+
496
+ </details>
497
+
498
+ ### Docker (`--docker`)
499
+
500
+ Scaffolds a self-contained Docker workspace with `docker-compose.yaml` and a strategy entry point, for zero-downtime live trading.
501
+
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
+ ```
506
+
507
+ <details>
508
+ <summary>Two launch modes & environment variables</summary>
509
+
510
+ **1. `command:` in `docker-compose.yaml`** โ€” pin mode and flags directly; the entrypoint forwards all args to the CLI unchanged:
511
+
512
+ ```yaml
513
+ command: [--live, --symbol, TRXUSDT, --strategy, feb_2026_strategy, --exchange, ccxt-exchange, ./content/feb_2026/feb_2026.strategy.ts, --ui]
514
+ ```
515
+
516
+ **2. Inline env vars** โ€” `MODE` + `STRATEGY_FILE` on the command line, no file edits:
517
+
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/ โ€ฆ
555
+ ```
556
+
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:
585
+
586
+ ```json
587
+ {
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"]
593
+ }
594
+ ```
595
+
596
+ </details>
597
+
598
+ ### Entry point formats
599
+
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.
601
+
602
+ ---
603
+
604
+ ## ๐Ÿ”Œ Broker adapter โ€” transactional live orders
605
+
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.
607
+
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 |
616
+
617
+ > Resolved relative to `cwd` (the strategy dir); `.mjs`/`.cjs`/`.ts` tried automatically. A missing module is a soft warning, not an error.
618
+
619
+ <details>
620
+ <summary>Adapter example & hook reference</summary>
621
+
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();
636
+ ```
637
+
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 |
648
+
649
+ All methods are optional; unimplemented hooks are silently skipped. TypeScript: implement `Partial<IBroker>` with typed payloads (`BrokerSignalOpenPayload`, etc.).
650
+
651
+ </details>
652
+
653
+ ---
654
+
655
+ ## โš™๏ธ Configuration files (`config/*`)
656
+
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`.
658
+
659
+ ### `setup.config` โ€” persistence & one-time init
660
+
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.
662
+
663
+ <details>
664
+ <summary>MongoDB + Redis via @backtest-kit/mongo</summary>
665
+
666
+ `setup()` registers all 15 persistence adapters in one call, reading connection params from env (or passed explicitly):
667
+
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 })
672
+ ```
673
+
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
+ ```
679
+
680
+ No strategy-code changes โ€” adapters are wired transparently before the first persistence call.
681
+
682
+ </details>
683
+
684
+ ### `loader.config` โ€” async startup gate
685
+
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.
687
+
688
+ <details>
689
+ <summary>When to use it, export styles & examples</summary>
690
+
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.
692
+
693
+ Exactly one export style โ€” **never both** (if both present, `default` wins):
694
+
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 () => { /* โ€ฆ */ };
700
+ ```
701
+
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`).
703
+
704
+ </details>
705
+
706
+ ### `alias.config` โ€” override any module import
707
+
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.
709
+
710
+ <details>
711
+ <summary>Formats & async factory</summary>
712
+
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") };
718
+ ```
719
+
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:
721
+
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") });
725
+ ```
726
+
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).
728
+
729
+ </details>
730
+
731
+ ### `symbol.config` & `notification.config` โ€” UI dashboard
732
+
733
+ <details>
734
+ <summary>symbol.config โ€” restrict/reorder the UI symbol list</summary>
735
+
736
+ By default the UI shows all exchange symbols. Override with a `config/symbol.config` (resolution: strategy dir โ†’ project root โ†’ package default):
737
+
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
+ ];
744
+ ```
745
+
746
+ </details>
747
+
748
+ <details>
749
+ <summary>notification.config โ€” which notification categories the UI shows</summary>
750
+
751
+ Defaults (override per strategy):
752
+
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 |
766
+
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
+ ```
771
+
772
+ </details>
773
+
774
+ ### `telegram.config` โ€” programmatic message rendering
775
+
776
+ <details>
777
+ <summary>Override Mustache rendering with get*Markdown methods</summary>
778
+
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.
780
+
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
+ };
789
+ ```
790
+
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>
794
+
795
+ ---
796
+
797
+ ## ๐Ÿ”” Integrations
798
+
799
+ ### Web dashboard (`--ui`)
800
+
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).
802
+
803
+ ### Telegram (`--telegram`)
804
+
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).
806
+
807
+ ---
808
+
809
+ ## ๐Ÿงช Programmatic API โ€” `run(mode, args)`
810
+
811
+ Use the CLI as a library โ€” call `run()` from your own script, no child process or flag parsing.
812
+
813
+ ```typescript
814
+ import { run } from '@backtest-kit/cli';
815
+
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 });
819
+ ```
820
+
821
+ <details>
822
+ <summary>Payload fields (call once per process)</summary>
823
+
824
+ `run()` can be called **only once per process** โ€” a second call throws `"Should be called only once"`. `mode`: `"backtest" | "paper" | "live"`.
825
+
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`).
827
+
828
+ **Paper / Live:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `verbose` (`false`).
829
+
830
+ </details>
831
+
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
842
+ ```
843
+
844
+ <details>
845
+ <summary>Default behaviors (when a schema isn't registered)</summary>
846
+
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` |
853
+
854
+ </details>
855
+
856
+ ---
857
+
858
+ ## ๐Ÿ’ก Why @backtest-kit/cli
859
+
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:
861
+
862
+ ```json
863
+ { "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
864
+ ```
865
+
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).
867
+
868
+ ## ๐Ÿค Contribute
869
+
870
+ Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
871
+
872
+ ## ๐Ÿ“œ License
873
+
874
+ MIT ยฉ [tripolskypetr](https://github.com/tripolskypetr)