@backtest-kit/cli 9.0.0 → 9.1.0

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