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