@backtest-kit/cli 14.1.0 โ 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +874 -874
- package/build/index.cjs +387 -274
- package/build/index.mjs +381 -271
- package/config/notification.config.mjs +13 -13
- package/config/symbol.config.mjs +460 -460
- package/docker/.env.example +2 -2
- package/docker/content/feb_2026/feb_2026.strategy.ts +11 -11
- package/docker/content/feb_2026/modules/backtest.module.ts +83 -83
- package/docker/docker-compose.yaml +46 -46
- package/docker/package.json +39 -39
- package/docker/tsconfig.json +36 -36
- package/package.json +129 -129
- package/template/average-buy.mustache +22 -22
- package/template/breakeven.mustache +21 -21
- package/template/cancel-scheduled.mustache +14 -14
- package/template/cancelled.mustache +21 -21
- package/template/close-pending.mustache +16 -16
- package/template/closed.mustache +23 -23
- package/template/opened.mustache +22 -22
- package/template/partial-loss.mustache +22 -22
- package/template/partial-profit.mustache +22 -22
- package/template/project/config/symbol.config.ts +460 -460
- package/template/project/package.mustache +28 -28
- package/template/risk.mustache +19 -19
- package/template/scheduled.mustache +22 -22
- package/template/signal-close.mustache +22 -22
- package/template/signal-info.mustache +20 -20
- package/template/signal-open.mustache +22 -22
- package/template/source/CLAUDE.md +160 -160
- package/template/trailing-stop.mustache +21 -21
- package/template/trailing-take.mustache +21 -21
- package/types.d.ts +7 -7
package/README.md
CHANGED
|
@@ -1,874 +1,874 @@
|
|
|
1
|
-
<img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/square_compasses.svg" height="45px" align="right">
|
|
2
|
-
|
|
3
|
-
# ๐ @backtest-kit/cli
|
|
4
|
-
|
|
5
|
-
> Zero-boilerplate CLI for [backtest-kit](https://www.npmjs.com/package/backtest-kit). Point it at a strategy file, pick a mode, and it handles exchange connectivity, candle caching, the web dashboard, Telegram alerts, and graceful shutdown for you โ no setup code.
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
10
|
-
[](https://npmjs.org/package/@backtest-kit/cli)
|
|
11
|
-
[]()
|
|
12
|
-
|
|
13
|
-
๐ **[Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** ยท ๐ **[Reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example)** ยท ๐ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
|
|
14
|
-
|
|
15
|
-
> **New here?** The fastest real setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) โ a working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there, not from scratch.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## ๐ Quick Start
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
# Scaffold a project (boilerplate stays inside the CLI; docs auto-fetched)
|
|
23
|
-
npx @backtest-kit/cli --init --output backtest-kit-project
|
|
24
|
-
cd backtest-kit-project && npm install && npm start -- --help
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The whole onboarding is: write a strategy file that registers schemas via `backtest-kit`, point the CLI at it, choose a flag.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts --symbol BTCUSDT
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
<details>
|
|
34
|
-
<summary>The strategy entry point (the CLI is only the runner)</summary>
|
|
35
|
-
|
|
36
|
-
```javascript
|
|
37
|
-
// src/index.mjs โ registers schemas via backtest-kit; @backtest-kit/cli just runs it
|
|
38
|
-
import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
|
|
39
|
-
import ccxt from 'ccxt';
|
|
40
|
-
|
|
41
|
-
addExchangeSchema({
|
|
42
|
-
exchangeName: 'binance',
|
|
43
|
-
getCandles: async (symbol, interval, since, limit) => {
|
|
44
|
-
const exchange = new ccxt.binance();
|
|
45
|
-
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
46
|
-
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
47
|
-
({ timestamp, open, high, low, close, volume }));
|
|
48
|
-
},
|
|
49
|
-
formatPrice: (symbol, price) => price.toFixed(2),
|
|
50
|
-
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
addFrameSchema({ frameName: 'feb-2024', interval: '1m',
|
|
54
|
-
startDate: new Date('2024-02-01'), endDate: new Date('2024-02-29') });
|
|
55
|
-
|
|
56
|
-
addStrategySchema({ strategyName: 'my-strategy', interval: '15m',
|
|
57
|
-
getSignal: async (symbol) => null }); // return a signal or null
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Wire it into `package.json` once and the positional path never changes:
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
{
|
|
64
|
-
"scripts": {
|
|
65
|
-
"backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
|
|
66
|
-
"paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
|
|
67
|
-
"start": "npx @backtest-kit/cli --live ./src/index.mjs"
|
|
68
|
-
},
|
|
69
|
-
"dependencies": { "@backtest-kit/cli": "latest", "backtest-kit": "latest", "ccxt": "latest" }
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npm run backtest -- --symbol BTCUSDT --ui --telegram # add integrations with flags
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
</details>
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## ๐ค Philosophy โ React vs Next.js, but for trading
|
|
82
|
-
|
|
83
|
-
`@backtest-kit/cli` does **two things well with one tool**.
|
|
84
|
-
|
|
85
|
-
**1. The lightest runner for a solo quant on day one.** Write a strategy, point the CLI at it, you're trading. No DI container to learn, no scaffold to fight, no infra to copy-paste. The day you have an idea you can backtest it; the week you have an edge you can paper-trade it; the month you have a P&L you can run it live โ same CLI, different flag.
|
|
86
|
-
|
|
87
|
-
**2. A monorepo-grade runner for when the business takes off.** The moment you start making money is the worst moment to rewrite your stack. So the CLI is monorepo-ready from day one even if you don't use it that way at first: per-strategy `.env`, per-strategy broker modules, folder-based import aliases, isolated dump dirs. The tool you backtested your first idea with is the tool that runs a desk of strategies in production โ no rewrite, no language switch, only more files.
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## ๐บ๏ธ Mode matrix
|
|
92
|
-
|
|
93
|
-
Every invocation is **one mode** (a primary flag) + a positional strategy/entry path + optional modifiers. `--ui` and `--telegram` are integrations that attach to any trading mode.
|
|
94
|
-
|
|
95
|
-
| Mode | Flag | What it does |
|
|
96
|
-
|------|------|--------------|
|
|
97
|
-
| **Backtest** | `--backtest` | Run a strategy on historical candle data (uses a `FrameSchema`) |
|
|
98
|
-
| **Paper** | `--paper` | Live prices, no real orders โ identical code path to live |
|
|
99
|
-
| **Live** | `--live` | Real trades via exchange API |
|
|
100
|
-
| **Walker** | `--walker` | A/B-compare multiple strategies on the same history, ranked report |
|
|
101
|
-
| **Main** | `--main` | Run a custom entry point with the full environment prepared, **no** trading harness |
|
|
102
|
-
| **Pine** | `--pine` | Run a local `.pine` indicator against exchange data |
|
|
103
|
-
| **Editor** | `--editor` | Open the visual Pine Script editor in the browser |
|
|
104
|
-
| **Candle Dump** | `--dump` | Fetch & save raw OHLCV candles to a file |
|
|
105
|
-
| **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price & direction |
|
|
106
|
-
| **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live adapter |
|
|
107
|
-
| **Flush** | `--flush` | Delete report/log/markdown/agent folders from a strategy dump dir |
|
|
108
|
-
| **Init** | `--init` | Scaffold a new project |
|
|
109
|
-
| **Docker** | `--docker` | Scaffold a self-contained Docker workspace |
|
|
110
|
-
| *modifiers* | `--ui` ยท `--telegram` ยท `--entry` | Web dashboard ยท Telegram alerts ยท fan out one strategy across many symbols |
|
|
111
|
-
|
|
112
|
-
<details>
|
|
113
|
-
<summary>Complete core flag reference</summary>
|
|
114
|
-
|
|
115
|
-
| Flag | Type | Description |
|
|
116
|
-
|------|------|-------------|
|
|
117
|
-
| `--backtest` | boolean | Run historical backtest (default `false`) |
|
|
118
|
-
| `--walker` | boolean | Run Walker A/B comparison (default `false`) |
|
|
119
|
-
| `--paper` | boolean | Paper trading โ live prices, no orders (default `false`) |
|
|
120
|
-
| `--live` | boolean | Run live trading (default `false`) |
|
|
121
|
-
| `--main` | boolean | Custom entry point, no trading harness (default `false`) |
|
|
122
|
-
| `--ui` | boolean | Start web UI dashboard (default `false`) |
|
|
123
|
-
| `--telegram` | boolean | Enable Telegram notifications (default `false`) |
|
|
124
|
-
| `--verbose` | boolean | Log each candle fetch (default `false`) |
|
|
125
|
-
| `--noCache` | boolean | Skip candle cache warming before backtest (default `false`) |
|
|
126
|
-
| `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before run (default `false`) |
|
|
127
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
128
|
-
| `--strategy` | string | Strategy name (default: first registered) |
|
|
129
|
-
| `--exchange` | string | Exchange name (default: first registered) |
|
|
130
|
-
| `--frame` | string | Backtest frame name (default: first registered) |
|
|
131
|
-
| `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
|
|
132
|
-
| `--brokerdebug` | boolean | Fire a single broker commit against the live adapter (default `false`) |
|
|
133
|
-
| `--commit` | string | Commit type for `--brokerdebug` (default `"signal-open"`) |
|
|
134
|
-
|
|
135
|
-
**Positional argument (required):** path to your strategy entry point file โ set once in `package.json` scripts. Tool-specific flags (`--pine`, `--dump`, `--pnldebug`, `--docker`, โฆ) are documented in their sections below.
|
|
136
|
-
|
|
137
|
-
</details>
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## ๐ Trading modes
|
|
142
|
-
|
|
143
|
-
The four modes that actually run strategies share one engine and one set of guarantees โ only the clock and the order routing differ.
|
|
144
|
-
|
|
145
|
-
### Backtest ยท Paper ยท Live
|
|
146
|
-
|
|
147
|
-
<details>
|
|
148
|
-
<summary>How each behaves</summary>
|
|
149
|
-
|
|
150
|
-
**Backtest** (`--backtest`) โ runs against historical candles via a registered `FrameSchema`. Before running, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from the strategy's `dump/` dir, then warms the candle cache for every interval in `--cacheInterval`; subsequent runs reuse the cache with no API calls. `--noCache` skips warming, `--noFlush` keeps output folders.
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{ "scripts": { "backtest": "npx @backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs" } }
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
**Paper** (`--paper`) โ connects to the live exchange but places no real orders. **Identical code path to live** โ the safe way to validate a strategy.
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
{ "scripts": { "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs" } }
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**Live** (`--live`) โ deploys a real bot. Requires exchange API keys in `.env`. Combine with `--ui --telegram` for a monitored deployment.
|
|
163
|
-
|
|
164
|
-
```json
|
|
165
|
-
{ "scripts": { "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs" } }
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
</details>
|
|
169
|
-
|
|
170
|
-
### Walker โ A/B strategy comparison
|
|
171
|
-
|
|
172
|
-
Runs the same historical period against multiple strategy files and prints a ranked report. Use it to pick the best variant before deploying.
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
npx @backtest-kit/cli --walker --symbol BTCUSDT --noCache --markdown --output feb_2026_comparison \
|
|
176
|
-
./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
177
|
-
# โ ./dump/feb_2026_comparison.md
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
<details>
|
|
181
|
-
<summary>Walker flags, output modes & behavior</summary>
|
|
182
|
-
|
|
183
|
-
Each positional argument is a separate strategy entry point. Before loading them the CLI removes the `report`/`log`/`markdown`/`agent` folders from each entry point's `dump/` (skip with `--noFlush`). All files load **without changing `process.cwd()`** โ `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files. If no frame is registered, the CLI falls back to the last 31 days from `Date.now()` with a warning.
|
|
184
|
-
|
|
185
|
-
| Flag | Type | Description |
|
|
186
|
-
|------|------|-------------|
|
|
187
|
-
| `--walker` | boolean | Enable Walker comparison |
|
|
188
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
189
|
-
| `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
|
|
190
|
-
| `--noCache` | boolean | Skip candle cache warming |
|
|
191
|
-
| `--noFlush` | boolean | Skip removing output folders before the run |
|
|
192
|
-
| `--verbose` | boolean | Log each candle fetch and strategy progress |
|
|
193
|
-
| `--output` | string | Output file base name (default `walker_{SYMBOL}_{TIMESTAMP}`) |
|
|
194
|
-
| `--json` | boolean | Save `Walker.getData()` as JSON to `./dump/<output>.json` and exit |
|
|
195
|
-
| `--markdown` | boolean | Save `Walker.getReport()` as `./dump/<output>.md` and exit |
|
|
196
|
-
|
|
197
|
-
**Output:** no flag โ print Markdown report to stdout; `--json` / `--markdown` โ save and exit. **Module hook:** `./modules/walker.module` loads automatically before the comparison (`.ts`/`.mjs`/`.cjs` tried in order).
|
|
198
|
-
|
|
199
|
-
</details>
|
|
200
|
-
|
|
201
|
-
### Main โ custom entry point, no trading harness
|
|
202
|
-
|
|
203
|
-
Runs a single entry point with the full CLI environment prepared (`.env`, `config/setup.config`, `config/loader.config`, `./modules/main.module`, cwd changed to the entry-point folder, graceful shutdown wired) โ but **never** starts a trading harness. Use it to bootstrap the environment for a quick action, e.g. calling a 3rd-party API with automatic `.env` import.
|
|
204
|
-
|
|
205
|
-
<details>
|
|
206
|
-
<summary>Main behavior & flags</summary>
|
|
207
|
-
|
|
208
|
-
Unlike the trading modes it does not call `Backtest/Live/Walker.background`, pick a symbol, warm the cache, or resolve a strategy/exchange/frame โ the entry point decides what to run. Exactly **one** positional entry point is required (`Entry point is required` otherwise). `process.cwd()` changes to the entry-point directory and its local `.env` overrides the root `.env`.
|
|
209
|
-
|
|
210
|
-
Although the CLI starts nothing itself, any `Backtest`/`Live`/`Walker` run **your** entry point launches is still managed: the process exits once `listenDone*` reports completion, the first `Ctrl+C` stops every active run via `*.list()`/`*.stop()`, a second force-quits. `./modules/main.module` loads automatically before the entry point.
|
|
211
|
-
|
|
212
|
-
| Flag | Type | Description |
|
|
213
|
-
|------|------|-------------|
|
|
214
|
-
| `--main` | boolean | Enable Main mode |
|
|
215
|
-
| `--noFlush` | boolean | Skip removing output folders before the run |
|
|
216
|
-
|
|
217
|
-
```json
|
|
218
|
-
{ "scripts": { "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts" } }
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
</details>
|
|
222
|
-
|
|
223
|
-
### Parallel multi-symbol (`--entry`)
|
|
224
|
-
|
|
225
|
-
> **Power-user modifier โ skip unless needed.** The standard flow runs one symbol from `--symbol`. Use `--entry` to fan one strategy out across many symbols at once, or to drive `*.background()` from a UI / DB / API.
|
|
226
|
-
|
|
227
|
-
`--entry` is a modifier โ combine it with exactly one of `--backtest`/`--live`/`--paper`/`--walker`, plus one positional entry file. The CLI does only the boilerplate (`Setup`, providers, the matching `./modules/<mode>.module`, SIGINT that stops every active run, `shutdown()` once `listenDone*` reports all runs complete); **you** pick the symbol set, warm cache, and call `*.background()`.
|
|
228
|
-
|
|
229
|
-
<details>
|
|
230
|
-
<summary>Example โ backtest one strategy across five symbols</summary>
|
|
231
|
-
|
|
232
|
-
```javascript
|
|
233
|
-
// src/multi-symbol.mjs
|
|
234
|
-
import { addExchangeSchema, addFrameSchema, addStrategySchema, Backtest, warmCandles } from "backtest-kit";
|
|
235
|
-
import ccxt from "ccxt";
|
|
236
|
-
|
|
237
|
-
addExchangeSchema({ exchangeName: "binance",
|
|
238
|
-
getCandles: async (symbol, interval, since, limit) => {
|
|
239
|
-
const exchange = new ccxt.binance();
|
|
240
|
-
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
241
|
-
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
242
|
-
({ timestamp, open, high, low, close, volume }));
|
|
243
|
-
},
|
|
244
|
-
formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8) });
|
|
245
|
-
|
|
246
|
-
addFrameSchema({ frameName: "feb-2026", interval: "1m",
|
|
247
|
-
startDate: new Date("2026-02-01"), endDate: new Date("2026-02-28") });
|
|
248
|
-
addStrategySchema({ strategyName: "my-strategy", interval: "15m", getSignal: async () => null });
|
|
249
|
-
|
|
250
|
-
// Decide the symbol set yourself โ UI, database, API, or a list.
|
|
251
|
-
for (const symbol of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
252
|
-
// optional: await warmCandles({ exchangeName: "binance", interval: "1m", symbol, from, to });
|
|
253
|
-
Backtest.background(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "feb-2026" });
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
```bash
|
|
258
|
-
npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter).
|
|
262
|
-
|
|
263
|
-
</details>
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## ๐ ๏ธ Tooling modes
|
|
268
|
-
|
|
269
|
-
Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
|
|
270
|
-
|
|
271
|
-
> **The `<mode>.module` convention.** By default the CLI auto-registers CCXT Binance. To use a different exchange (custom API keys, rate limits, a non-spot market), drop a `modules/<mode>.module.ts` that calls `addExchangeSchema` from `backtest-kit`. The CLI loads it automatically before running, trying `.ts`/`.mjs`/`.cjs`; it's searched **next to the target file first, then in the project root**. `.env` is loaded root-first then the target-file dir (override), so API keys stay out of code.
|
|
272
|
-
|
|
273
|
-
<details>
|
|
274
|
-
<summary>The shared <code><mode>.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
// modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
|
|
278
|
-
import { addExchangeSchema } from "backtest-kit";
|
|
279
|
-
import ccxt from "ccxt";
|
|
280
|
-
|
|
281
|
-
addExchangeSchema({
|
|
282
|
-
exchangeName: "my-exchange",
|
|
283
|
-
getCandles: async (symbol, interval, since, limit) => {
|
|
284
|
-
const exchange = new ccxt.bybit({
|
|
285
|
-
apiKey: process.env.BYBIT_API_KEY, secret: process.env.BYBIT_API_SECRET, enableRateLimit: true,
|
|
286
|
-
});
|
|
287
|
-
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
288
|
-
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
289
|
-
({ timestamp, open, high, low, close, volume }));
|
|
290
|
-
},
|
|
291
|
-
formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8),
|
|
292
|
-
});
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
```env
|
|
296
|
-
# .env (loaded root-first, then next to the target file)
|
|
297
|
-
BYBIT_API_KEY=xxx
|
|
298
|
-
BYBIT_API_SECRET=yyy
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
</details>
|
|
302
|
-
|
|
303
|
-
### ๐ฒ Pine โ run local PineScript (`--pine`)
|
|
304
|
-
|
|
305
|
-
Executes any local `.pine` file against a real exchange and prints results as a Markdown table โ no TradingView account. Reads every `plot()` that uses `display=display.data_window` as an output column (others ignored); column names come straight from the plot names.
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180 --when "2025-09-24T12:00:00.000Z"
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
<details>
|
|
312
|
-
<summary>Pine flags, requirements & output</summary>
|
|
313
|
-
|
|
314
|
-
| Flag | Type | Description |
|
|
315
|
-
|------|------|-------------|
|
|
316
|
-
| `--pine` | boolean | Enable PineScript mode |
|
|
317
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
318
|
-
| `--timeframe` | string | Candle interval (default `"15m"`) |
|
|
319
|
-
| `--limit` | string | Candles to fetch (default `250`) |
|
|
320
|
-
| `--when` | string | End date โ ISO 8601 or Unix ms (default now) |
|
|
321
|
-
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
322
|
-
| `--output` | string | Output base name (default: `.pine` file name) |
|
|
323
|
-
| `--json` | boolean | Write plots as JSON array to `<pine-dir>/dump/{output}.json` |
|
|
324
|
-
| `--jsonl` | boolean | Write plots as JSONL to `<pine-dir>/dump/{output}.jsonl` |
|
|
325
|
-
| `--markdown` | boolean | Write Markdown table to `<pine-dir>/dump/{output}.md` |
|
|
326
|
-
|
|
327
|
-
`--limit` must cover indicator warmup bars โ rows before warmup show `N/A`. Positional: path to the `.pine` file. Exchange via `pine.module` (see convention above). Required plot form:
|
|
328
|
-
|
|
329
|
-
```pine
|
|
330
|
-
//@version=5
|
|
331
|
-
indicator("MyIndicator", overlay=true)
|
|
332
|
-
plot(close, "Close", display=display.data_window)
|
|
333
|
-
plot(position, "Position", display=display.data_window)
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Output (stdout, or `--markdown`/`--json`/`--jsonl` to `<pine-dir>/dump/`):
|
|
337
|
-
|
|
338
|
-
```
|
|
339
|
-
| Close | Position | timestamp |
|
|
340
|
-
| --- | --- | --- |
|
|
341
|
-
| 112871.28 | -1.0000 | 2025-09-22T15:00:00.000Z |
|
|
342
|
-
| 112736.00 | 0.0000 | 2025-09-22T18:30:00.000Z |
|
|
343
|
-
| 112653.90 | 1.0000 | 2025-09-22T22:15:00.000Z |
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
</details>
|
|
347
|
-
|
|
348
|
-
### ๐จ Editor โ visual Pine Script editor (`--editor`)
|
|
349
|
-
|
|
350
|
-

|
|
351
|
-
|
|
352
|
-
A browser-based Pine Script editor (powered by `@backtest-kit/ui`) with a live chart that updates on **โถ Run**.
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
npx @backtest-kit/cli --editor # โ http://localhost:60050?pine=1 opens automatically
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
<details>
|
|
359
|
-
<summary>Editor behavior & exchange</summary>
|
|
360
|
-
|
|
361
|
-
The CLI loads `./modules/editor.module` if present (register your exchange, same as `pine.module`), starts the `@backtest-kit/ui` server on `CC_WWWROOT_PORT` (default `60050`), and opens the editor in your browser. **Ctrl+C** stops it. Env: `CC_WWWROOT_HOST` (default `0.0.0.0`), `CC_WWWROOT_PORT` (default `60050`).
|
|
362
|
-
|
|
363
|
-
</details>
|
|
364
|
-
|
|
365
|
-
### ๐พ Candle Dump (`--dump`)
|
|
366
|
-
|
|
367
|
-
Fetch raw OHLCV candles from any registered exchange and save them โ no strategy file required. `dump/` is created in the current working directory.
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --when "2026-02-28T00:00:00.000Z" --jsonl --output feb2026_btc
|
|
371
|
-
# โ ./dump/feb2026_btc.jsonl
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
<details>
|
|
375
|
-
<summary>Dump flags</summary>
|
|
376
|
-
|
|
377
|
-
| Flag | Type | Description |
|
|
378
|
-
|------|------|-------------|
|
|
379
|
-
| `--dump` | boolean | Enable candle dump |
|
|
380
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
381
|
-
| `--timeframe` | string | Candle interval (default `"15m"`) |
|
|
382
|
-
| `--limit` | string | Candles to fetch (default `250`) |
|
|
383
|
-
| `--when` | string | End date โ ISO 8601 or Unix ms (default now) |
|
|
384
|
-
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
385
|
-
| `--output` | string | Output base name (default `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}`) |
|
|
386
|
-
| `--json` | boolean | Write candles as JSON array to `./dump/{output}.json` |
|
|
387
|
-
| `--jsonl` | boolean | Write candles as JSONL to `./dump/{output}.jsonl` |
|
|
388
|
-
|
|
389
|
-
Exchange via `dump.module` (see convention above), searched in the current working directory. No flag โ print to stdout.
|
|
390
|
-
|
|
391
|
-
</details>
|
|
392
|
-
|
|
393
|
-
### ๐ PnL Debug (`--pnldebug`)
|
|
394
|
-
|
|
395
|
-
Simulate a hypothetical position minute by minute โ running PnL, peak profit, max drawdown per candle โ without placing trades or loading a strategy.
|
|
396
|
-
|
|
397
|
-
```bash
|
|
398
|
-
npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
<details>
|
|
402
|
-
<summary>PnL Debug flags, columns & sample output</summary>
|
|
403
|
-
|
|
404
|
-
| Flag | Type | Description |
|
|
405
|
-
|------|------|-------------|
|
|
406
|
-
| `--pnldebug` | boolean | Enable PnL debug |
|
|
407
|
-
| `--priceopen` | number | Entry price (**required**) |
|
|
408
|
-
| `--direction` | string | `long` or `short` (default `long`) |
|
|
409
|
-
| `--when` | string | Start timestamp โ ISO 8601 or Unix ms (default now) |
|
|
410
|
-
| `--minutes` | string | Number of 1m candles to simulate (default `60`) |
|
|
411
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
412
|
-
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
413
|
-
| `--output` | string | Output base name (default `{SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP}`) |
|
|
414
|
-
| `--json` / `--jsonl` / `--markdown` | boolean | Save to `./dump/<output>.{json,jsonl,md}` |
|
|
415
|
-
|
|
416
|
-
Columns: `min` (1-based offset), `timestamp`, `close`, `pnl%` (signed, vs entry), `peak%` (highest so far, โฅ0), `drawdown%` (lowest so far, โค0). Exchange via `pnldebug.module` (convention above).
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
|
|
420
|
-
min | timestamp | close | pnl% | peak% | drawdown%
|
|
421
|
-
1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
|
|
422
|
-
2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
|
|
423
|
-
120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
</details>
|
|
427
|
-
|
|
428
|
-
### ๐ Broker Debug (`--brokerdebug`)
|
|
429
|
-
|
|
430
|
-
Fire a single broker commit against your live adapter without a full strategy โ verify your `brokerdebug.module` wires exchange calls correctly before waiting hours for a real signal.
|
|
431
|
-
|
|
432
|
-
```bash
|
|
433
|
-
npx @backtest-kit/cli --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
<details>
|
|
437
|
-
<summary>Broker Debug flags, commit types & how it works</summary>
|
|
438
|
-
|
|
439
|
-
| Flag | Type | Description |
|
|
440
|
-
|------|------|-------------|
|
|
441
|
-
| `--brokerdebug` | boolean | Enable broker debug |
|
|
442
|
-
| `--commit` | string | Commit type to fire (default `"signal-open"`) |
|
|
443
|
-
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
444
|
-
| `--exchange` | string | Exchange (default: first registered) |
|
|
445
|
-
|
|
446
|
-
`--commit` values โ hook: `signal-open`โ`onSignalOpenCommit`, `signal-close`โ`onSignalCloseCommit`, `partial-profit`โ`onPartialProfitCommit`, `partial-loss`โ`onPartialLossCommit`, `average-buy`โ`onAverageBuyCommit`, `trailing-stop`โ`onTrailingStopCommit`, `trailing-take`โ`onTrailingTakeCommit`, `breakeven`โ`onBreakevenCommit`.
|
|
447
|
-
|
|
448
|
-
The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--symbol`, derives a synthetic payload from `currentPrice` (TP = +2%, SL = โ2%), and calls the selected hook once; exits `0` on success. The module registers a `Broker` adapter (`Broker.useBrokerAdapter(...)` + `Broker.enable()`), not an exchange.
|
|
449
|
-
|
|
450
|
-
</details>
|
|
451
|
-
|
|
452
|
-
### ๐๏ธ Flush (`--flush`)
|
|
453
|
-
|
|
454
|
-
Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
|
|
455
|
-
|
|
456
|
-
```bash
|
|
457
|
-
npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts ./content/mar_2026.strategy/modules/backtest.module.ts
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
<details>
|
|
461
|
-
<summary>What flush removes</summary>
|
|
462
|
-
|
|
463
|
-
For each positional entry point the CLI resolves its directory and removes from `<entry-dir>/dump/`: `report` (backtest `.jsonl`), `log` (`log.jsonl`), `markdown` (exported reports), `agent` (agent outlines). Candle cache (`dump/data/`) and AI forecast outlines (`dump/outline/`) are **not** removed.
|
|
464
|
-
|
|
465
|
-
</details>
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## ๐๏ธ Project & monorepo
|
|
470
|
-
|
|
471
|
-
### Scaffolding (`--init`)
|
|
472
|
-
|
|
473
|
-
Bootstraps a ready-to-use project with an example strategy, an example Pine indicator, an AI-agent `CLAUDE.md`, and documentation fetched automatically. The target dir must not exist or be empty.
|
|
474
|
-
|
|
475
|
-
```bash
|
|
476
|
-
npx @backtest-kit/cli --init --output my-trading-bot # โ ./my-trading-bot/
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
<details>
|
|
480
|
-
<summary>Generated structure & automatic docs fetch</summary>
|
|
481
|
-
|
|
482
|
-
```
|
|
483
|
-
backtest-kit-project/
|
|
484
|
-
โโโ package.json # pre-configured with all backtest-kit deps
|
|
485
|
-
โโโ CLAUDE.md # AI-agent guide for writing strategies
|
|
486
|
-
โโโ content/feb_2026.strategy.ts # example strategy entry point
|
|
487
|
-
โโโ math/feb_2026.pine # example PineScript indicator
|
|
488
|
-
โโโ modules/{dump,pine}.module.ts # exchange schemas for --dump / --pine
|
|
489
|
-
โโโ report/feb_2026.md # example research report
|
|
490
|
-
โโโ docs/{...}.md + docs/lib/ # guides + fetched library READMEs
|
|
491
|
-
โโโ scripts/fetch_docs.mjs # downloads library READMEs into docs/lib/
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
After scaffolding the CLI runs `scripts/fetch_docs.mjs`, downloading the latest READMEs for `backtest-kit`, `@backtest-kit/graph`, `@backtest-kit/pinets`, `@backtest-kit/cli`, `garch`, `volume-anomaly`, `agent-swarm-kit`, `functools-kit` into `docs/lib/`. Re-run anytime with `node ./scripts/fetch_docs.mjs` or `npm run sync:lib`.
|
|
495
|
-
|
|
496
|
-
</details>
|
|
497
|
-
|
|
498
|
-
### Docker (`--docker`)
|
|
499
|
-
|
|
500
|
-
Scaffolds a self-contained Docker workspace with `docker-compose.yaml` and a strategy entry point, for zero-downtime live trading.
|
|
501
|
-
|
|
502
|
-
```bash
|
|
503
|
-
npx @backtest-kit/cli --docker && cd backtest-kit-docker
|
|
504
|
-
MODE=live SYMBOL=TRXUSDT STRATEGY_FILE=./content/feb_2026/feb_2026.strategy.ts docker-compose up -d
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
<details>
|
|
508
|
-
<summary>Two launch modes & environment variables</summary>
|
|
509
|
-
|
|
510
|
-
**1. `command:` in `docker-compose.yaml`** โ pin mode and flags directly; the entrypoint forwards all args to the CLI unchanged:
|
|
511
|
-
|
|
512
|
-
```yaml
|
|
513
|
-
command: [--live, --symbol, TRXUSDT, --strategy, feb_2026_strategy, --exchange, ccxt-exchange, ./content/feb_2026/feb_2026.strategy.ts, --ui]
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
**2. Inline env vars** โ `MODE` + `STRATEGY_FILE` on the command line, no file edits:
|
|
517
|
-
|
|
518
|
-
| Variable | Required | Default | Description |
|
|
519
|
-
|----------|----------|---------|-------------|
|
|
520
|
-
| `MODE` | yes | โ | `backtest` \| `live` \| `paper` \| `walker` |
|
|
521
|
-
| `STRATEGY_FILE` | yes | โ | Path to entry point (relative to `working_dir`) |
|
|
522
|
-
| `SYMBOL` | no | `BTCUSDT` | Trading pair |
|
|
523
|
-
| `STRATEGY` / `EXCHANGE` / `FRAME` | no | first registered | Names |
|
|
524
|
-
| `UI` / `TELEGRAM` / `VERBOSE` / `NO_CACHE` / `NO_FLUSH` / `ENTRY` | no | โ | Any non-empty value enables the matching flag |
|
|
525
|
-
|
|
526
|
-
</details>
|
|
527
|
-
|
|
528
|
-
### Monorepo cwd resolution
|
|
529
|
-
|
|
530
|
-
When the CLI loads an entry point it **changes the working directory to that file's location**, so every relative path (`dump/`, `modules/`, `template/`) resolves inside that strategy's folder. Each strategy gets its own `.env`, broker modules, templates, and dump dir โ so the same tool scales from one strategy to a desk of them.
|
|
531
|
-
|
|
532
|
-
<details>
|
|
533
|
-
<summary>How it works + isolated resources</summary>
|
|
534
|
-
|
|
535
|
-
`ResolveService` runs, before executing your entry point:
|
|
536
|
-
|
|
537
|
-
```
|
|
538
|
-
process.chdir(path.dirname(entryPoint)) // cwd โ strategy directory
|
|
539
|
-
dotenv.config({ path: rootDir + '/.env' }) // root .env first
|
|
540
|
-
dotenv.config({ path: strategyDir + '/.env', override: true }) // strategy .env overrides
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
```
|
|
544
|
-
monorepo/
|
|
545
|
-
โโโ package.json # root scripts (one per strategy)
|
|
546
|
-
โโโ .env # shared API keys
|
|
547
|
-
โโโ strategies/
|
|
548
|
-
โโโ oct_2025/
|
|
549
|
-
โ โโโ index.mjs # registers exchange/frame/strategy schemas
|
|
550
|
-
โ โโโ .env # overrides root .env for this strategy
|
|
551
|
-
โ โโโ modules/{live,paper,backtest}.module.mjs # broker adapters (optional)
|
|
552
|
-
โ โโโ template/ # custom Mustache templates (optional)
|
|
553
|
-
โ โโโ dump/ # auto-created: candle cache + reports
|
|
554
|
-
โโโ dec_2025/ โฆ
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
| Resource | Path (relative to strategy dir) | Isolated |
|
|
558
|
-
|----------|----------------------------------|----------|
|
|
559
|
-
| Candle cache | `./dump/data/candle/` | โ
per-strategy |
|
|
560
|
-
| Backtest reports | `./dump/` | โ
per-strategy |
|
|
561
|
-
| Broker module (live/paper/backtest) | `./modules/{live,paper,backtest}.module.mjs` | โ
per-strategy |
|
|
562
|
-
| Config module (walker) | `./modules/walker.module.mjs` | โ
loaded once |
|
|
563
|
-
| Telegram templates | `./template/*.mustache` | โ
per-strategy |
|
|
564
|
-
| Environment variables | `./.env` (overrides root) | โ
per-strategy |
|
|
565
|
-
|
|
566
|
-
Each run produces its own `dump/` โ easy to compare results across periods, by inspection or by pointing an AI agent at a specific folder.
|
|
567
|
-
|
|
568
|
-
</details>
|
|
569
|
-
|
|
570
|
-
### Folder-based import aliases
|
|
571
|
-
|
|
572
|
-
Every **top-level folder** in `process.cwd()` automatically becomes a bare import alias inside any strategy file โ no config, just create the folder. Extract shared utilities, indicators, or AI-agent logic into named folders and reuse them across strategies without relative-path hell.
|
|
573
|
-
|
|
574
|
-
<details>
|
|
575
|
-
<summary>Resolution table, structure & tsconfig</summary>
|
|
576
|
-
|
|
577
|
-
| Import | Resolves to |
|
|
578
|
-
|--------|-------------|
|
|
579
|
-
| `import { fn } from "utils"` | `<cwd>/utils/index.ts` (or `.js`/`.mjs`/`.cjs`) |
|
|
580
|
-
| `import { calcRSI } from "math/rsi"` | `<cwd>/math/rsi.ts` |
|
|
581
|
-
| `import { research } from "logic"` | `<cwd>/logic/index.ts` |
|
|
582
|
-
| `import { X } from "logic/contract/ResearchResponse.contract"` | `<cwd>/logic/contract/ResearchResponse.contract.ts` |
|
|
583
|
-
|
|
584
|
-
Both barrel and deep-subpath imports are supported. Add a matching `paths` entry to `tsconfig.json` so the editor resolves them:
|
|
585
|
-
|
|
586
|
-
```json
|
|
587
|
-
{
|
|
588
|
-
"compilerOptions": {
|
|
589
|
-
"moduleResolution": "bundler",
|
|
590
|
-
"paths": { "logic": ["./logic/index.ts"], "logic/*": ["./logic/*"], "math": ["./math/index.ts"], "math/*": ["./math/*"], "utils": ["./utils/index.ts"], "utils/*": ["./utils/*"] }
|
|
591
|
-
},
|
|
592
|
-
"include": ["./logic", "./math", "./utils", "./content", "./modules"]
|
|
593
|
-
}
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
</details>
|
|
597
|
-
|
|
598
|
-
### Entry point formats
|
|
599
|
-
|
|
600
|
-
The CLI auto-detects the format and loads it with the right runtime โ no flags. `.ts` via [`tsx`](https://tsx.is/) `tsImport()` (handles ESMโCJS cross-imports, no `tsc` step), `.mjs` via native `import()` (top-level `await`, ESM), `.cjs` via native `require()` (legacy/dual-package). Add `tsx` to deps for `.ts` strategies.
|
|
601
|
-
|
|
602
|
-
---
|
|
603
|
-
|
|
604
|
-
## ๐ Broker adapter โ transactional live orders
|
|
605
|
-
|
|
606
|
-
Mode-specific module files register a `Broker` adapter via side-effect import before the strategy starts. From then on, `backtest-kit` intercepts **every** trade-mutating call through the adapter *before* updating internal state โ if the adapter throws, the position state is never changed (atomic rollback, retried next tick). No manual wiring; in backtest mode no adapter is called at all.
|
|
607
|
-
|
|
608
|
-
| Mode flag | Module file | Loaded before |
|
|
609
|
-
|-----------|-------------|----------------|
|
|
610
|
-
| `--live` | `./modules/live.module.mjs` | `Live.background()` |
|
|
611
|
-
| `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
|
|
612
|
-
| `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
|
|
613
|
-
| `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
|
|
614
|
-
| `--main` | `./modules/main.module.mjs` | the custom entry point |
|
|
615
|
-
| `--brokerdebug` | `./modules/brokerdebug.module.mjs` | the broker commit test |
|
|
616
|
-
|
|
617
|
-
> Resolved relative to `cwd` (the strategy dir); `.mjs`/`.cjs`/`.ts` tried automatically. A missing module is a soft warning, not an error.
|
|
618
|
-
|
|
619
|
-
<details>
|
|
620
|
-
<summary>Adapter example & hook reference</summary>
|
|
621
|
-
|
|
622
|
-
```javascript
|
|
623
|
-
// live.module.mjs
|
|
624
|
-
import { Broker } from 'backtest-kit';
|
|
625
|
-
import { myExchange } from './exchange.mjs';
|
|
626
|
-
|
|
627
|
-
class MyBroker {
|
|
628
|
-
async onSignalOpenCommit({ symbol, priceOpen, direction }) { await myExchange.openPosition(symbol, direction, priceOpen); }
|
|
629
|
-
async onSignalCloseCommit({ symbol, priceClosed }) { await myExchange.closePosition(symbol, priceClosed); }
|
|
630
|
-
async onPartialProfitCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'sell', quantity: cost / currentPrice }); }
|
|
631
|
-
async onAverageBuyCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'buy', quantity: cost / currentPrice }); }
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
Broker.useBrokerAdapter(MyBroker);
|
|
635
|
-
Broker.enable();
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
| Method | Payload type | Triggered on |
|
|
639
|
-
|--------|--------------|--------------|
|
|
640
|
-
| `onSignalOpenCommit` | `BrokerSignalOpenPayload` | Position activation |
|
|
641
|
-
| `onSignalCloseCommit` | `BrokerSignalClosePayload` | SL / TP / manual close |
|
|
642
|
-
| `onPartialProfitCommit` | `BrokerPartialProfitPayload` | Partial profit |
|
|
643
|
-
| `onPartialLossCommit` | `BrokerPartialLossPayload` | Partial loss |
|
|
644
|
-
| `onTrailingStopCommit` | `BrokerTrailingStopPayload` | SL adjustment |
|
|
645
|
-
| `onTrailingTakeCommit` | `BrokerTrailingTakePayload` | TP adjustment |
|
|
646
|
-
| `onBreakevenCommit` | `BrokerBreakevenPayload` | SL moved to entry |
|
|
647
|
-
| `onAverageBuyCommit` | `BrokerAverageBuyPayload` | DCA entry |
|
|
648
|
-
|
|
649
|
-
All methods are optional; unimplemented hooks are silently skipped. TypeScript: implement `Partial<IBroker>` with typed payloads (`BrokerSignalOpenPayload`, etc.).
|
|
650
|
-
|
|
651
|
-
</details>
|
|
652
|
-
|
|
653
|
-
---
|
|
654
|
-
|
|
655
|
-
## โ๏ธ Configuration files (`config/*`)
|
|
656
|
-
|
|
657
|
-
Loaded from `{projectRoot}/config/`. The three runtime configs load in order โ **`setup.config` โ `loader.config` โ `alias.config`** โ before any strategy or module code. The UI/Telegram configs resolve **strategy dir โ project root โ package default** (first match wins) and accept `.ts`/`.cjs`/`.mjs`/`.js`.
|
|
658
|
-
|
|
659
|
-
### `setup.config` โ persistence & one-time init
|
|
660
|
-
|
|
661
|
-
Loaded once before any persistence call. **When present, the CLI skips its own default adapter registration** โ your config takes full ownership of the persistence layer.
|
|
662
|
-
|
|
663
|
-
<details>
|
|
664
|
-
<summary>MongoDB + Redis via @backtest-kit/mongo</summary>
|
|
665
|
-
|
|
666
|
-
`setup()` registers all 15 persistence adapters in one call, reading connection params from env (or passed explicitly):
|
|
667
|
-
|
|
668
|
-
```ts
|
|
669
|
-
// config/setup.config.ts
|
|
670
|
-
import { setup } from '@backtest-kit/mongo';
|
|
671
|
-
setup(); // or setup({ CC_MONGO_CONNECTION_STRING, CC_REDIS_HOST, CC_REDIS_PORT, CC_REDIS_PASSWORD })
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
```env
|
|
675
|
-
CC_MONGO_CONNECTION_STRING=mongodb://localhost:27017/backtest-kit
|
|
676
|
-
CC_REDIS_HOST=127.0.0.1
|
|
677
|
-
CC_REDIS_PORT=6379
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
No strategy-code changes โ adapters are wired transparently before the first persistence call.
|
|
681
|
-
|
|
682
|
-
</details>
|
|
683
|
-
|
|
684
|
-
### `loader.config` โ async startup gate
|
|
685
|
-
|
|
686
|
-
Loaded **after** `setup.config`, **before** strategy/module code. Unlike `setup.config` (side-effect import), it exports a function the CLI `await`s โ use it to wait for an async dependency before the run starts.
|
|
687
|
-
|
|
688
|
-
<details>
|
|
689
|
-
<summary>When to use it, export styles & examples</summary>
|
|
690
|
-
|
|
691
|
-
**Use it to:** wire microfrontends in a monorepo (pre-load sibling packages, hydrate a shared DI container); wait for a DB connection so the backtest fails fast instead of mid-run; warm caches / external APIs (instruments, calendar, fee tables); run schema migrations before signals flow.
|
|
692
|
-
|
|
693
|
-
Exactly one export style โ **never both** (if both present, `default` wins):
|
|
694
|
-
|
|
695
|
-
```ts
|
|
696
|
-
// config/loader.config.ts โ default export (preferred)
|
|
697
|
-
export default async () => { await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!); await redis.ping(); };
|
|
698
|
-
// โ or named export
|
|
699
|
-
export const loader = async () => { /* โฆ */ };
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
`@backtest-kit/mongo`'s `setup()` registers adapters synchronously but doesn't block on the connection; gate the run on a real connection here. To stitch microfrontends: `import "@my-org/brokers"; import "@my-org/signals";` (the `@my-org` alias is declared in `alias.config`).
|
|
703
|
-
|
|
704
|
-
</details>
|
|
705
|
-
|
|
706
|
-
### `alias.config` โ override any module import
|
|
707
|
-
|
|
708
|
-
Override any Node module import without touching strategy code. Loaded once on the first `import` and applied globally โ e.g. replace a heavy dependency with a stub for backtesting, or swap an external API for a mock in CI.
|
|
709
|
-
|
|
710
|
-
<details>
|
|
711
|
-
<summary>Formats & async factory</summary>
|
|
712
|
-
|
|
713
|
-
```ts
|
|
714
|
-
// config/alias.config.ts โ named export
|
|
715
|
-
export const ccxt = require("./stubs/ccxt.stub.cjs");
|
|
716
|
-
// config/alias.config.cjs โ default export
|
|
717
|
-
module.exports = { ccxt: require("./stubs/ccxt.stub.cjs") };
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
It may also export an **async factory** the CLI `await`s before strategy code runs โ handy for ESM-only modules that `require()` would throw on:
|
|
721
|
-
|
|
722
|
-
```ts
|
|
723
|
-
// async factory (default export); or `export const loader = async () => ({...})`
|
|
724
|
-
export default async () => ({ nanoid: await import("nanoid"), "p-limit": await import("p-limit") });
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
Both styles supported, never both at once (`default` wins). When strategy code calls `require("ccxt")`, the loader checks the alias table first โ no monkey-patching of `node_modules`. Applies to **all** modules in the process (not per-strategy).
|
|
728
|
-
|
|
729
|
-
</details>
|
|
730
|
-
|
|
731
|
-
### `symbol.config` & `notification.config` โ UI dashboard
|
|
732
|
-
|
|
733
|
-
<details>
|
|
734
|
-
<summary>symbol.config โ restrict/reorder the UI symbol list</summary>
|
|
735
|
-
|
|
736
|
-
By default the UI shows all exchange symbols. Override with a `config/symbol.config` (resolution: strategy dir โ project root โ package default):
|
|
737
|
-
|
|
738
|
-
```ts
|
|
739
|
-
// config/symbol.config.ts
|
|
740
|
-
export const symbol_list = [
|
|
741
|
-
{ icon: "/icon/btc.png", logo: "/icon/128/btc.png", symbol: "BTCUSDT", displayName: "Bitcoin", color: "#F7931A", priority: 50, description: "Bitcoin โ the first and most popular cryptocurrency" },
|
|
742
|
-
{ icon: "/icon/eth.png", logo: "/icon/128/eth.png", symbol: "ETHUSDT", displayName: "Ethereum", color: "#6F42C1", priority: 50, description: "Ethereum โ a blockchain platform for smart contracts" },
|
|
743
|
-
];
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
</details>
|
|
747
|
-
|
|
748
|
-
<details>
|
|
749
|
-
<summary>notification.config โ which notification categories the UI shows</summary>
|
|
750
|
-
|
|
751
|
-
Defaults (override per strategy):
|
|
752
|
-
|
|
753
|
-
| Key | Default | Description |
|
|
754
|
-
|-----|---------|-------------|
|
|
755
|
-
| `signal` | `true` | Signal lifecycle: opened, scheduled, closed, cancelled |
|
|
756
|
-
| `risk` | `true` | Risk manager rejections |
|
|
757
|
-
| `info` | `true` | Informational messages on an active signal |
|
|
758
|
-
| `breakeven` | `true` | Breakeven level reached |
|
|
759
|
-
| `common_error` | `true` | Non-fatal runtime errors |
|
|
760
|
-
| `critical_error` | `true` | Fatal errors that terminate the session |
|
|
761
|
-
| `validation_error` | `true` | Config / input validation errors |
|
|
762
|
-
| `strategy_commit` | `true` | All committed actions (partial close, DCA, trailing, โฆ) |
|
|
763
|
-
| `partial_loss` | `false` | Partial loss level reached (before commit) |
|
|
764
|
-
| `partial_profit` | `false` | Partial profit level reached (before commit) |
|
|
765
|
-
| `signal_sync` | `false` | Live order fill / exit confirmations from exchange sync |
|
|
766
|
-
|
|
767
|
-
```js
|
|
768
|
-
// config/notification.config.ts
|
|
769
|
-
export default { signal: true, risk: true, info: true, breakeven: true, common_error: true, critical_error: true, validation_error: true, strategy_commit: true, partial_loss: false, partial_profit: false, signal_sync: false };
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
</details>
|
|
773
|
-
|
|
774
|
-
### `telegram.config` โ programmatic message rendering
|
|
775
|
-
|
|
776
|
-
<details>
|
|
777
|
-
<summary>Override Mustache rendering with get*Markdown methods</summary>
|
|
778
|
-
|
|
779
|
-
By default messages render from Mustache templates (`template/*.mustache`). Export an object with any subset of `get*Markdown` methods (each gets the event payload, returns `Promise<string>`); unimplemented ones fall back to the template.
|
|
780
|
-
|
|
781
|
-
```ts
|
|
782
|
-
// config/telegram.config.ts
|
|
783
|
-
import { IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract } from "backtest-kit";
|
|
784
|
-
export default {
|
|
785
|
-
async getOpenedMarkdown(e: IStrategyTickResultOpened) { return `**Opened** ${e.symbol} at ${e.priceOpen}`; },
|
|
786
|
-
async getClosedMarkdown(e: IStrategyTickResultClosed) { return `**Closed** ${e.symbol} at ${e.priceClosed}`; },
|
|
787
|
-
async getRiskMarkdown(e: RiskContract) { return `**Risk rejected** ${e.symbol}`; },
|
|
788
|
-
};
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
Methods โ event types: `getOpenedMarkdown`/`getClosedMarkdown`/`getScheduledMarkdown`/`getCancelledMarkdown` (`IStrategyTickResult*`), `getRiskMarkdown` (`RiskContract`), `getPartialProfitMarkdown`/`getPartialLossMarkdown`/`getBreakevenMarkdown`/`getTrailingTakeMarkdown`/`getTrailingStopMarkdown`/`getAverageBuyMarkdown` (the matching `*Commit`), `getSignalOpenMarkdown`/`getSignalCloseMarkdown` (`SignalOpen/CloseContract`), `getCancelScheduledMarkdown`/`getClosePendingMarkdown` (`*Commit`), `getSignalInfoMarkdown` (`SignalInfoContract`).
|
|
792
|
-
|
|
793
|
-
</details>
|
|
794
|
-
|
|
795
|
-
---
|
|
796
|
-
|
|
797
|
-
## ๐ Integrations
|
|
798
|
-
|
|
799
|
-
### Web dashboard (`--ui`)
|
|
800
|
-
|
|
801
|
-
Starts the `@backtest-kit/ui` server at `http://localhost:60050` (host/port via `CC_WWWROOT_HOST` / `CC_WWWROOT_PORT`). Restrict the symbol list with `symbol.config` and notification categories with `notification.config` (above).
|
|
802
|
-
|
|
803
|
-
### Telegram (`--telegram`)
|
|
804
|
-
|
|
805
|
-
Sends formatted HTML messages with 1m / 15m / 1h price charts for every position event โ opened, closed, scheduled, cancelled, risk rejection, partial profit/loss, trailing stop/take, breakeven. Requires `CC_TELEGRAM_TOKEN` and `CC_TELEGRAM_CHANNEL`. Customize per-event rendering with `telegram.config` (above).
|
|
806
|
-
|
|
807
|
-
---
|
|
808
|
-
|
|
809
|
-
## ๐งช Programmatic API โ `run(mode, args)`
|
|
810
|
-
|
|
811
|
-
Use the CLI as a library โ call `run()` from your own script, no child process or flag parsing.
|
|
812
|
-
|
|
813
|
-
```typescript
|
|
814
|
-
import { run } from '@backtest-kit/cli';
|
|
815
|
-
|
|
816
|
-
await run('backtest', { entryPoint: './src/index.mjs', symbol: 'ETHUSDT', frame: 'feb-2024', cacheInterval: ['1m','15m','1h'], verbose: true });
|
|
817
|
-
await run('paper', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT' });
|
|
818
|
-
await run('live', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT', verbose: true });
|
|
819
|
-
```
|
|
820
|
-
|
|
821
|
-
<details>
|
|
822
|
-
<summary>Payload fields (call once per process)</summary>
|
|
823
|
-
|
|
824
|
-
`run()` can be called **only once per process** โ a second call throws `"Should be called only once"`. `mode`: `"backtest" | "paper" | "live"`.
|
|
825
|
-
|
|
826
|
-
**Backtest:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `frame` (first registered), `cacheInterval` (`["1m","15m","30m","1h","4h"]`), `noCache` (`false`), `noFlush` (`false`), `verbose` (`false`).
|
|
827
|
-
|
|
828
|
-
**Paper / Live:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `verbose` (`false`).
|
|
829
|
-
|
|
830
|
-
</details>
|
|
831
|
-
|
|
832
|
-
---
|
|
833
|
-
|
|
834
|
-
## ๐ Environment variables
|
|
835
|
-
|
|
836
|
-
```env
|
|
837
|
-
CC_TELEGRAM_TOKEN=your_bot_token_here # required for --telegram (from @BotFather)
|
|
838
|
-
CC_TELEGRAM_CHANNEL=-100123456789 # required for --telegram (channel/chat ID)
|
|
839
|
-
CC_WWWROOT_HOST=0.0.0.0 # UI bind address (default 0.0.0.0)
|
|
840
|
-
CC_WWWROOT_PORT=60050 # UI port (default 60050)
|
|
841
|
-
CC_QUICKCHART_HOST= # optional self-hosted QuickChart URL
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
<details>
|
|
845
|
-
<summary>Default behaviors (when a schema isn't registered)</summary>
|
|
846
|
-
|
|
847
|
-
| Component | Default | Note |
|
|
848
|
-
|-----------|---------|------|
|
|
849
|
-
| **Exchange** | CCXT Binance (`default_exchange`) | warns; **does not support order book in backtest** โ register a custom exchange with snapshot storage if your strategy calls `getOrderBook()` in backtest |
|
|
850
|
-
| **Frame** | February 2024 (`default_frame`) | warns |
|
|
851
|
-
| **Symbol** | `BTCUSDT` | โ |
|
|
852
|
-
| **Cache intervals** | `1m, 15m, 30m, 4h` | used if `--cacheInterval` not given; skip with `--noCache` |
|
|
853
|
-
|
|
854
|
-
</details>
|
|
855
|
-
|
|
856
|
-
---
|
|
857
|
-
|
|
858
|
-
## ๐ก Why @backtest-kit/cli
|
|
859
|
-
|
|
860
|
-
Instead of writing infrastructure for every project โ manual logger/storage/notification setup, CLI arg parsing, exchange registration, cache warming, Telegram bot, SIGINT handling, run wiring โ the whole thing is one script:
|
|
861
|
-
|
|
862
|
-
```json
|
|
863
|
-
{ "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
Zero to running backtest in seconds ยท automatic candle-cache warming with retry ยท production web dashboard out of the box ยท Telegram alerts with charts (no chart code) ยท graceful SIGINT shutdown (no hanging processes) ยท pluggable logger โ override the built-in one with `setLogger()` from your strategy module ยท works with any `backtest-kit` strategy as-is ยท broker hooks via side-effect modules (no CLI internals to touch).
|
|
867
|
-
|
|
868
|
-
## ๐ค Contribute
|
|
869
|
-
|
|
870
|
-
Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
871
|
-
|
|
872
|
-
## ๐ License
|
|
873
|
-
|
|
874
|
-
MIT ยฉ [tripolskypetr](https://github.com/tripolskypetr)
|
|
1
|
+
<img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/square_compasses.svg" height="45px" align="right">
|
|
2
|
+
|
|
3
|
+
# ๐ @backtest-kit/cli
|
|
4
|
+
|
|
5
|
+
> Zero-boilerplate CLI for [backtest-kit](https://www.npmjs.com/package/backtest-kit). Point it at a strategy file, pick a mode, and it handles exchange connectivity, candle caching, the web dashboard, Telegram alerts, and graceful shutdown for you โ no setup code.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
10
|
+
[](https://npmjs.org/package/@backtest-kit/cli)
|
|
11
|
+
[]()
|
|
12
|
+
|
|
13
|
+
๐ **[Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** ยท ๐ **[Reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example)** ยท ๐ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
|
|
14
|
+
|
|
15
|
+
> **New here?** The fastest real setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) โ a working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there, not from scratch.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐ Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Scaffold a project (boilerplate stays inside the CLI; docs auto-fetched)
|
|
23
|
+
npx @backtest-kit/cli --init --output backtest-kit-project
|
|
24
|
+
cd backtest-kit-project && npm install && npm start -- --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The whole onboarding is: write a strategy file that registers schemas via `backtest-kit`, point the CLI at it, choose a flag.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts --symbol BTCUSDT
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
<details>
|
|
34
|
+
<summary>The strategy entry point (the CLI is only the runner)</summary>
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// src/index.mjs โ registers schemas via backtest-kit; @backtest-kit/cli just runs it
|
|
38
|
+
import { addStrategySchema, addExchangeSchema, addFrameSchema } from 'backtest-kit';
|
|
39
|
+
import ccxt from 'ccxt';
|
|
40
|
+
|
|
41
|
+
addExchangeSchema({
|
|
42
|
+
exchangeName: 'binance',
|
|
43
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
44
|
+
const exchange = new ccxt.binance();
|
|
45
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
46
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
47
|
+
({ timestamp, open, high, low, close, volume }));
|
|
48
|
+
},
|
|
49
|
+
formatPrice: (symbol, price) => price.toFixed(2),
|
|
50
|
+
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
addFrameSchema({ frameName: 'feb-2024', interval: '1m',
|
|
54
|
+
startDate: new Date('2024-02-01'), endDate: new Date('2024-02-29') });
|
|
55
|
+
|
|
56
|
+
addStrategySchema({ strategyName: 'my-strategy', interval: '15m',
|
|
57
|
+
getSignal: async (symbol) => null }); // return a signal or null
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Wire it into `package.json` once and the positional path never changes:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"scripts": {
|
|
65
|
+
"backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
|
|
66
|
+
"paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
|
|
67
|
+
"start": "npx @backtest-kit/cli --live ./src/index.mjs"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": { "@backtest-kit/cli": "latest", "backtest-kit": "latest", "ccxt": "latest" }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run backtest -- --symbol BTCUSDT --ui --telegram # add integrations with flags
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
</details>
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## ๐ค Philosophy โ React vs Next.js, but for trading
|
|
82
|
+
|
|
83
|
+
`@backtest-kit/cli` does **two things well with one tool**.
|
|
84
|
+
|
|
85
|
+
**1. The lightest runner for a solo quant on day one.** Write a strategy, point the CLI at it, you're trading. No DI container to learn, no scaffold to fight, no infra to copy-paste. The day you have an idea you can backtest it; the week you have an edge you can paper-trade it; the month you have a P&L you can run it live โ same CLI, different flag.
|
|
86
|
+
|
|
87
|
+
**2. A monorepo-grade runner for when the business takes off.** The moment you start making money is the worst moment to rewrite your stack. So the CLI is monorepo-ready from day one even if you don't use it that way at first: per-strategy `.env`, per-strategy broker modules, folder-based import aliases, isolated dump dirs. The tool you backtested your first idea with is the tool that runs a desk of strategies in production โ no rewrite, no language switch, only more files.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ๐บ๏ธ Mode matrix
|
|
92
|
+
|
|
93
|
+
Every invocation is **one mode** (a primary flag) + a positional strategy/entry path + optional modifiers. `--ui` and `--telegram` are integrations that attach to any trading mode.
|
|
94
|
+
|
|
95
|
+
| Mode | Flag | What it does |
|
|
96
|
+
|------|------|--------------|
|
|
97
|
+
| **Backtest** | `--backtest` | Run a strategy on historical candle data (uses a `FrameSchema`) |
|
|
98
|
+
| **Paper** | `--paper` | Live prices, no real orders โ identical code path to live |
|
|
99
|
+
| **Live** | `--live` | Real trades via exchange API |
|
|
100
|
+
| **Walker** | `--walker` | A/B-compare multiple strategies on the same history, ranked report |
|
|
101
|
+
| **Main** | `--main` | Run a custom entry point with the full environment prepared, **no** trading harness |
|
|
102
|
+
| **Pine** | `--pine` | Run a local `.pine` indicator against exchange data |
|
|
103
|
+
| **Editor** | `--editor` | Open the visual Pine Script editor in the browser |
|
|
104
|
+
| **Candle Dump** | `--dump` | Fetch & save raw OHLCV candles to a file |
|
|
105
|
+
| **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price & direction |
|
|
106
|
+
| **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live adapter |
|
|
107
|
+
| **Flush** | `--flush` | Delete report/log/markdown/agent folders from a strategy dump dir |
|
|
108
|
+
| **Init** | `--init` | Scaffold a new project |
|
|
109
|
+
| **Docker** | `--docker` | Scaffold a self-contained Docker workspace |
|
|
110
|
+
| *modifiers* | `--ui` ยท `--telegram` ยท `--entry` | Web dashboard ยท Telegram alerts ยท fan out one strategy across many symbols |
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>Complete core flag reference</summary>
|
|
114
|
+
|
|
115
|
+
| Flag | Type | Description |
|
|
116
|
+
|------|------|-------------|
|
|
117
|
+
| `--backtest` | boolean | Run historical backtest (default `false`) |
|
|
118
|
+
| `--walker` | boolean | Run Walker A/B comparison (default `false`) |
|
|
119
|
+
| `--paper` | boolean | Paper trading โ live prices, no orders (default `false`) |
|
|
120
|
+
| `--live` | boolean | Run live trading (default `false`) |
|
|
121
|
+
| `--main` | boolean | Custom entry point, no trading harness (default `false`) |
|
|
122
|
+
| `--ui` | boolean | Start web UI dashboard (default `false`) |
|
|
123
|
+
| `--telegram` | boolean | Enable Telegram notifications (default `false`) |
|
|
124
|
+
| `--verbose` | boolean | Log each candle fetch (default `false`) |
|
|
125
|
+
| `--noCache` | boolean | Skip candle cache warming before backtest (default `false`) |
|
|
126
|
+
| `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before run (default `false`) |
|
|
127
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
128
|
+
| `--strategy` | string | Strategy name (default: first registered) |
|
|
129
|
+
| `--exchange` | string | Exchange name (default: first registered) |
|
|
130
|
+
| `--frame` | string | Backtest frame name (default: first registered) |
|
|
131
|
+
| `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
|
|
132
|
+
| `--brokerdebug` | boolean | Fire a single broker commit against the live adapter (default `false`) |
|
|
133
|
+
| `--commit` | string | Commit type for `--brokerdebug` (default `"signal-open"`) |
|
|
134
|
+
|
|
135
|
+
**Positional argument (required):** path to your strategy entry point file โ set once in `package.json` scripts. Tool-specific flags (`--pine`, `--dump`, `--pnldebug`, `--docker`, โฆ) are documented in their sections below.
|
|
136
|
+
|
|
137
|
+
</details>
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## ๐ Trading modes
|
|
142
|
+
|
|
143
|
+
The four modes that actually run strategies share one engine and one set of guarantees โ only the clock and the order routing differ.
|
|
144
|
+
|
|
145
|
+
### Backtest ยท Paper ยท Live
|
|
146
|
+
|
|
147
|
+
<details>
|
|
148
|
+
<summary>How each behaves</summary>
|
|
149
|
+
|
|
150
|
+
**Backtest** (`--backtest`) โ runs against historical candles via a registered `FrameSchema`. Before running, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from the strategy's `dump/` dir, then warms the candle cache for every interval in `--cacheInterval`; subsequent runs reuse the cache with no API calls. `--noCache` skips warming, `--noFlush` keeps output folders.
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{ "scripts": { "backtest": "npx @backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs" } }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Paper** (`--paper`) โ connects to the live exchange but places no real orders. **Identical code path to live** โ the safe way to validate a strategy.
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{ "scripts": { "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs" } }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Live** (`--live`) โ deploys a real bot. Requires exchange API keys in `.env`. Combine with `--ui --telegram` for a monitored deployment.
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{ "scripts": { "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs" } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
</details>
|
|
169
|
+
|
|
170
|
+
### Walker โ A/B strategy comparison
|
|
171
|
+
|
|
172
|
+
Runs the same historical period against multiple strategy files and prints a ranked report. Use it to pick the best variant before deploying.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npx @backtest-kit/cli --walker --symbol BTCUSDT --noCache --markdown --output feb_2026_comparison \
|
|
176
|
+
./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
177
|
+
# โ ./dump/feb_2026_comparison.md
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
<details>
|
|
181
|
+
<summary>Walker flags, output modes & behavior</summary>
|
|
182
|
+
|
|
183
|
+
Each positional argument is a separate strategy entry point. Before loading them the CLI removes the `report`/`log`/`markdown`/`agent` folders from each entry point's `dump/` (skip with `--noFlush`). All files load **without changing `process.cwd()`** โ `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files. If no frame is registered, the CLI falls back to the last 31 days from `Date.now()` with a warning.
|
|
184
|
+
|
|
185
|
+
| Flag | Type | Description |
|
|
186
|
+
|------|------|-------------|
|
|
187
|
+
| `--walker` | boolean | Enable Walker comparison |
|
|
188
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
189
|
+
| `--cacheInterval` | string | Intervals to pre-cache (default `"1m, 15m, 30m, 4h"`) |
|
|
190
|
+
| `--noCache` | boolean | Skip candle cache warming |
|
|
191
|
+
| `--noFlush` | boolean | Skip removing output folders before the run |
|
|
192
|
+
| `--verbose` | boolean | Log each candle fetch and strategy progress |
|
|
193
|
+
| `--output` | string | Output file base name (default `walker_{SYMBOL}_{TIMESTAMP}`) |
|
|
194
|
+
| `--json` | boolean | Save `Walker.getData()` as JSON to `./dump/<output>.json` and exit |
|
|
195
|
+
| `--markdown` | boolean | Save `Walker.getReport()` as `./dump/<output>.md` and exit |
|
|
196
|
+
|
|
197
|
+
**Output:** no flag โ print Markdown report to stdout; `--json` / `--markdown` โ save and exit. **Module hook:** `./modules/walker.module` loads automatically before the comparison (`.ts`/`.mjs`/`.cjs` tried in order).
|
|
198
|
+
|
|
199
|
+
</details>
|
|
200
|
+
|
|
201
|
+
### Main โ custom entry point, no trading harness
|
|
202
|
+
|
|
203
|
+
Runs a single entry point with the full CLI environment prepared (`.env`, `config/setup.config`, `config/loader.config`, `./modules/main.module`, cwd changed to the entry-point folder, graceful shutdown wired) โ but **never** starts a trading harness. Use it to bootstrap the environment for a quick action, e.g. calling a 3rd-party API with automatic `.env` import.
|
|
204
|
+
|
|
205
|
+
<details>
|
|
206
|
+
<summary>Main behavior & flags</summary>
|
|
207
|
+
|
|
208
|
+
Unlike the trading modes it does not call `Backtest/Live/Walker.background`, pick a symbol, warm the cache, or resolve a strategy/exchange/frame โ the entry point decides what to run. Exactly **one** positional entry point is required (`Entry point is required` otherwise). `process.cwd()` changes to the entry-point directory and its local `.env` overrides the root `.env`.
|
|
209
|
+
|
|
210
|
+
Although the CLI starts nothing itself, any `Backtest`/`Live`/`Walker` run **your** entry point launches is still managed: the process exits once `listenDone*` reports completion, the first `Ctrl+C` stops every active run via `*.list()`/`*.stop()`, a second force-quits. `./modules/main.module` loads automatically before the entry point.
|
|
211
|
+
|
|
212
|
+
| Flag | Type | Description |
|
|
213
|
+
|------|------|-------------|
|
|
214
|
+
| `--main` | boolean | Enable Main mode |
|
|
215
|
+
| `--noFlush` | boolean | Skip removing output folders before the run |
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{ "scripts": { "main": "npx @backtest-kit/cli --main ./tools/fetch_fear_and_greed.ts" } }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
</details>
|
|
222
|
+
|
|
223
|
+
### Parallel multi-symbol (`--entry`)
|
|
224
|
+
|
|
225
|
+
> **Power-user modifier โ skip unless needed.** The standard flow runs one symbol from `--symbol`. Use `--entry` to fan one strategy out across many symbols at once, or to drive `*.background()` from a UI / DB / API.
|
|
226
|
+
|
|
227
|
+
`--entry` is a modifier โ combine it with exactly one of `--backtest`/`--live`/`--paper`/`--walker`, plus one positional entry file. The CLI does only the boilerplate (`Setup`, providers, the matching `./modules/<mode>.module`, SIGINT that stops every active run, `shutdown()` once `listenDone*` reports all runs complete); **you** pick the symbol set, warm cache, and call `*.background()`.
|
|
228
|
+
|
|
229
|
+
<details>
|
|
230
|
+
<summary>Example โ backtest one strategy across five symbols</summary>
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// src/multi-symbol.mjs
|
|
234
|
+
import { addExchangeSchema, addFrameSchema, addStrategySchema, Backtest, warmCandles } from "backtest-kit";
|
|
235
|
+
import ccxt from "ccxt";
|
|
236
|
+
|
|
237
|
+
addExchangeSchema({ exchangeName: "binance",
|
|
238
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
239
|
+
const exchange = new ccxt.binance();
|
|
240
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
241
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
242
|
+
({ timestamp, open, high, low, close, volume }));
|
|
243
|
+
},
|
|
244
|
+
formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8) });
|
|
245
|
+
|
|
246
|
+
addFrameSchema({ frameName: "feb-2026", interval: "1m",
|
|
247
|
+
startDate: new Date("2026-02-01"), endDate: new Date("2026-02-28") });
|
|
248
|
+
addStrategySchema({ strategyName: "my-strategy", interval: "15m", getSignal: async () => null });
|
|
249
|
+
|
|
250
|
+
// Decide the symbol set yourself โ UI, database, API, or a list.
|
|
251
|
+
for (const symbol of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
252
|
+
// optional: await warmCandles({ exchangeName: "binance", interval: "1m", symbol, from, to });
|
|
253
|
+
Backtest.background(symbol, { strategyName: "my-strategy", exchangeName: "binance", frameName: "feb-2026" });
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter).
|
|
262
|
+
|
|
263
|
+
</details>
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## ๐ ๏ธ Tooling modes
|
|
268
|
+
|
|
269
|
+
Five utilities that don't run a strategy. They share one convention, explained once here and referenced below.
|
|
270
|
+
|
|
271
|
+
> **The `<mode>.module` convention.** By default the CLI auto-registers CCXT Binance. To use a different exchange (custom API keys, rate limits, a non-spot market), drop a `modules/<mode>.module.ts` that calls `addExchangeSchema` from `backtest-kit`. The CLI loads it automatically before running, trying `.ts`/`.mjs`/`.cjs`; it's searched **next to the target file first, then in the project root**. `.env` is loaded root-first then the target-file dir (override), so API keys stay out of code.
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary>The shared <code><mode>.module.ts</code> shape (pine / editor / dump / pnldebug / brokerdebug)</summary>
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// modules/pine.module.ts (same shape for editor/dump/pnldebug.module; brokerdebug registers a Broker instead)
|
|
278
|
+
import { addExchangeSchema } from "backtest-kit";
|
|
279
|
+
import ccxt from "ccxt";
|
|
280
|
+
|
|
281
|
+
addExchangeSchema({
|
|
282
|
+
exchangeName: "my-exchange",
|
|
283
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
284
|
+
const exchange = new ccxt.bybit({
|
|
285
|
+
apiKey: process.env.BYBIT_API_KEY, secret: process.env.BYBIT_API_SECRET, enableRateLimit: true,
|
|
286
|
+
});
|
|
287
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
288
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) =>
|
|
289
|
+
({ timestamp, open, high, low, close, volume }));
|
|
290
|
+
},
|
|
291
|
+
formatPrice: (s, p) => p.toFixed(2), formatQuantity: (s, q) => q.toFixed(8),
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```env
|
|
296
|
+
# .env (loaded root-first, then next to the target file)
|
|
297
|
+
BYBIT_API_KEY=xxx
|
|
298
|
+
BYBIT_API_SECRET=yyy
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
</details>
|
|
302
|
+
|
|
303
|
+
### ๐ฒ Pine โ run local PineScript (`--pine`)
|
|
304
|
+
|
|
305
|
+
Executes any local `.pine` file against a real exchange and prints results as a Markdown table โ no TradingView account. Reads every `plot()` that uses `display=display.data_window` as an output column (others ignored); column names come straight from the plot names.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180 --when "2025-09-24T12:00:00.000Z"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
<details>
|
|
312
|
+
<summary>Pine flags, requirements & output</summary>
|
|
313
|
+
|
|
314
|
+
| Flag | Type | Description |
|
|
315
|
+
|------|------|-------------|
|
|
316
|
+
| `--pine` | boolean | Enable PineScript mode |
|
|
317
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
318
|
+
| `--timeframe` | string | Candle interval (default `"15m"`) |
|
|
319
|
+
| `--limit` | string | Candles to fetch (default `250`) |
|
|
320
|
+
| `--when` | string | End date โ ISO 8601 or Unix ms (default now) |
|
|
321
|
+
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
322
|
+
| `--output` | string | Output base name (default: `.pine` file name) |
|
|
323
|
+
| `--json` | boolean | Write plots as JSON array to `<pine-dir>/dump/{output}.json` |
|
|
324
|
+
| `--jsonl` | boolean | Write plots as JSONL to `<pine-dir>/dump/{output}.jsonl` |
|
|
325
|
+
| `--markdown` | boolean | Write Markdown table to `<pine-dir>/dump/{output}.md` |
|
|
326
|
+
|
|
327
|
+
`--limit` must cover indicator warmup bars โ rows before warmup show `N/A`. Positional: path to the `.pine` file. Exchange via `pine.module` (see convention above). Required plot form:
|
|
328
|
+
|
|
329
|
+
```pine
|
|
330
|
+
//@version=5
|
|
331
|
+
indicator("MyIndicator", overlay=true)
|
|
332
|
+
plot(close, "Close", display=display.data_window)
|
|
333
|
+
plot(position, "Position", display=display.data_window)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Output (stdout, or `--markdown`/`--json`/`--jsonl` to `<pine-dir>/dump/`):
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
| Close | Position | timestamp |
|
|
340
|
+
| --- | --- | --- |
|
|
341
|
+
| 112871.28 | -1.0000 | 2025-09-22T15:00:00.000Z |
|
|
342
|
+
| 112736.00 | 0.0000 | 2025-09-22T18:30:00.000Z |
|
|
343
|
+
| 112653.90 | 1.0000 | 2025-09-22T22:15:00.000Z |
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
</details>
|
|
347
|
+
|
|
348
|
+
### ๐จ Editor โ visual Pine Script editor (`--editor`)
|
|
349
|
+
|
|
350
|
+

|
|
351
|
+
|
|
352
|
+
A browser-based Pine Script editor (powered by `@backtest-kit/ui`) with a live chart that updates on **โถ Run**.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npx @backtest-kit/cli --editor # โ http://localhost:60050?pine=1 opens automatically
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
<details>
|
|
359
|
+
<summary>Editor behavior & exchange</summary>
|
|
360
|
+
|
|
361
|
+
The CLI loads `./modules/editor.module` if present (register your exchange, same as `pine.module`), starts the `@backtest-kit/ui` server on `CC_WWWROOT_PORT` (default `60050`), and opens the editor in your browser. **Ctrl+C** stops it. Env: `CC_WWWROOT_HOST` (default `0.0.0.0`), `CC_WWWROOT_PORT` (default `60050`).
|
|
362
|
+
|
|
363
|
+
</details>
|
|
364
|
+
|
|
365
|
+
### ๐พ Candle Dump (`--dump`)
|
|
366
|
+
|
|
367
|
+
Fetch raw OHLCV candles from any registered exchange and save them โ no strategy file required. `dump/` is created in the current working directory.
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --when "2026-02-28T00:00:00.000Z" --jsonl --output feb2026_btc
|
|
371
|
+
# โ ./dump/feb2026_btc.jsonl
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
<details>
|
|
375
|
+
<summary>Dump flags</summary>
|
|
376
|
+
|
|
377
|
+
| Flag | Type | Description |
|
|
378
|
+
|------|------|-------------|
|
|
379
|
+
| `--dump` | boolean | Enable candle dump |
|
|
380
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
381
|
+
| `--timeframe` | string | Candle interval (default `"15m"`) |
|
|
382
|
+
| `--limit` | string | Candles to fetch (default `250`) |
|
|
383
|
+
| `--when` | string | End date โ ISO 8601 or Unix ms (default now) |
|
|
384
|
+
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
385
|
+
| `--output` | string | Output base name (default `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}`) |
|
|
386
|
+
| `--json` | boolean | Write candles as JSON array to `./dump/{output}.json` |
|
|
387
|
+
| `--jsonl` | boolean | Write candles as JSONL to `./dump/{output}.jsonl` |
|
|
388
|
+
|
|
389
|
+
Exchange via `dump.module` (see convention above), searched in the current working directory. No flag โ print to stdout.
|
|
390
|
+
|
|
391
|
+
</details>
|
|
392
|
+
|
|
393
|
+
### ๐ PnL Debug (`--pnldebug`)
|
|
394
|
+
|
|
395
|
+
Simulate a hypothetical position minute by minute โ running PnL, peak profit, max drawdown per candle โ without placing trades or loading a strategy.
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npx @backtest-kit/cli --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
<details>
|
|
402
|
+
<summary>PnL Debug flags, columns & sample output</summary>
|
|
403
|
+
|
|
404
|
+
| Flag | Type | Description |
|
|
405
|
+
|------|------|-------------|
|
|
406
|
+
| `--pnldebug` | boolean | Enable PnL debug |
|
|
407
|
+
| `--priceopen` | number | Entry price (**required**) |
|
|
408
|
+
| `--direction` | string | `long` or `short` (default `long`) |
|
|
409
|
+
| `--when` | string | Start timestamp โ ISO 8601 or Unix ms (default now) |
|
|
410
|
+
| `--minutes` | string | Number of 1m candles to simulate (default `60`) |
|
|
411
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
412
|
+
| `--exchange` | string | Exchange (default: first registered, falls back to CCXT Binance) |
|
|
413
|
+
| `--output` | string | Output base name (default `{SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP}`) |
|
|
414
|
+
| `--json` / `--jsonl` / `--markdown` | boolean | Save to `./dump/<output>.{json,jsonl,md}` |
|
|
415
|
+
|
|
416
|
+
Columns: `min` (1-based offset), `timestamp`, `close`, `pnl%` (signed, vs entry), `peak%` (highest so far, โฅ0), `drawdown%` (lowest so far, โค0). Exchange via `pnldebug.module` (convention above).
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:00:00.000Z | Minutes: 120
|
|
420
|
+
min | timestamp | close | pnl% | peak% | drawdown%
|
|
421
|
+
1 | 2025-02-25T00:01:00.000Z | 64020.10 | +0.08% | +0.08% | 0.00%
|
|
422
|
+
2 | 2025-02-25T00:02:00.000Z | 64105.30 | -0.06% | +0.08% | -0.06%
|
|
423
|
+
120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
</details>
|
|
427
|
+
|
|
428
|
+
### ๐ Broker Debug (`--brokerdebug`)
|
|
429
|
+
|
|
430
|
+
Fire a single broker commit against your live adapter without a full strategy โ verify your `brokerdebug.module` wires exchange calls correctly before waiting hours for a real signal.
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
npx @backtest-kit/cli --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
<details>
|
|
437
|
+
<summary>Broker Debug flags, commit types & how it works</summary>
|
|
438
|
+
|
|
439
|
+
| Flag | Type | Description |
|
|
440
|
+
|------|------|-------------|
|
|
441
|
+
| `--brokerdebug` | boolean | Enable broker debug |
|
|
442
|
+
| `--commit` | string | Commit type to fire (default `"signal-open"`) |
|
|
443
|
+
| `--symbol` | string | Trading pair (default `"BTCUSDT"`) |
|
|
444
|
+
| `--exchange` | string | Exchange (default: first registered) |
|
|
445
|
+
|
|
446
|
+
`--commit` values โ hook: `signal-open`โ`onSignalOpenCommit`, `signal-close`โ`onSignalCloseCommit`, `partial-profit`โ`onPartialProfitCommit`, `partial-loss`โ`onPartialLossCommit`, `average-buy`โ`onAverageBuyCommit`, `trailing-stop`โ`onTrailingStopCommit`, `trailing-take`โ`onTrailingTakeCommit`, `breakeven`โ`onBreakevenCommit`.
|
|
447
|
+
|
|
448
|
+
The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--symbol`, derives a synthetic payload from `currentPrice` (TP = +2%, SL = โ2%), and calls the selected hook once; exits `0` on success. The module registers a `Broker` adapter (`Broker.useBrokerAdapter(...)` + `Broker.enable()`), not an exchange.
|
|
449
|
+
|
|
450
|
+
</details>
|
|
451
|
+
|
|
452
|
+
### ๐๏ธ Flush (`--flush`)
|
|
453
|
+
|
|
454
|
+
Delete generated output folders from one or more strategy dump dirs **without** touching cached candle data.
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts ./content/mar_2026.strategy/modules/backtest.module.ts
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
<details>
|
|
461
|
+
<summary>What flush removes</summary>
|
|
462
|
+
|
|
463
|
+
For each positional entry point the CLI resolves its directory and removes from `<entry-dir>/dump/`: `report` (backtest `.jsonl`), `log` (`log.jsonl`), `markdown` (exported reports), `agent` (agent outlines). Candle cache (`dump/data/`) and AI forecast outlines (`dump/outline/`) are **not** removed.
|
|
464
|
+
|
|
465
|
+
</details>
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## ๐๏ธ Project & monorepo
|
|
470
|
+
|
|
471
|
+
### Scaffolding (`--init`)
|
|
472
|
+
|
|
473
|
+
Bootstraps a ready-to-use project with an example strategy, an example Pine indicator, an AI-agent `CLAUDE.md`, and documentation fetched automatically. The target dir must not exist or be empty.
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
npx @backtest-kit/cli --init --output my-trading-bot # โ ./my-trading-bot/
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
<details>
|
|
480
|
+
<summary>Generated structure & automatic docs fetch</summary>
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
backtest-kit-project/
|
|
484
|
+
โโโ package.json # pre-configured with all backtest-kit deps
|
|
485
|
+
โโโ CLAUDE.md # AI-agent guide for writing strategies
|
|
486
|
+
โโโ content/feb_2026.strategy.ts # example strategy entry point
|
|
487
|
+
โโโ math/feb_2026.pine # example PineScript indicator
|
|
488
|
+
โโโ modules/{dump,pine}.module.ts # exchange schemas for --dump / --pine
|
|
489
|
+
โโโ report/feb_2026.md # example research report
|
|
490
|
+
โโโ docs/{...}.md + docs/lib/ # guides + fetched library READMEs
|
|
491
|
+
โโโ scripts/fetch_docs.mjs # downloads library READMEs into docs/lib/
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
After scaffolding the CLI runs `scripts/fetch_docs.mjs`, downloading the latest READMEs for `backtest-kit`, `@backtest-kit/graph`, `@backtest-kit/pinets`, `@backtest-kit/cli`, `garch`, `volume-anomaly`, `agent-swarm-kit`, `functools-kit` into `docs/lib/`. Re-run anytime with `node ./scripts/fetch_docs.mjs` or `npm run sync:lib`.
|
|
495
|
+
|
|
496
|
+
</details>
|
|
497
|
+
|
|
498
|
+
### Docker (`--docker`)
|
|
499
|
+
|
|
500
|
+
Scaffolds a self-contained Docker workspace with `docker-compose.yaml` and a strategy entry point, for zero-downtime live trading.
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
npx @backtest-kit/cli --docker && cd backtest-kit-docker
|
|
504
|
+
MODE=live SYMBOL=TRXUSDT STRATEGY_FILE=./content/feb_2026/feb_2026.strategy.ts docker-compose up -d
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
<details>
|
|
508
|
+
<summary>Two launch modes & environment variables</summary>
|
|
509
|
+
|
|
510
|
+
**1. `command:` in `docker-compose.yaml`** โ pin mode and flags directly; the entrypoint forwards all args to the CLI unchanged:
|
|
511
|
+
|
|
512
|
+
```yaml
|
|
513
|
+
command: [--live, --symbol, TRXUSDT, --strategy, feb_2026_strategy, --exchange, ccxt-exchange, ./content/feb_2026/feb_2026.strategy.ts, --ui]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**2. Inline env vars** โ `MODE` + `STRATEGY_FILE` on the command line, no file edits:
|
|
517
|
+
|
|
518
|
+
| Variable | Required | Default | Description |
|
|
519
|
+
|----------|----------|---------|-------------|
|
|
520
|
+
| `MODE` | yes | โ | `backtest` \| `live` \| `paper` \| `walker` |
|
|
521
|
+
| `STRATEGY_FILE` | yes | โ | Path to entry point (relative to `working_dir`) |
|
|
522
|
+
| `SYMBOL` | no | `BTCUSDT` | Trading pair |
|
|
523
|
+
| `STRATEGY` / `EXCHANGE` / `FRAME` | no | first registered | Names |
|
|
524
|
+
| `UI` / `TELEGRAM` / `VERBOSE` / `NO_CACHE` / `NO_FLUSH` / `ENTRY` | no | โ | Any non-empty value enables the matching flag |
|
|
525
|
+
|
|
526
|
+
</details>
|
|
527
|
+
|
|
528
|
+
### Monorepo cwd resolution
|
|
529
|
+
|
|
530
|
+
When the CLI loads an entry point it **changes the working directory to that file's location**, so every relative path (`dump/`, `modules/`, `template/`) resolves inside that strategy's folder. Each strategy gets its own `.env`, broker modules, templates, and dump dir โ so the same tool scales from one strategy to a desk of them.
|
|
531
|
+
|
|
532
|
+
<details>
|
|
533
|
+
<summary>How it works + isolated resources</summary>
|
|
534
|
+
|
|
535
|
+
`ResolveService` runs, before executing your entry point:
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
process.chdir(path.dirname(entryPoint)) // cwd โ strategy directory
|
|
539
|
+
dotenv.config({ path: rootDir + '/.env' }) // root .env first
|
|
540
|
+
dotenv.config({ path: strategyDir + '/.env', override: true }) // strategy .env overrides
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
monorepo/
|
|
545
|
+
โโโ package.json # root scripts (one per strategy)
|
|
546
|
+
โโโ .env # shared API keys
|
|
547
|
+
โโโ strategies/
|
|
548
|
+
โโโ oct_2025/
|
|
549
|
+
โ โโโ index.mjs # registers exchange/frame/strategy schemas
|
|
550
|
+
โ โโโ .env # overrides root .env for this strategy
|
|
551
|
+
โ โโโ modules/{live,paper,backtest}.module.mjs # broker adapters (optional)
|
|
552
|
+
โ โโโ template/ # custom Mustache templates (optional)
|
|
553
|
+
โ โโโ dump/ # auto-created: candle cache + reports
|
|
554
|
+
โโโ dec_2025/ โฆ
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
| Resource | Path (relative to strategy dir) | Isolated |
|
|
558
|
+
|----------|----------------------------------|----------|
|
|
559
|
+
| Candle cache | `./dump/data/candle/` | โ
per-strategy |
|
|
560
|
+
| Backtest reports | `./dump/` | โ
per-strategy |
|
|
561
|
+
| Broker module (live/paper/backtest) | `./modules/{live,paper,backtest}.module.mjs` | โ
per-strategy |
|
|
562
|
+
| Config module (walker) | `./modules/walker.module.mjs` | โ
loaded once |
|
|
563
|
+
| Telegram templates | `./template/*.mustache` | โ
per-strategy |
|
|
564
|
+
| Environment variables | `./.env` (overrides root) | โ
per-strategy |
|
|
565
|
+
|
|
566
|
+
Each run produces its own `dump/` โ easy to compare results across periods, by inspection or by pointing an AI agent at a specific folder.
|
|
567
|
+
|
|
568
|
+
</details>
|
|
569
|
+
|
|
570
|
+
### Folder-based import aliases
|
|
571
|
+
|
|
572
|
+
Every **top-level folder** in `process.cwd()` automatically becomes a bare import alias inside any strategy file โ no config, just create the folder. Extract shared utilities, indicators, or AI-agent logic into named folders and reuse them across strategies without relative-path hell.
|
|
573
|
+
|
|
574
|
+
<details>
|
|
575
|
+
<summary>Resolution table, structure & tsconfig</summary>
|
|
576
|
+
|
|
577
|
+
| Import | Resolves to |
|
|
578
|
+
|--------|-------------|
|
|
579
|
+
| `import { fn } from "utils"` | `<cwd>/utils/index.ts` (or `.js`/`.mjs`/`.cjs`) |
|
|
580
|
+
| `import { calcRSI } from "math/rsi"` | `<cwd>/math/rsi.ts` |
|
|
581
|
+
| `import { research } from "logic"` | `<cwd>/logic/index.ts` |
|
|
582
|
+
| `import { X } from "logic/contract/ResearchResponse.contract"` | `<cwd>/logic/contract/ResearchResponse.contract.ts` |
|
|
583
|
+
|
|
584
|
+
Both barrel and deep-subpath imports are supported. Add a matching `paths` entry to `tsconfig.json` so the editor resolves them:
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"compilerOptions": {
|
|
589
|
+
"moduleResolution": "bundler",
|
|
590
|
+
"paths": { "logic": ["./logic/index.ts"], "logic/*": ["./logic/*"], "math": ["./math/index.ts"], "math/*": ["./math/*"], "utils": ["./utils/index.ts"], "utils/*": ["./utils/*"] }
|
|
591
|
+
},
|
|
592
|
+
"include": ["./logic", "./math", "./utils", "./content", "./modules"]
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
</details>
|
|
597
|
+
|
|
598
|
+
### Entry point formats
|
|
599
|
+
|
|
600
|
+
The CLI auto-detects the format and loads it with the right runtime โ no flags. `.ts` via [`tsx`](https://tsx.is/) `tsImport()` (handles ESMโCJS cross-imports, no `tsc` step), `.mjs` via native `import()` (top-level `await`, ESM), `.cjs` via native `require()` (legacy/dual-package). Add `tsx` to deps for `.ts` strategies.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## ๐ Broker adapter โ transactional live orders
|
|
605
|
+
|
|
606
|
+
Mode-specific module files register a `Broker` adapter via side-effect import before the strategy starts. From then on, `backtest-kit` intercepts **every** trade-mutating call through the adapter *before* updating internal state โ if the adapter throws, the position state is never changed (atomic rollback, retried next tick). No manual wiring; in backtest mode no adapter is called at all.
|
|
607
|
+
|
|
608
|
+
| Mode flag | Module file | Loaded before |
|
|
609
|
+
|-----------|-------------|----------------|
|
|
610
|
+
| `--live` | `./modules/live.module.mjs` | `Live.background()` |
|
|
611
|
+
| `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
|
|
612
|
+
| `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
|
|
613
|
+
| `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
|
|
614
|
+
| `--main` | `./modules/main.module.mjs` | the custom entry point |
|
|
615
|
+
| `--brokerdebug` | `./modules/brokerdebug.module.mjs` | the broker commit test |
|
|
616
|
+
|
|
617
|
+
> Resolved relative to `cwd` (the strategy dir); `.mjs`/`.cjs`/`.ts` tried automatically. A missing module is a soft warning, not an error.
|
|
618
|
+
|
|
619
|
+
<details>
|
|
620
|
+
<summary>Adapter example & hook reference</summary>
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
// live.module.mjs
|
|
624
|
+
import { Broker } from 'backtest-kit';
|
|
625
|
+
import { myExchange } from './exchange.mjs';
|
|
626
|
+
|
|
627
|
+
class MyBroker {
|
|
628
|
+
async onSignalOpenCommit({ symbol, priceOpen, direction }) { await myExchange.openPosition(symbol, direction, priceOpen); }
|
|
629
|
+
async onSignalCloseCommit({ symbol, priceClosed }) { await myExchange.closePosition(symbol, priceClosed); }
|
|
630
|
+
async onPartialProfitCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'sell', quantity: cost / currentPrice }); }
|
|
631
|
+
async onAverageBuyCommit({ symbol, cost, currentPrice }) { await myExchange.createOrder({ symbol, side: 'buy', quantity: cost / currentPrice }); }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
Broker.useBrokerAdapter(MyBroker);
|
|
635
|
+
Broker.enable();
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
| Method | Payload type | Triggered on |
|
|
639
|
+
|--------|--------------|--------------|
|
|
640
|
+
| `onSignalOpenCommit` | `BrokerSignalOpenPayload` | Position activation |
|
|
641
|
+
| `onSignalCloseCommit` | `BrokerSignalClosePayload` | SL / TP / manual close |
|
|
642
|
+
| `onPartialProfitCommit` | `BrokerPartialProfitPayload` | Partial profit |
|
|
643
|
+
| `onPartialLossCommit` | `BrokerPartialLossPayload` | Partial loss |
|
|
644
|
+
| `onTrailingStopCommit` | `BrokerTrailingStopPayload` | SL adjustment |
|
|
645
|
+
| `onTrailingTakeCommit` | `BrokerTrailingTakePayload` | TP adjustment |
|
|
646
|
+
| `onBreakevenCommit` | `BrokerBreakevenPayload` | SL moved to entry |
|
|
647
|
+
| `onAverageBuyCommit` | `BrokerAverageBuyPayload` | DCA entry |
|
|
648
|
+
|
|
649
|
+
All methods are optional; unimplemented hooks are silently skipped. TypeScript: implement `Partial<IBroker>` with typed payloads (`BrokerSignalOpenPayload`, etc.).
|
|
650
|
+
|
|
651
|
+
</details>
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## โ๏ธ Configuration files (`config/*`)
|
|
656
|
+
|
|
657
|
+
Loaded from `{projectRoot}/config/`. The three runtime configs load in order โ **`setup.config` โ `loader.config` โ `alias.config`** โ before any strategy or module code. The UI/Telegram configs resolve **strategy dir โ project root โ package default** (first match wins) and accept `.ts`/`.cjs`/`.mjs`/`.js`.
|
|
658
|
+
|
|
659
|
+
### `setup.config` โ persistence & one-time init
|
|
660
|
+
|
|
661
|
+
Loaded once before any persistence call. **When present, the CLI skips its own default adapter registration** โ your config takes full ownership of the persistence layer.
|
|
662
|
+
|
|
663
|
+
<details>
|
|
664
|
+
<summary>MongoDB + Redis via @backtest-kit/mongo</summary>
|
|
665
|
+
|
|
666
|
+
`setup()` registers all 15 persistence adapters in one call, reading connection params from env (or passed explicitly):
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
// config/setup.config.ts
|
|
670
|
+
import { setup } from '@backtest-kit/mongo';
|
|
671
|
+
setup(); // or setup({ CC_MONGO_CONNECTION_STRING, CC_REDIS_HOST, CC_REDIS_PORT, CC_REDIS_PASSWORD })
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
```env
|
|
675
|
+
CC_MONGO_CONNECTION_STRING=mongodb://localhost:27017/backtest-kit
|
|
676
|
+
CC_REDIS_HOST=127.0.0.1
|
|
677
|
+
CC_REDIS_PORT=6379
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
No strategy-code changes โ adapters are wired transparently before the first persistence call.
|
|
681
|
+
|
|
682
|
+
</details>
|
|
683
|
+
|
|
684
|
+
### `loader.config` โ async startup gate
|
|
685
|
+
|
|
686
|
+
Loaded **after** `setup.config`, **before** strategy/module code. Unlike `setup.config` (side-effect import), it exports a function the CLI `await`s โ use it to wait for an async dependency before the run starts.
|
|
687
|
+
|
|
688
|
+
<details>
|
|
689
|
+
<summary>When to use it, export styles & examples</summary>
|
|
690
|
+
|
|
691
|
+
**Use it to:** wire microfrontends in a monorepo (pre-load sibling packages, hydrate a shared DI container); wait for a DB connection so the backtest fails fast instead of mid-run; warm caches / external APIs (instruments, calendar, fee tables); run schema migrations before signals flow.
|
|
692
|
+
|
|
693
|
+
Exactly one export style โ **never both** (if both present, `default` wins):
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
// config/loader.config.ts โ default export (preferred)
|
|
697
|
+
export default async () => { await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!); await redis.ping(); };
|
|
698
|
+
// โ or named export
|
|
699
|
+
export const loader = async () => { /* โฆ */ };
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
`@backtest-kit/mongo`'s `setup()` registers adapters synchronously but doesn't block on the connection; gate the run on a real connection here. To stitch microfrontends: `import "@my-org/brokers"; import "@my-org/signals";` (the `@my-org` alias is declared in `alias.config`).
|
|
703
|
+
|
|
704
|
+
</details>
|
|
705
|
+
|
|
706
|
+
### `alias.config` โ override any module import
|
|
707
|
+
|
|
708
|
+
Override any Node module import without touching strategy code. Loaded once on the first `import` and applied globally โ e.g. replace a heavy dependency with a stub for backtesting, or swap an external API for a mock in CI.
|
|
709
|
+
|
|
710
|
+
<details>
|
|
711
|
+
<summary>Formats & async factory</summary>
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
// config/alias.config.ts โ named export
|
|
715
|
+
export const ccxt = require("./stubs/ccxt.stub.cjs");
|
|
716
|
+
// config/alias.config.cjs โ default export
|
|
717
|
+
module.exports = { ccxt: require("./stubs/ccxt.stub.cjs") };
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
It may also export an **async factory** the CLI `await`s before strategy code runs โ handy for ESM-only modules that `require()` would throw on:
|
|
721
|
+
|
|
722
|
+
```ts
|
|
723
|
+
// async factory (default export); or `export const loader = async () => ({...})`
|
|
724
|
+
export default async () => ({ nanoid: await import("nanoid"), "p-limit": await import("p-limit") });
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Both styles supported, never both at once (`default` wins). When strategy code calls `require("ccxt")`, the loader checks the alias table first โ no monkey-patching of `node_modules`. Applies to **all** modules in the process (not per-strategy).
|
|
728
|
+
|
|
729
|
+
</details>
|
|
730
|
+
|
|
731
|
+
### `symbol.config` & `notification.config` โ UI dashboard
|
|
732
|
+
|
|
733
|
+
<details>
|
|
734
|
+
<summary>symbol.config โ restrict/reorder the UI symbol list</summary>
|
|
735
|
+
|
|
736
|
+
By default the UI shows all exchange symbols. Override with a `config/symbol.config` (resolution: strategy dir โ project root โ package default):
|
|
737
|
+
|
|
738
|
+
```ts
|
|
739
|
+
// config/symbol.config.ts
|
|
740
|
+
export const symbol_list = [
|
|
741
|
+
{ icon: "/icon/btc.png", logo: "/icon/128/btc.png", symbol: "BTCUSDT", displayName: "Bitcoin", color: "#F7931A", priority: 50, description: "Bitcoin โ the first and most popular cryptocurrency" },
|
|
742
|
+
{ icon: "/icon/eth.png", logo: "/icon/128/eth.png", symbol: "ETHUSDT", displayName: "Ethereum", color: "#6F42C1", priority: 50, description: "Ethereum โ a blockchain platform for smart contracts" },
|
|
743
|
+
];
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
</details>
|
|
747
|
+
|
|
748
|
+
<details>
|
|
749
|
+
<summary>notification.config โ which notification categories the UI shows</summary>
|
|
750
|
+
|
|
751
|
+
Defaults (override per strategy):
|
|
752
|
+
|
|
753
|
+
| Key | Default | Description |
|
|
754
|
+
|-----|---------|-------------|
|
|
755
|
+
| `signal` | `true` | Signal lifecycle: opened, scheduled, closed, cancelled |
|
|
756
|
+
| `risk` | `true` | Risk manager rejections |
|
|
757
|
+
| `info` | `true` | Informational messages on an active signal |
|
|
758
|
+
| `breakeven` | `true` | Breakeven level reached |
|
|
759
|
+
| `common_error` | `true` | Non-fatal runtime errors |
|
|
760
|
+
| `critical_error` | `true` | Fatal errors that terminate the session |
|
|
761
|
+
| `validation_error` | `true` | Config / input validation errors |
|
|
762
|
+
| `strategy_commit` | `true` | All committed actions (partial close, DCA, trailing, โฆ) |
|
|
763
|
+
| `partial_loss` | `false` | Partial loss level reached (before commit) |
|
|
764
|
+
| `partial_profit` | `false` | Partial profit level reached (before commit) |
|
|
765
|
+
| `signal_sync` | `false` | Live order fill / exit confirmations from exchange sync |
|
|
766
|
+
|
|
767
|
+
```js
|
|
768
|
+
// config/notification.config.ts
|
|
769
|
+
export default { signal: true, risk: true, info: true, breakeven: true, common_error: true, critical_error: true, validation_error: true, strategy_commit: true, partial_loss: false, partial_profit: false, signal_sync: false };
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
</details>
|
|
773
|
+
|
|
774
|
+
### `telegram.config` โ programmatic message rendering
|
|
775
|
+
|
|
776
|
+
<details>
|
|
777
|
+
<summary>Override Mustache rendering with get*Markdown methods</summary>
|
|
778
|
+
|
|
779
|
+
By default messages render from Mustache templates (`template/*.mustache`). Export an object with any subset of `get*Markdown` methods (each gets the event payload, returns `Promise<string>`); unimplemented ones fall back to the template.
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
// config/telegram.config.ts
|
|
783
|
+
import { IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract } from "backtest-kit";
|
|
784
|
+
export default {
|
|
785
|
+
async getOpenedMarkdown(e: IStrategyTickResultOpened) { return `**Opened** ${e.symbol} at ${e.priceOpen}`; },
|
|
786
|
+
async getClosedMarkdown(e: IStrategyTickResultClosed) { return `**Closed** ${e.symbol} at ${e.priceClosed}`; },
|
|
787
|
+
async getRiskMarkdown(e: RiskContract) { return `**Risk rejected** ${e.symbol}`; },
|
|
788
|
+
};
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
Methods โ event types: `getOpenedMarkdown`/`getClosedMarkdown`/`getScheduledMarkdown`/`getCancelledMarkdown` (`IStrategyTickResult*`), `getRiskMarkdown` (`RiskContract`), `getPartialProfitMarkdown`/`getPartialLossMarkdown`/`getBreakevenMarkdown`/`getTrailingTakeMarkdown`/`getTrailingStopMarkdown`/`getAverageBuyMarkdown` (the matching `*Commit`), `getSignalOpenMarkdown`/`getSignalCloseMarkdown` (`SignalOpen/CloseContract`), `getCancelScheduledMarkdown`/`getClosePendingMarkdown` (`*Commit`), `getSignalInfoMarkdown` (`SignalInfoContract`).
|
|
792
|
+
|
|
793
|
+
</details>
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
## ๐ Integrations
|
|
798
|
+
|
|
799
|
+
### Web dashboard (`--ui`)
|
|
800
|
+
|
|
801
|
+
Starts the `@backtest-kit/ui` server at `http://localhost:60050` (host/port via `CC_WWWROOT_HOST` / `CC_WWWROOT_PORT`). Restrict the symbol list with `symbol.config` and notification categories with `notification.config` (above).
|
|
802
|
+
|
|
803
|
+
### Telegram (`--telegram`)
|
|
804
|
+
|
|
805
|
+
Sends formatted HTML messages with 1m / 15m / 1h price charts for every position event โ opened, closed, scheduled, cancelled, risk rejection, partial profit/loss, trailing stop/take, breakeven. Requires `CC_TELEGRAM_TOKEN` and `CC_TELEGRAM_CHANNEL`. Customize per-event rendering with `telegram.config` (above).
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## ๐งช Programmatic API โ `run(mode, args)`
|
|
810
|
+
|
|
811
|
+
Use the CLI as a library โ call `run()` from your own script, no child process or flag parsing.
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
import { run } from '@backtest-kit/cli';
|
|
815
|
+
|
|
816
|
+
await run('backtest', { entryPoint: './src/index.mjs', symbol: 'ETHUSDT', frame: 'feb-2024', cacheInterval: ['1m','15m','1h'], verbose: true });
|
|
817
|
+
await run('paper', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT' });
|
|
818
|
+
await run('live', { entryPoint: './src/index.mjs', symbol: 'BTCUSDT', verbose: true });
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
<details>
|
|
822
|
+
<summary>Payload fields (call once per process)</summary>
|
|
823
|
+
|
|
824
|
+
`run()` can be called **only once per process** โ a second call throws `"Should be called only once"`. `mode`: `"backtest" | "paper" | "live"`.
|
|
825
|
+
|
|
826
|
+
**Backtest:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `frame` (first registered), `cacheInterval` (`["1m","15m","30m","1h","4h"]`), `noCache` (`false`), `noFlush` (`false`), `verbose` (`false`).
|
|
827
|
+
|
|
828
|
+
**Paper / Live:** `entryPoint`, `symbol` (`"BTCUSDT"`), `strategy` (first registered), `exchange` (first registered), `verbose` (`false`).
|
|
829
|
+
|
|
830
|
+
</details>
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## ๐ Environment variables
|
|
835
|
+
|
|
836
|
+
```env
|
|
837
|
+
CC_TELEGRAM_TOKEN=your_bot_token_here # required for --telegram (from @BotFather)
|
|
838
|
+
CC_TELEGRAM_CHANNEL=-100123456789 # required for --telegram (channel/chat ID)
|
|
839
|
+
CC_WWWROOT_HOST=0.0.0.0 # UI bind address (default 0.0.0.0)
|
|
840
|
+
CC_WWWROOT_PORT=60050 # UI port (default 60050)
|
|
841
|
+
CC_QUICKCHART_HOST= # optional self-hosted QuickChart URL
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
<details>
|
|
845
|
+
<summary>Default behaviors (when a schema isn't registered)</summary>
|
|
846
|
+
|
|
847
|
+
| Component | Default | Note |
|
|
848
|
+
|-----------|---------|------|
|
|
849
|
+
| **Exchange** | CCXT Binance (`default_exchange`) | warns; **does not support order book in backtest** โ register a custom exchange with snapshot storage if your strategy calls `getOrderBook()` in backtest |
|
|
850
|
+
| **Frame** | February 2024 (`default_frame`) | warns |
|
|
851
|
+
| **Symbol** | `BTCUSDT` | โ |
|
|
852
|
+
| **Cache intervals** | `1m, 15m, 30m, 4h` | used if `--cacheInterval` not given; skip with `--noCache` |
|
|
853
|
+
|
|
854
|
+
</details>
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
## ๐ก Why @backtest-kit/cli
|
|
859
|
+
|
|
860
|
+
Instead of writing infrastructure for every project โ manual logger/storage/notification setup, CLI arg parsing, exchange registration, cache warming, Telegram bot, SIGINT handling, run wiring โ the whole thing is one script:
|
|
861
|
+
|
|
862
|
+
```json
|
|
863
|
+
{ "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Zero to running backtest in seconds ยท automatic candle-cache warming with retry ยท production web dashboard out of the box ยท Telegram alerts with charts (no chart code) ยท graceful SIGINT shutdown (no hanging processes) ยท pluggable logger โ override the built-in one with `setLogger()` from your strategy module ยท works with any `backtest-kit` strategy as-is ยท broker hooks via side-effect modules (no CLI internals to touch).
|
|
867
|
+
|
|
868
|
+
## ๐ค Contribute
|
|
869
|
+
|
|
870
|
+
Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
871
|
+
|
|
872
|
+
## ๐ License
|
|
873
|
+
|
|
874
|
+
MIT ยฉ [tripolskypetr](https://github.com/tripolskypetr)
|