@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 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
- | `--json` | string | Write plots as a JSON array to a file (e.g. `--json=./output.json`) |
454
- | `--jsonl` | string | Write plots as JSONL (one row per line) to a file (e.g. `--jsonl=./output.jsonl`) |
455
- | `--markdown` | string | Write Markdown table to a file (e.g. `--markdown=./output.md`) |
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
- │ ├── master_trend_15m.pine ← indicator
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
- │ └── master_trend_15m.pine
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/master_trend_15m.pine \
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/master_trend_15m.pine --symbol BTCUSDT --timeframe 15m --limit 180"
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
- Redirect to a file to save the report:
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
- npm run pine > report.md
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
- json: {
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: "string",
528
- default: "",
535
+ type: "boolean",
536
+ default: false,
529
537
  },
530
538
  markdown: {
531
- type: "string",
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$5 = async () => {
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$5();
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$4 = async () => {
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$4();
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$3 = async () => {
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$3();
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$2 = async () => {
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$2();
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$1 = async () => {
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$1();
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 jsonPath = values.json;
2384
- if (jsonPath) {
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
- await fs$1.writeFile(jsonPath, JSON.stringify(rows, null, 2), "utf-8");
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
- const jsonlPath = values.jsonl;
2390
- if (jsonlPath) {
2401
+ if (values.jsonl) {
2391
2402
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2392
- await fs$1.writeFile(jsonlPath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
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
- const markdownPath = values.markdown;
2396
- if (markdownPath) {
2397
- await fs$1.writeFile(markdownPath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
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
- json: {
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: "string",
503
- default: "",
510
+ type: "boolean",
511
+ default: false,
504
512
  },
505
513
  markdown: {
506
- type: "string",
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$5 = async () => {
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$5();
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$4 = async () => {
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$4();
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$3 = async () => {
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$3();
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$2 = async () => {
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$2();
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$1 = async () => {
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$1();
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 jsonPath = values.json;
2355
- if (jsonPath) {
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
- await writeFile(jsonPath, JSON.stringify(rows, null, 2), "utf-8");
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
- const jsonlPath = values.jsonl;
2361
- if (jsonlPath) {
2372
+ if (values.jsonl) {
2362
2373
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2363
- await writeFile(jsonlPath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
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
- const markdownPath = values.markdown;
2367
- if (markdownPath) {
2368
- await writeFile(markdownPath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
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.0",
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",