@backtest-kit/cli 5.10.0 → 5.10.2
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 +115 -9
- package/build/index.cjs +88 -25
- package/build/index.mjs +91 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -450,9 +450,10 @@ For projects that compile to or use CommonJS. Loaded via `require()`:
|
|
|
450
450
|
| `--limit` | string | Number of candles to fetch (default: `250`) |
|
|
451
451
|
| `--when` | string | End date for candle window — ISO 8601 or Unix ms (default: now) |
|
|
452
452
|
| `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
|
|
453
|
-
| `--
|
|
454
|
-
| `--
|
|
455
|
-
| `--
|
|
453
|
+
| `--output` | string | Output file base name without extension (default: `.pine` file name) |
|
|
454
|
+
| `--json` | boolean | Write plots as a JSON array to `<pine-dir>/dump/{output}.json` |
|
|
455
|
+
| `--jsonl` | boolean | Write plots as JSONL (one row per line) to `<pine-dir>/dump/{output}.jsonl` |
|
|
456
|
+
| `--markdown` | boolean | Write Markdown table to `<pine-dir>/dump/{output}.md` |
|
|
456
457
|
|
|
457
458
|
**Important:** `limit` must cover indicator warmup bars — rows before warmup completes will show `N/A`
|
|
458
459
|
|
|
@@ -470,7 +471,7 @@ The CLI looks for `modules/pine.module` in two locations (first match wins):
|
|
|
470
471
|
```
|
|
471
472
|
my-project/
|
|
472
473
|
├── math/
|
|
473
|
-
│ ├──
|
|
474
|
+
│ ├── impulse_trend_15m.pine ← indicator
|
|
474
475
|
│ └── modules/
|
|
475
476
|
│ └── pine.module.ts ← loaded first (next to .pine file)
|
|
476
477
|
├── modules/
|
|
@@ -507,7 +508,7 @@ Before loading `pine.module`, the CLI loads `.env` files in the same order as fo
|
|
|
507
508
|
my-project/
|
|
508
509
|
├── math/
|
|
509
510
|
│ ├── .env ← loaded second (overrides root)
|
|
510
|
-
│ └──
|
|
511
|
+
│ └── impulse_trend_15m.pine
|
|
511
512
|
├── .env ← loaded first
|
|
512
513
|
└── package.json
|
|
513
514
|
```
|
|
@@ -538,7 +539,7 @@ addExchangeSchema({
|
|
|
538
539
|
Then run:
|
|
539
540
|
|
|
540
541
|
```bash
|
|
541
|
-
npx @backtest-kit/cli --pine ./math/
|
|
542
|
+
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine \
|
|
542
543
|
--exchange my-exchange \
|
|
543
544
|
--symbol BTCUSDT \
|
|
544
545
|
--timeframe 15m \
|
|
@@ -551,7 +552,7 @@ Or add it to `package.json`:
|
|
|
551
552
|
```json
|
|
552
553
|
{
|
|
553
554
|
"scripts": {
|
|
554
|
-
"pine": "npx @backtest-kit/cli --pine ./math/
|
|
555
|
+
"pine": "npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180"
|
|
555
556
|
}
|
|
556
557
|
}
|
|
557
558
|
```
|
|
@@ -593,10 +594,115 @@ The CLI prints a Markdown table to stdout:
|
|
|
593
594
|
| 112653.90 | 1.0000 | 2025-09-22T22:15:00.000Z |
|
|
594
595
|
```
|
|
595
596
|
|
|
596
|
-
|
|
597
|
+
Save to `./math/dump/impulse_trend_15m.md` (uses `.pine` file name automatically, dump is created next to the `.pine` file):
|
|
597
598
|
|
|
598
599
|
```bash
|
|
599
|
-
|
|
600
|
+
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --markdown
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Override the output name with `--output`:
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine --jsonl --output feb2026_bb
|
|
607
|
+
# → ./math/dump/feb2026_bb.jsonl
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Print to stdout (no flag):
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## 💾 Dumping Raw Candles
|
|
617
|
+
|
|
618
|
+
`@backtest-kit/cli` can fetch raw OHLCV candles from any registered exchange and save them to a file — no strategy file required.
|
|
619
|
+
|
|
620
|
+
### CLI Flags
|
|
621
|
+
|
|
622
|
+
| Flag | Type | Description |
|
|
623
|
+
|------|------|-------------|
|
|
624
|
+
| `--dump` | boolean | Enable candle dump mode |
|
|
625
|
+
| `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
|
|
626
|
+
| `--timeframe` | string | Candle interval (default: `"15m"`) |
|
|
627
|
+
| `--limit` | string | Number of candles to fetch (default: `250`) |
|
|
628
|
+
| `--when` | string | End date for candle window — ISO 8601 or Unix ms (default: now) |
|
|
629
|
+
| `--exchange` | string | Exchange name (default: first registered, falls back to CCXT Binance) |
|
|
630
|
+
| `--output` | string | Output file base name without extension (default: `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}`) |
|
|
631
|
+
| `--json` | boolean | Write candles as a JSON array to `./dump/{output}.json` |
|
|
632
|
+
| `--jsonl` | boolean | Write candles as JSONL (one row per line) to `./dump/{output}.jsonl` |
|
|
633
|
+
|
|
634
|
+
The `dump/` directory is created in the current working directory (where the CLI is invoked from).
|
|
635
|
+
|
|
636
|
+
### Exchange via `dump.module`
|
|
637
|
+
|
|
638
|
+
By default the CLI registers CCXT Binance automatically. To use a different exchange — or to configure API keys, custom rate limits, or a non-spot market — create a `modules/dump.module.ts` file. The CLI loads it automatically before fetching candles.
|
|
639
|
+
|
|
640
|
+
The CLI looks for `modules/dump.module` in the current working directory
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
my-project/
|
|
644
|
+
├── modules/
|
|
645
|
+
│ └── dump.module.ts ← exchange registration
|
|
646
|
+
├── dump/ ← auto-created: candle output files
|
|
647
|
+
└── package.json
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Inside `dump.module.ts` call `addExchangeSchema` from `backtest-kit`:
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
// modules/dump.module.ts
|
|
654
|
+
import { addExchangeSchema } from "backtest-kit";
|
|
655
|
+
import ccxt from "ccxt";
|
|
656
|
+
|
|
657
|
+
addExchangeSchema({
|
|
658
|
+
exchangeName: "my-exchange",
|
|
659
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
660
|
+
const exchange = new ccxt.bybit({ enableRateLimit: true });
|
|
661
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
662
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
|
|
663
|
+
timestamp, open, high, low, close, volume,
|
|
664
|
+
}));
|
|
665
|
+
},
|
|
666
|
+
formatPrice: (symbol, price) => price.toFixed(2),
|
|
667
|
+
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
|
|
668
|
+
});
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Output
|
|
672
|
+
|
|
673
|
+
Each candle row contains OHLCV fields. Print to stdout:
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 100
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Save to `./dump/BTCUSDT_100_15m_{timestamp}.jsonl`:
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 100 --jsonl
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Fetch candles up to a specific date with `--when` and override the file name with `--output`:
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 \
|
|
689
|
+
--when "2026-02-28T00:00:00.000Z" \
|
|
690
|
+
--jsonl --output feb2026_btc
|
|
691
|
+
# → ./dump/feb2026_btc.jsonl
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Or add it to `package.json`:
|
|
695
|
+
|
|
696
|
+
```json
|
|
697
|
+
{
|
|
698
|
+
"scripts": {
|
|
699
|
+
"dump": "npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl"
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
600
706
|
```
|
|
601
707
|
|
|
602
708
|
## 🌍 Environment Variables
|
package/build/index.cjs
CHANGED
|
@@ -507,6 +507,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
507
507
|
type: "boolean",
|
|
508
508
|
default: false,
|
|
509
509
|
},
|
|
510
|
+
dump: {
|
|
511
|
+
type: "boolean",
|
|
512
|
+
default: false,
|
|
513
|
+
},
|
|
510
514
|
timeframe: {
|
|
511
515
|
type: "string",
|
|
512
516
|
default: "",
|
|
@@ -519,17 +523,21 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
519
523
|
type: "string",
|
|
520
524
|
default: "",
|
|
521
525
|
},
|
|
522
|
-
|
|
526
|
+
output: {
|
|
523
527
|
type: "string",
|
|
524
528
|
default: "",
|
|
525
529
|
},
|
|
530
|
+
json: {
|
|
531
|
+
type: "boolean",
|
|
532
|
+
default: false,
|
|
533
|
+
},
|
|
526
534
|
jsonl: {
|
|
527
|
-
type: "
|
|
528
|
-
default:
|
|
535
|
+
type: "boolean",
|
|
536
|
+
default: false,
|
|
529
537
|
},
|
|
530
538
|
markdown: {
|
|
531
|
-
type: "
|
|
532
|
-
default:
|
|
539
|
+
type: "boolean",
|
|
540
|
+
default: false,
|
|
533
541
|
},
|
|
534
542
|
},
|
|
535
543
|
strict: false,
|
|
@@ -2198,7 +2206,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
2198
2206
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
2199
2207
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2200
2208
|
});
|
|
2201
|
-
const main$
|
|
2209
|
+
const main$6 = async () => {
|
|
2202
2210
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2203
2211
|
return;
|
|
2204
2212
|
}
|
|
@@ -2209,7 +2217,7 @@ const main$5 = async () => {
|
|
|
2209
2217
|
await cli.backtestMainService.connect();
|
|
2210
2218
|
listenGracefulShutdown$4();
|
|
2211
2219
|
};
|
|
2212
|
-
main$
|
|
2220
|
+
main$6();
|
|
2213
2221
|
|
|
2214
2222
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
2215
2223
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2230,7 +2238,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
2230
2238
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
2231
2239
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2232
2240
|
});
|
|
2233
|
-
const main$
|
|
2241
|
+
const main$5 = async () => {
|
|
2234
2242
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2235
2243
|
return;
|
|
2236
2244
|
}
|
|
@@ -2241,7 +2249,7 @@ const main$4 = async () => {
|
|
|
2241
2249
|
cli.paperMainService.connect();
|
|
2242
2250
|
listenGracefulShutdown$3();
|
|
2243
2251
|
};
|
|
2244
|
-
main$
|
|
2252
|
+
main$5();
|
|
2245
2253
|
|
|
2246
2254
|
const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
2247
2255
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2262,7 +2270,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
2262
2270
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
2263
2271
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2264
2272
|
});
|
|
2265
|
-
const main$
|
|
2273
|
+
const main$4 = async () => {
|
|
2266
2274
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2267
2275
|
return;
|
|
2268
2276
|
}
|
|
@@ -2273,7 +2281,7 @@ const main$3 = async () => {
|
|
|
2273
2281
|
await cli.liveMainService.connect();
|
|
2274
2282
|
listenGracefulShutdown$2();
|
|
2275
2283
|
};
|
|
2276
|
-
main$
|
|
2284
|
+
main$4();
|
|
2277
2285
|
|
|
2278
2286
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
2279
2287
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2283,7 +2291,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
2283
2291
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
2284
2292
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2285
2293
|
});
|
|
2286
|
-
const main$
|
|
2294
|
+
const main$3 = async () => {
|
|
2287
2295
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2288
2296
|
return;
|
|
2289
2297
|
}
|
|
@@ -2293,7 +2301,7 @@ const main$2 = async () => {
|
|
|
2293
2301
|
}
|
|
2294
2302
|
listenGracefulShutdown$1();
|
|
2295
2303
|
};
|
|
2296
|
-
main$
|
|
2304
|
+
main$3();
|
|
2297
2305
|
|
|
2298
2306
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
2299
2307
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2303,7 +2311,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
2303
2311
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
2304
2312
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2305
2313
|
});
|
|
2306
|
-
const main$
|
|
2314
|
+
const main$2 = async () => {
|
|
2307
2315
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2308
2316
|
return;
|
|
2309
2317
|
}
|
|
@@ -2313,7 +2321,7 @@ const main$1 = async () => {
|
|
|
2313
2321
|
}
|
|
2314
2322
|
listenGracefulShutdown();
|
|
2315
2323
|
};
|
|
2316
|
-
main$
|
|
2324
|
+
main$2();
|
|
2317
2325
|
|
|
2318
2326
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2319
2327
|
const keys = Object.keys(schema);
|
|
@@ -2335,7 +2343,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2335
2343
|
}
|
|
2336
2344
|
return rows;
|
|
2337
2345
|
};
|
|
2338
|
-
const main = async () => {
|
|
2346
|
+
const main$1 = async () => {
|
|
2339
2347
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2340
2348
|
return;
|
|
2341
2349
|
}
|
|
@@ -2380,25 +2388,80 @@ const main = async () => {
|
|
|
2380
2388
|
return true;
|
|
2381
2389
|
}))
|
|
2382
2390
|
.map((key) => [key, key]));
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2391
|
+
const dumpName = values.output || path.basename(entryPoint, path.extname(entryPoint));
|
|
2392
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
2393
|
+
if (values.json) {
|
|
2385
2394
|
const rows = EXTRACT_ROWS_FN(plots, signalSchema);
|
|
2386
|
-
|
|
2395
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
2396
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
2397
|
+
await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
2398
|
+
console.log(`Saved: ${filePath}`);
|
|
2387
2399
|
return;
|
|
2388
2400
|
}
|
|
2389
|
-
|
|
2390
|
-
if (jsonlPath) {
|
|
2401
|
+
if (values.jsonl) {
|
|
2391
2402
|
const rows = EXTRACT_ROWS_FN(plots, signalSchema);
|
|
2392
|
-
|
|
2403
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
|
|
2404
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
2405
|
+
await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
2406
|
+
console.log(`Saved: ${filePath}`);
|
|
2393
2407
|
return;
|
|
2394
2408
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
await fs$1.
|
|
2409
|
+
if (values.markdown) {
|
|
2410
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
2411
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
2412
|
+
await fs$1.writeFile(filePath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
|
|
2413
|
+
console.log(`Saved: ${filePath}`);
|
|
2398
2414
|
return;
|
|
2399
2415
|
}
|
|
2400
2416
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
2401
2417
|
};
|
|
2418
|
+
main$1();
|
|
2419
|
+
|
|
2420
|
+
const main = async () => {
|
|
2421
|
+
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const { values } = getArgs();
|
|
2425
|
+
if (!values.dump) {
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
await cli.moduleConnectionService.loadModule("./dump.module");
|
|
2429
|
+
{
|
|
2430
|
+
await cli.exchangeSchemaService.addSchema();
|
|
2431
|
+
await cli.symbolSchemaService.addSchema();
|
|
2432
|
+
}
|
|
2433
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
2434
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
2435
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
2436
|
+
const timeframe = values.timeframe || "15m";
|
|
2437
|
+
const limitStr = values.limit || "250";
|
|
2438
|
+
const limitNum = parseInt(limitStr);
|
|
2439
|
+
const limit = isNaN(limitNum) ? 250 : limitNum;
|
|
2440
|
+
const whenStr = values.when || Date.now().toString();
|
|
2441
|
+
const whenStamp = Date.parse(whenStr);
|
|
2442
|
+
const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
|
|
2443
|
+
const timestamp = BacktestKit.alignToInterval(when, timeframe).getTime();
|
|
2444
|
+
const candles = await BacktestKit.Exchange.getRawCandles(symbol, timeframe, {
|
|
2445
|
+
exchangeName,
|
|
2446
|
+
}, limit, undefined, timestamp);
|
|
2447
|
+
const dumpName = values.output || `${symbol}_${limit}_${timeframe}_${timestamp}`;
|
|
2448
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
2449
|
+
if (values.json) {
|
|
2450
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
2451
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
2452
|
+
await fs$1.writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
|
|
2453
|
+
console.log(`Saved: ${filePath}`);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (values.jsonl) {
|
|
2457
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
|
|
2458
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
2459
|
+
await fs$1.writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
2460
|
+
console.log(`Saved: ${filePath}`);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
console.log(JSON.stringify(candles, null, 2));
|
|
2464
|
+
};
|
|
2402
2465
|
main();
|
|
2403
2466
|
|
|
2404
2467
|
function setLogger(logger) {
|
package/build/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { Storage, Notification, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync } from 'backtest-kit';
|
|
3
|
+
import { Storage, Notification, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, alignToInterval, Exchange } from 'backtest-kit';
|
|
4
4
|
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString, createAwaiter, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize } from 'functools-kit';
|
|
5
5
|
import fs, { constants } from 'fs';
|
|
6
6
|
import * as stackTrace from 'stack-trace';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import fs$1, { access, readFile, writeFile } from 'fs/promises';
|
|
7
|
+
import path, { basename, extname, join, resolve } from 'path';
|
|
8
|
+
import fs$1, { access, readFile, mkdir, writeFile } from 'fs/promises';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import { createActivator } from 'di-kit';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
@@ -482,6 +482,10 @@ const getArgs = singleshot(() => {
|
|
|
482
482
|
type: "boolean",
|
|
483
483
|
default: false,
|
|
484
484
|
},
|
|
485
|
+
dump: {
|
|
486
|
+
type: "boolean",
|
|
487
|
+
default: false,
|
|
488
|
+
},
|
|
485
489
|
timeframe: {
|
|
486
490
|
type: "string",
|
|
487
491
|
default: "",
|
|
@@ -494,17 +498,21 @@ const getArgs = singleshot(() => {
|
|
|
494
498
|
type: "string",
|
|
495
499
|
default: "",
|
|
496
500
|
},
|
|
497
|
-
|
|
501
|
+
output: {
|
|
498
502
|
type: "string",
|
|
499
503
|
default: "",
|
|
500
504
|
},
|
|
505
|
+
json: {
|
|
506
|
+
type: "boolean",
|
|
507
|
+
default: false,
|
|
508
|
+
},
|
|
501
509
|
jsonl: {
|
|
502
|
-
type: "
|
|
503
|
-
default:
|
|
510
|
+
type: "boolean",
|
|
511
|
+
default: false,
|
|
504
512
|
},
|
|
505
513
|
markdown: {
|
|
506
|
-
type: "
|
|
507
|
-
default:
|
|
514
|
+
type: "boolean",
|
|
515
|
+
default: false,
|
|
508
516
|
},
|
|
509
517
|
},
|
|
510
518
|
strict: false,
|
|
@@ -2169,7 +2177,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
2169
2177
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
2170
2178
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2171
2179
|
});
|
|
2172
|
-
const main$
|
|
2180
|
+
const main$6 = async () => {
|
|
2173
2181
|
if (!getEntry(import.meta.url)) {
|
|
2174
2182
|
return;
|
|
2175
2183
|
}
|
|
@@ -2180,7 +2188,7 @@ const main$5 = async () => {
|
|
|
2180
2188
|
await cli.backtestMainService.connect();
|
|
2181
2189
|
listenGracefulShutdown$4();
|
|
2182
2190
|
};
|
|
2183
|
-
main$
|
|
2191
|
+
main$6();
|
|
2184
2192
|
|
|
2185
2193
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
2186
2194
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2201,7 +2209,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
2201
2209
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
2202
2210
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2203
2211
|
});
|
|
2204
|
-
const main$
|
|
2212
|
+
const main$5 = async () => {
|
|
2205
2213
|
if (!getEntry(import.meta.url)) {
|
|
2206
2214
|
return;
|
|
2207
2215
|
}
|
|
@@ -2212,7 +2220,7 @@ const main$4 = async () => {
|
|
|
2212
2220
|
cli.paperMainService.connect();
|
|
2213
2221
|
listenGracefulShutdown$3();
|
|
2214
2222
|
};
|
|
2215
|
-
main$
|
|
2223
|
+
main$5();
|
|
2216
2224
|
|
|
2217
2225
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
2218
2226
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2233,7 +2241,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
2233
2241
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
2234
2242
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2235
2243
|
});
|
|
2236
|
-
const main$
|
|
2244
|
+
const main$4 = async () => {
|
|
2237
2245
|
if (!getEntry(import.meta.url)) {
|
|
2238
2246
|
return;
|
|
2239
2247
|
}
|
|
@@ -2244,7 +2252,7 @@ const main$3 = async () => {
|
|
|
2244
2252
|
await cli.liveMainService.connect();
|
|
2245
2253
|
listenGracefulShutdown$2();
|
|
2246
2254
|
};
|
|
2247
|
-
main$
|
|
2255
|
+
main$4();
|
|
2248
2256
|
|
|
2249
2257
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
2250
2258
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2254,7 +2262,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
2254
2262
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
2255
2263
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2256
2264
|
});
|
|
2257
|
-
const main$
|
|
2265
|
+
const main$3 = async () => {
|
|
2258
2266
|
if (!getEntry(import.meta.url)) {
|
|
2259
2267
|
return;
|
|
2260
2268
|
}
|
|
@@ -2264,7 +2272,7 @@ const main$2 = async () => {
|
|
|
2264
2272
|
}
|
|
2265
2273
|
listenGracefulShutdown$1();
|
|
2266
2274
|
};
|
|
2267
|
-
main$
|
|
2275
|
+
main$3();
|
|
2268
2276
|
|
|
2269
2277
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
2270
2278
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2274,7 +2282,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
2274
2282
|
const listenGracefulShutdown = singleshot(() => {
|
|
2275
2283
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2276
2284
|
});
|
|
2277
|
-
const main$
|
|
2285
|
+
const main$2 = async () => {
|
|
2278
2286
|
if (!getEntry(import.meta.url)) {
|
|
2279
2287
|
return;
|
|
2280
2288
|
}
|
|
@@ -2284,7 +2292,7 @@ const main$1 = async () => {
|
|
|
2284
2292
|
}
|
|
2285
2293
|
listenGracefulShutdown();
|
|
2286
2294
|
};
|
|
2287
|
-
main$
|
|
2295
|
+
main$2();
|
|
2288
2296
|
|
|
2289
2297
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2290
2298
|
const keys = Object.keys(schema);
|
|
@@ -2306,7 +2314,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2306
2314
|
}
|
|
2307
2315
|
return rows;
|
|
2308
2316
|
};
|
|
2309
|
-
const main = async () => {
|
|
2317
|
+
const main$1 = async () => {
|
|
2310
2318
|
if (!getEntry(import.meta.url)) {
|
|
2311
2319
|
return;
|
|
2312
2320
|
}
|
|
@@ -2351,25 +2359,80 @@ const main = async () => {
|
|
|
2351
2359
|
return true;
|
|
2352
2360
|
}))
|
|
2353
2361
|
.map((key) => [key, key]));
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2362
|
+
const dumpName = values.output || basename(entryPoint, extname(entryPoint));
|
|
2363
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
2364
|
+
if (values.json) {
|
|
2356
2365
|
const rows = EXTRACT_ROWS_FN(plots, signalSchema);
|
|
2357
|
-
|
|
2366
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
2367
|
+
await mkdir(dumpDir, { recursive: true });
|
|
2368
|
+
await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
2369
|
+
console.log(`Saved: ${filePath}`);
|
|
2358
2370
|
return;
|
|
2359
2371
|
}
|
|
2360
|
-
|
|
2361
|
-
if (jsonlPath) {
|
|
2372
|
+
if (values.jsonl) {
|
|
2362
2373
|
const rows = EXTRACT_ROWS_FN(plots, signalSchema);
|
|
2363
|
-
|
|
2374
|
+
const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
|
|
2375
|
+
await mkdir(dumpDir, { recursive: true });
|
|
2376
|
+
await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
2377
|
+
console.log(`Saved: ${filePath}`);
|
|
2364
2378
|
return;
|
|
2365
2379
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
await
|
|
2380
|
+
if (values.markdown) {
|
|
2381
|
+
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
2382
|
+
await mkdir(dumpDir, { recursive: true });
|
|
2383
|
+
await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
|
|
2384
|
+
console.log(`Saved: ${filePath}`);
|
|
2369
2385
|
return;
|
|
2370
2386
|
}
|
|
2371
2387
|
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
2372
2388
|
};
|
|
2389
|
+
main$1();
|
|
2390
|
+
|
|
2391
|
+
const main = async () => {
|
|
2392
|
+
if (!getEntry(import.meta.url)) {
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
const { values } = getArgs();
|
|
2396
|
+
if (!values.dump) {
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
await cli.moduleConnectionService.loadModule("./dump.module");
|
|
2400
|
+
{
|
|
2401
|
+
await cli.exchangeSchemaService.addSchema();
|
|
2402
|
+
await cli.symbolSchemaService.addSchema();
|
|
2403
|
+
}
|
|
2404
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
2405
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
2406
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
2407
|
+
const timeframe = values.timeframe || "15m";
|
|
2408
|
+
const limitStr = values.limit || "250";
|
|
2409
|
+
const limitNum = parseInt(limitStr);
|
|
2410
|
+
const limit = isNaN(limitNum) ? 250 : limitNum;
|
|
2411
|
+
const whenStr = values.when || Date.now().toString();
|
|
2412
|
+
const whenStamp = Date.parse(whenStr);
|
|
2413
|
+
const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
|
|
2414
|
+
const timestamp = alignToInterval(when, timeframe).getTime();
|
|
2415
|
+
const candles = await Exchange.getRawCandles(symbol, timeframe, {
|
|
2416
|
+
exchangeName,
|
|
2417
|
+
}, limit, undefined, timestamp);
|
|
2418
|
+
const dumpName = values.output || `${symbol}_${limit}_${timeframe}_${timestamp}`;
|
|
2419
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
2420
|
+
if (values.json) {
|
|
2421
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
2422
|
+
await mkdir(dumpDir, { recursive: true });
|
|
2423
|
+
await writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
|
|
2424
|
+
console.log(`Saved: ${filePath}`);
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
if (values.jsonl) {
|
|
2428
|
+
const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
|
|
2429
|
+
await mkdir(dumpDir, { recursive: true });
|
|
2430
|
+
await writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
|
|
2431
|
+
console.log(`Saved: ${filePath}`);
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
console.log(JSON.stringify(candles, null, 2));
|
|
2435
|
+
};
|
|
2373
2436
|
main();
|
|
2374
2437
|
|
|
2375
2438
|
function setLogger(logger) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.2",
|
|
4
4
|
"description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications — no setup code required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|