@backtest-kit/cli 5.10.1 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -613,6 +613,98 @@ Print to stdout (no flag):
613
613
  npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine
614
614
  ```
615
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
706
+ ```
707
+
616
708
  ## 🌍 Environment Variables
617
709
 
618
710
  Create a `.env` file in your project root:
package/build/index.cjs CHANGED
@@ -118,6 +118,7 @@ BacktestKit.setConfig({
118
118
  BacktestKit.setConfig({
119
119
  CC_ENABLE_DCA_EVERYWHERE: true,
120
120
  CC_ENABLE_PPPL_EVERYWHERE: true,
121
+ CC_ENABLE_TRAILING_EVERYWHERE: true,
121
122
  });
122
123
  BacktestKit.setConfig({
123
124
  CC_MAX_SIGNAL_GENERATION_SECONDS: 15 * 60,
@@ -445,6 +446,14 @@ var FrameName;
445
446
  })(FrameName || (FrameName = {}));
446
447
  var FrameName$1 = FrameName;
447
448
 
449
+ const ALLOWED_EXTENSIONS = [
450
+ `.cjs`,
451
+ `.mjs`,
452
+ `.ts`,
453
+ `.tsx`,
454
+ `.js`,
455
+ `.pine`,
456
+ ];
448
457
  const getArgs = functoolsKit.singleshot(() => {
449
458
  const { values, positionals } = util.parseArgs({
450
459
  args: process.argv,
@@ -507,6 +516,10 @@ const getArgs = functoolsKit.singleshot(() => {
507
516
  type: "boolean",
508
517
  default: false,
509
518
  },
519
+ dump: {
520
+ type: "boolean",
521
+ default: false,
522
+ },
510
523
  timeframe: {
511
524
  type: "string",
512
525
  default: "",
@@ -544,6 +557,11 @@ const getArgs = functoolsKit.singleshot(() => {
544
557
  positionals,
545
558
  };
546
559
  });
560
+ const getPositional = functoolsKit.singleshot(() => {
561
+ const { positionals = [] } = getArgs();
562
+ const result = positionals.find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
563
+ return result || null;
564
+ });
547
565
 
548
566
  const ADD_FRAME_FN = (self) => {
549
567
  self.loggerService.log("Adding February 2024 as a default frame schema");
@@ -729,11 +747,11 @@ class BacktestMainService {
729
747
  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)))) {
730
748
  return;
731
749
  }
732
- const { values, positionals } = getArgs();
750
+ const { values } = getArgs();
733
751
  if (!values.backtest) {
734
752
  return;
735
753
  }
736
- const [entryPoint = null] = positionals.slice(-1);
754
+ const entryPoint = getPositional();
737
755
  if (!entryPoint) {
738
756
  throw new Error("Entry point is required");
739
757
  }
@@ -808,11 +826,11 @@ class LiveMainService {
808
826
  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)))) {
809
827
  return;
810
828
  }
811
- const { values, positionals } = getArgs();
829
+ const { values } = getArgs();
812
830
  if (!values.live) {
813
831
  return;
814
832
  }
815
- const [entryPoint = null] = positionals.slice(-1);
833
+ const entryPoint = getPositional();
816
834
  if (!entryPoint) {
817
835
  throw new Error("Entry point is required");
818
836
  }
@@ -881,11 +899,11 @@ class PaperMainService {
881
899
  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)))) {
882
900
  return;
883
901
  }
884
- const { values, positionals } = getArgs();
902
+ const { values } = getArgs();
885
903
  if (!values.paper) {
886
904
  return;
887
905
  }
888
- const [entryPoint = null] = positionals.slice(-1);
906
+ const entryPoint = getPositional();
889
907
  if (!entryPoint) {
890
908
  throw new Error("Entry point is required");
891
909
  }
@@ -2202,7 +2220,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2202
2220
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
2203
2221
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2204
2222
  });
2205
- const main$5 = async () => {
2223
+ const main$6 = async () => {
2206
2224
  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)))) {
2207
2225
  return;
2208
2226
  }
@@ -2213,7 +2231,7 @@ const main$5 = async () => {
2213
2231
  await cli.backtestMainService.connect();
2214
2232
  listenGracefulShutdown$4();
2215
2233
  };
2216
- main$5();
2234
+ main$6();
2217
2235
 
2218
2236
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2219
2237
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2234,7 +2252,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2234
2252
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
2235
2253
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2236
2254
  });
2237
- const main$4 = async () => {
2255
+ const main$5 = async () => {
2238
2256
  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)))) {
2239
2257
  return;
2240
2258
  }
@@ -2245,7 +2263,7 @@ const main$4 = async () => {
2245
2263
  cli.paperMainService.connect();
2246
2264
  listenGracefulShutdown$3();
2247
2265
  };
2248
- main$4();
2266
+ main$5();
2249
2267
 
2250
2268
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2251
2269
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2266,7 +2284,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2266
2284
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2267
2285
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2268
2286
  });
2269
- const main$3 = async () => {
2287
+ const main$4 = async () => {
2270
2288
  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)))) {
2271
2289
  return;
2272
2290
  }
@@ -2277,7 +2295,7 @@ const main$3 = async () => {
2277
2295
  await cli.liveMainService.connect();
2278
2296
  listenGracefulShutdown$2();
2279
2297
  };
2280
- main$3();
2298
+ main$4();
2281
2299
 
2282
2300
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2283
2301
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2287,7 +2305,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2287
2305
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
2288
2306
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2289
2307
  });
2290
- const main$2 = async () => {
2308
+ const main$3 = async () => {
2291
2309
  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)))) {
2292
2310
  return;
2293
2311
  }
@@ -2297,7 +2315,7 @@ const main$2 = async () => {
2297
2315
  }
2298
2316
  listenGracefulShutdown$1();
2299
2317
  };
2300
- main$2();
2318
+ main$3();
2301
2319
 
2302
2320
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2303
2321
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2307,7 +2325,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2307
2325
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
2308
2326
  process.on("SIGINT", BEFORE_EXIT_FN);
2309
2327
  });
2310
- const main$1 = async () => {
2328
+ const main$2 = async () => {
2311
2329
  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)))) {
2312
2330
  return;
2313
2331
  }
@@ -2317,7 +2335,7 @@ const main$1 = async () => {
2317
2335
  }
2318
2336
  listenGracefulShutdown();
2319
2337
  };
2320
- main$1();
2338
+ main$2();
2321
2339
 
2322
2340
  const EXTRACT_ROWS_FN = (plots, schema) => {
2323
2341
  const keys = Object.keys(schema);
@@ -2339,15 +2357,15 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2339
2357
  }
2340
2358
  return rows;
2341
2359
  };
2342
- const main = async () => {
2360
+ const main$1 = async () => {
2343
2361
  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)))) {
2344
2362
  return;
2345
2363
  }
2346
- const { values, positionals } = getArgs();
2364
+ const { values } = getArgs();
2347
2365
  if (!values.pine) {
2348
2366
  return;
2349
2367
  }
2350
- const [entryPoint = null] = positionals.slice(-1);
2368
+ const entryPoint = getPositional();
2351
2369
  if (!entryPoint) {
2352
2370
  return;
2353
2371
  }
@@ -2388,23 +2406,76 @@ const main = async () => {
2388
2406
  const dumpDir = path.join(process.cwd(), "dump");
2389
2407
  if (values.json) {
2390
2408
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2409
+ const filePath = path.resolve(dumpDir, `${dumpName}.json`);
2391
2410
  await fs$1.mkdir(dumpDir, { recursive: true });
2392
- await fs$1.writeFile(path.join(dumpDir, `${dumpName}.json`), JSON.stringify(rows, null, 2), "utf-8");
2411
+ await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2412
+ console.log(`Saved: ${filePath}`);
2393
2413
  return;
2394
2414
  }
2395
2415
  if (values.jsonl) {
2396
2416
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2417
+ const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
2397
2418
  await fs$1.mkdir(dumpDir, { recursive: true });
2398
- await fs$1.writeFile(path.join(dumpDir, `${dumpName}.jsonl`), rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2419
+ await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2420
+ console.log(`Saved: ${filePath}`);
2399
2421
  return;
2400
2422
  }
2401
2423
  if (values.markdown) {
2424
+ const filePath = path.resolve(dumpDir, `${dumpName}.md`);
2402
2425
  await fs$1.mkdir(dumpDir, { recursive: true });
2403
- await fs$1.writeFile(path.join(dumpDir, `${dumpName}.md`), await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
2426
+ await fs$1.writeFile(filePath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
2427
+ console.log(`Saved: ${filePath}`);
2404
2428
  return;
2405
2429
  }
2406
2430
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
2407
2431
  };
2432
+ main$1();
2433
+
2434
+ const main = async () => {
2435
+ 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)))) {
2436
+ return;
2437
+ }
2438
+ const { values } = getArgs();
2439
+ if (!values.dump) {
2440
+ return;
2441
+ }
2442
+ await cli.moduleConnectionService.loadModule("./dump.module");
2443
+ {
2444
+ await cli.exchangeSchemaService.addSchema();
2445
+ await cli.symbolSchemaService.addSchema();
2446
+ }
2447
+ const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
2448
+ const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
2449
+ const symbol = values.symbol || "BTCUSDT";
2450
+ const timeframe = values.timeframe || "15m";
2451
+ const limitStr = values.limit || "250";
2452
+ const limitNum = parseInt(limitStr);
2453
+ const limit = isNaN(limitNum) ? 250 : limitNum;
2454
+ const whenStr = values.when || Date.now().toString();
2455
+ const whenStamp = Date.parse(whenStr);
2456
+ const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
2457
+ const timestamp = BacktestKit.alignToInterval(when, timeframe).getTime();
2458
+ const candles = await BacktestKit.Exchange.getRawCandles(symbol, timeframe, {
2459
+ exchangeName,
2460
+ }, limit, undefined, timestamp);
2461
+ const dumpName = values.output || `${symbol}_${limit}_${timeframe}_${timestamp}`;
2462
+ const dumpDir = path.join(process.cwd(), "dump");
2463
+ if (values.json) {
2464
+ const filePath = path.resolve(dumpDir, `${dumpName}.json`);
2465
+ await fs$1.mkdir(dumpDir, { recursive: true });
2466
+ await fs$1.writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
2467
+ console.log(`Saved: ${filePath}`);
2468
+ return;
2469
+ }
2470
+ if (values.jsonl) {
2471
+ const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
2472
+ await fs$1.mkdir(dumpDir, { recursive: true });
2473
+ await fs$1.writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2474
+ console.log(`Saved: ${filePath}`);
2475
+ return;
2476
+ }
2477
+ console.log(JSON.stringify(candles, null, 2));
2478
+ };
2408
2479
  main();
2409
2480
 
2410
2481
  function setLogger(logger) {
package/build/index.mjs CHANGED
@@ -1,10 +1,10 @@
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, { basename, extname, join } from 'path';
7
+ import path, { basename, extname, join, resolve } from 'path';
8
8
  import fs$1, { access, readFile, mkdir, writeFile } from 'fs/promises';
9
9
  import dotenv from 'dotenv';
10
10
  import { createActivator } from 'di-kit';
@@ -93,6 +93,7 @@ setConfig({
93
93
  setConfig({
94
94
  CC_ENABLE_DCA_EVERYWHERE: true,
95
95
  CC_ENABLE_PPPL_EVERYWHERE: true,
96
+ CC_ENABLE_TRAILING_EVERYWHERE: true,
96
97
  });
97
98
  setConfig({
98
99
  CC_MAX_SIGNAL_GENERATION_SECONDS: 15 * 60,
@@ -420,6 +421,14 @@ var FrameName;
420
421
  })(FrameName || (FrameName = {}));
421
422
  var FrameName$1 = FrameName;
422
423
 
424
+ const ALLOWED_EXTENSIONS = [
425
+ `.cjs`,
426
+ `.mjs`,
427
+ `.ts`,
428
+ `.tsx`,
429
+ `.js`,
430
+ `.pine`,
431
+ ];
423
432
  const getArgs = singleshot(() => {
424
433
  const { values, positionals } = parseArgs({
425
434
  args: process.argv,
@@ -482,6 +491,10 @@ const getArgs = singleshot(() => {
482
491
  type: "boolean",
483
492
  default: false,
484
493
  },
494
+ dump: {
495
+ type: "boolean",
496
+ default: false,
497
+ },
485
498
  timeframe: {
486
499
  type: "string",
487
500
  default: "",
@@ -519,6 +532,11 @@ const getArgs = singleshot(() => {
519
532
  positionals,
520
533
  };
521
534
  });
535
+ const getPositional = singleshot(() => {
536
+ const { positionals = [] } = getArgs();
537
+ const result = positionals.find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
538
+ return result || null;
539
+ });
522
540
 
523
541
  const ADD_FRAME_FN = (self) => {
524
542
  self.loggerService.log("Adding February 2024 as a default frame schema");
@@ -704,11 +722,11 @@ class BacktestMainService {
704
722
  if (!getEntry(import.meta.url)) {
705
723
  return;
706
724
  }
707
- const { values, positionals } = getArgs();
725
+ const { values } = getArgs();
708
726
  if (!values.backtest) {
709
727
  return;
710
728
  }
711
- const [entryPoint = null] = positionals.slice(-1);
729
+ const entryPoint = getPositional();
712
730
  if (!entryPoint) {
713
731
  throw new Error("Entry point is required");
714
732
  }
@@ -783,11 +801,11 @@ class LiveMainService {
783
801
  if (!getEntry(import.meta.url)) {
784
802
  return;
785
803
  }
786
- const { values, positionals } = getArgs();
804
+ const { values } = getArgs();
787
805
  if (!values.live) {
788
806
  return;
789
807
  }
790
- const [entryPoint = null] = positionals.slice(-1);
808
+ const entryPoint = getPositional();
791
809
  if (!entryPoint) {
792
810
  throw new Error("Entry point is required");
793
811
  }
@@ -856,11 +874,11 @@ class PaperMainService {
856
874
  if (!getEntry(import.meta.url)) {
857
875
  return;
858
876
  }
859
- const { values, positionals } = getArgs();
877
+ const { values } = getArgs();
860
878
  if (!values.paper) {
861
879
  return;
862
880
  }
863
- const [entryPoint = null] = positionals.slice(-1);
881
+ const entryPoint = getPositional();
864
882
  if (!entryPoint) {
865
883
  throw new Error("Entry point is required");
866
884
  }
@@ -2173,7 +2191,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
2173
2191
  const listenGracefulShutdown$4 = singleshot(() => {
2174
2192
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2175
2193
  });
2176
- const main$5 = async () => {
2194
+ const main$6 = async () => {
2177
2195
  if (!getEntry(import.meta.url)) {
2178
2196
  return;
2179
2197
  }
@@ -2184,7 +2202,7 @@ const main$5 = async () => {
2184
2202
  await cli.backtestMainService.connect();
2185
2203
  listenGracefulShutdown$4();
2186
2204
  };
2187
- main$5();
2205
+ main$6();
2188
2206
 
2189
2207
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
2190
2208
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2205,7 +2223,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
2205
2223
  const listenGracefulShutdown$3 = singleshot(() => {
2206
2224
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2207
2225
  });
2208
- const main$4 = async () => {
2226
+ const main$5 = async () => {
2209
2227
  if (!getEntry(import.meta.url)) {
2210
2228
  return;
2211
2229
  }
@@ -2216,7 +2234,7 @@ const main$4 = async () => {
2216
2234
  cli.paperMainService.connect();
2217
2235
  listenGracefulShutdown$3();
2218
2236
  };
2219
- main$4();
2237
+ main$5();
2220
2238
 
2221
2239
  const BEFORE_EXIT_FN$2 = singleshot(async () => {
2222
2240
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2237,7 +2255,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
2237
2255
  const listenGracefulShutdown$2 = singleshot(() => {
2238
2256
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2239
2257
  });
2240
- const main$3 = async () => {
2258
+ const main$4 = async () => {
2241
2259
  if (!getEntry(import.meta.url)) {
2242
2260
  return;
2243
2261
  }
@@ -2248,7 +2266,7 @@ const main$3 = async () => {
2248
2266
  await cli.liveMainService.connect();
2249
2267
  listenGracefulShutdown$2();
2250
2268
  };
2251
- main$3();
2269
+ main$4();
2252
2270
 
2253
2271
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
2254
2272
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2258,7 +2276,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
2258
2276
  const listenGracefulShutdown$1 = singleshot(() => {
2259
2277
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2260
2278
  });
2261
- const main$2 = async () => {
2279
+ const main$3 = async () => {
2262
2280
  if (!getEntry(import.meta.url)) {
2263
2281
  return;
2264
2282
  }
@@ -2268,7 +2286,7 @@ const main$2 = async () => {
2268
2286
  }
2269
2287
  listenGracefulShutdown$1();
2270
2288
  };
2271
- main$2();
2289
+ main$3();
2272
2290
 
2273
2291
  const BEFORE_EXIT_FN = singleshot(async () => {
2274
2292
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2278,7 +2296,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
2278
2296
  const listenGracefulShutdown = singleshot(() => {
2279
2297
  process.on("SIGINT", BEFORE_EXIT_FN);
2280
2298
  });
2281
- const main$1 = async () => {
2299
+ const main$2 = async () => {
2282
2300
  if (!getEntry(import.meta.url)) {
2283
2301
  return;
2284
2302
  }
@@ -2288,7 +2306,7 @@ const main$1 = async () => {
2288
2306
  }
2289
2307
  listenGracefulShutdown();
2290
2308
  };
2291
- main$1();
2309
+ main$2();
2292
2310
 
2293
2311
  const EXTRACT_ROWS_FN = (plots, schema) => {
2294
2312
  const keys = Object.keys(schema);
@@ -2310,15 +2328,15 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2310
2328
  }
2311
2329
  return rows;
2312
2330
  };
2313
- const main = async () => {
2331
+ const main$1 = async () => {
2314
2332
  if (!getEntry(import.meta.url)) {
2315
2333
  return;
2316
2334
  }
2317
- const { values, positionals } = getArgs();
2335
+ const { values } = getArgs();
2318
2336
  if (!values.pine) {
2319
2337
  return;
2320
2338
  }
2321
- const [entryPoint = null] = positionals.slice(-1);
2339
+ const entryPoint = getPositional();
2322
2340
  if (!entryPoint) {
2323
2341
  return;
2324
2342
  }
@@ -2359,23 +2377,76 @@ const main = async () => {
2359
2377
  const dumpDir = join(process.cwd(), "dump");
2360
2378
  if (values.json) {
2361
2379
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2380
+ const filePath = resolve(dumpDir, `${dumpName}.json`);
2362
2381
  await mkdir(dumpDir, { recursive: true });
2363
- await writeFile(join(dumpDir, `${dumpName}.json`), JSON.stringify(rows, null, 2), "utf-8");
2382
+ await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2383
+ console.log(`Saved: ${filePath}`);
2364
2384
  return;
2365
2385
  }
2366
2386
  if (values.jsonl) {
2367
2387
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2388
+ const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
2368
2389
  await mkdir(dumpDir, { recursive: true });
2369
- await writeFile(join(dumpDir, `${dumpName}.jsonl`), rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2390
+ await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2391
+ console.log(`Saved: ${filePath}`);
2370
2392
  return;
2371
2393
  }
2372
2394
  if (values.markdown) {
2395
+ const filePath = resolve(dumpDir, `${dumpName}.md`);
2373
2396
  await mkdir(dumpDir, { recursive: true });
2374
- await writeFile(join(dumpDir, `${dumpName}.md`), await toMarkdown(signalId, plots, signalSchema), "utf-8");
2397
+ await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
2398
+ console.log(`Saved: ${filePath}`);
2375
2399
  return;
2376
2400
  }
2377
2401
  console.log(await toMarkdown(signalId, plots, signalSchema));
2378
2402
  };
2403
+ main$1();
2404
+
2405
+ const main = async () => {
2406
+ if (!getEntry(import.meta.url)) {
2407
+ return;
2408
+ }
2409
+ const { values } = getArgs();
2410
+ if (!values.dump) {
2411
+ return;
2412
+ }
2413
+ await cli.moduleConnectionService.loadModule("./dump.module");
2414
+ {
2415
+ await cli.exchangeSchemaService.addSchema();
2416
+ await cli.symbolSchemaService.addSchema();
2417
+ }
2418
+ const [defaultExchangeName = null] = await listExchangeSchema();
2419
+ const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
2420
+ const symbol = values.symbol || "BTCUSDT";
2421
+ const timeframe = values.timeframe || "15m";
2422
+ const limitStr = values.limit || "250";
2423
+ const limitNum = parseInt(limitStr);
2424
+ const limit = isNaN(limitNum) ? 250 : limitNum;
2425
+ const whenStr = values.when || Date.now().toString();
2426
+ const whenStamp = Date.parse(whenStr);
2427
+ const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
2428
+ const timestamp = alignToInterval(when, timeframe).getTime();
2429
+ const candles = await Exchange.getRawCandles(symbol, timeframe, {
2430
+ exchangeName,
2431
+ }, limit, undefined, timestamp);
2432
+ const dumpName = values.output || `${symbol}_${limit}_${timeframe}_${timestamp}`;
2433
+ const dumpDir = join(process.cwd(), "dump");
2434
+ if (values.json) {
2435
+ const filePath = resolve(dumpDir, `${dumpName}.json`);
2436
+ await mkdir(dumpDir, { recursive: true });
2437
+ await writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
2438
+ console.log(`Saved: ${filePath}`);
2439
+ return;
2440
+ }
2441
+ if (values.jsonl) {
2442
+ const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
2443
+ await mkdir(dumpDir, { recursive: true });
2444
+ await writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2445
+ console.log(`Saved: ${filePath}`);
2446
+ return;
2447
+ }
2448
+ console.log(JSON.stringify(candles, null, 2));
2449
+ };
2379
2450
  main();
2380
2451
 
2381
2452
  function setLogger(logger) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "5.10.1",
3
+ "version": "5.11.0",
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",
@@ -61,11 +61,11 @@
61
61
  "devDependencies": {
62
62
  "@babel/plugin-transform-modules-umd": "7.27.1",
63
63
  "@babel/standalone": "7.29.1",
64
- "@backtest-kit/ui": "5.10.0",
65
- "@backtest-kit/graph": "5.10.0",
66
- "@backtest-kit/ollama": "5.10.0",
67
- "@backtest-kit/pinets": "5.10.0",
68
- "@backtest-kit/signals": "5.10.0",
64
+ "@backtest-kit/ui": "5.11.0",
65
+ "@backtest-kit/graph": "5.11.0",
66
+ "@backtest-kit/ollama": "5.11.0",
67
+ "@backtest-kit/pinets": "5.11.0",
68
+ "@backtest-kit/signals": "5.11.0",
69
69
  "@rollup/plugin-replace": "6.0.3",
70
70
  "@rollup/plugin-typescript": "11.1.6",
71
71
  "@types/image-size": "0.7.0",
@@ -73,7 +73,7 @@
73
73
  "@types/mustache": "4.2.6",
74
74
  "@types/node": "22.9.0",
75
75
  "@types/stack-trace": "0.0.33",
76
- "backtest-kit": "5.10.0",
76
+ "backtest-kit": "5.11.0",
77
77
  "glob": "11.0.1",
78
78
  "markdown-it": "14.1.1",
79
79
  "rimraf": "6.0.1",
@@ -88,12 +88,12 @@
88
88
  "peerDependencies": {
89
89
  "@babel/plugin-transform-modules-umd": "^7.27.1",
90
90
  "@babel/standalone": "^7.29.1",
91
- "@backtest-kit/ui": "^5.10.0",
92
- "@backtest-kit/graph": "^5.10.0",
93
- "@backtest-kit/ollama": "^5.10.0",
94
- "@backtest-kit/pinets": "^5.10.0",
95
- "@backtest-kit/signals": "^5.10.0",
96
- "backtest-kit": "^5.10.0",
91
+ "@backtest-kit/ui": "^5.11.0",
92
+ "@backtest-kit/graph": "^5.11.0",
93
+ "@backtest-kit/ollama": "^5.11.0",
94
+ "@backtest-kit/pinets": "^5.11.0",
95
+ "@backtest-kit/signals": "^5.11.0",
96
+ "backtest-kit": "^5.11.0",
97
97
  "markdown-it": "^14.1.1",
98
98
  "typescript": "^5.0.0"
99
99
  },