@backtest-kit/cli 5.10.1 → 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
@@ -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
@@ -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: "",
@@ -2202,7 +2206,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2202
2206
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
2203
2207
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2204
2208
  });
2205
- const main$5 = async () => {
2209
+ const main$6 = async () => {
2206
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)))) {
2207
2211
  return;
2208
2212
  }
@@ -2213,7 +2217,7 @@ const main$5 = async () => {
2213
2217
  await cli.backtestMainService.connect();
2214
2218
  listenGracefulShutdown$4();
2215
2219
  };
2216
- main$5();
2220
+ main$6();
2217
2221
 
2218
2222
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2219
2223
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2234,7 +2238,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2234
2238
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
2235
2239
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2236
2240
  });
2237
- const main$4 = async () => {
2241
+ const main$5 = async () => {
2238
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)))) {
2239
2243
  return;
2240
2244
  }
@@ -2245,7 +2249,7 @@ const main$4 = async () => {
2245
2249
  cli.paperMainService.connect();
2246
2250
  listenGracefulShutdown$3();
2247
2251
  };
2248
- main$4();
2252
+ main$5();
2249
2253
 
2250
2254
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2251
2255
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2266,7 +2270,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2266
2270
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2267
2271
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2268
2272
  });
2269
- const main$3 = async () => {
2273
+ const main$4 = async () => {
2270
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)))) {
2271
2275
  return;
2272
2276
  }
@@ -2277,7 +2281,7 @@ const main$3 = async () => {
2277
2281
  await cli.liveMainService.connect();
2278
2282
  listenGracefulShutdown$2();
2279
2283
  };
2280
- main$3();
2284
+ main$4();
2281
2285
 
2282
2286
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2283
2287
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2287,7 +2291,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2287
2291
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
2288
2292
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2289
2293
  });
2290
- const main$2 = async () => {
2294
+ const main$3 = async () => {
2291
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)))) {
2292
2296
  return;
2293
2297
  }
@@ -2297,7 +2301,7 @@ const main$2 = async () => {
2297
2301
  }
2298
2302
  listenGracefulShutdown$1();
2299
2303
  };
2300
- main$2();
2304
+ main$3();
2301
2305
 
2302
2306
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2303
2307
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2307,7 +2311,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2307
2311
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
2308
2312
  process.on("SIGINT", BEFORE_EXIT_FN);
2309
2313
  });
2310
- const main$1 = async () => {
2314
+ const main$2 = async () => {
2311
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)))) {
2312
2316
  return;
2313
2317
  }
@@ -2317,7 +2321,7 @@ const main$1 = async () => {
2317
2321
  }
2318
2322
  listenGracefulShutdown();
2319
2323
  };
2320
- main$1();
2324
+ main$2();
2321
2325
 
2322
2326
  const EXTRACT_ROWS_FN = (plots, schema) => {
2323
2327
  const keys = Object.keys(schema);
@@ -2339,7 +2343,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2339
2343
  }
2340
2344
  return rows;
2341
2345
  };
2342
- const main = async () => {
2346
+ const main$1 = async () => {
2343
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)))) {
2344
2348
  return;
2345
2349
  }
@@ -2388,23 +2392,76 @@ const main = async () => {
2388
2392
  const dumpDir = path.join(process.cwd(), "dump");
2389
2393
  if (values.json) {
2390
2394
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2395
+ const filePath = path.resolve(dumpDir, `${dumpName}.json`);
2391
2396
  await fs$1.mkdir(dumpDir, { recursive: true });
2392
- await fs$1.writeFile(path.join(dumpDir, `${dumpName}.json`), JSON.stringify(rows, null, 2), "utf-8");
2397
+ await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2398
+ console.log(`Saved: ${filePath}`);
2393
2399
  return;
2394
2400
  }
2395
2401
  if (values.jsonl) {
2396
2402
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2403
+ const filePath = path.resolve(dumpDir, `${dumpName}.jsonl`);
2397
2404
  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");
2405
+ await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2406
+ console.log(`Saved: ${filePath}`);
2399
2407
  return;
2400
2408
  }
2401
2409
  if (values.markdown) {
2410
+ const filePath = path.resolve(dumpDir, `${dumpName}.md`);
2402
2411
  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");
2412
+ await fs$1.writeFile(filePath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
2413
+ console.log(`Saved: ${filePath}`);
2404
2414
  return;
2405
2415
  }
2406
2416
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
2407
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
+ };
2408
2465
  main();
2409
2466
 
2410
2467
  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';
@@ -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: "",
@@ -2173,7 +2177,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
2173
2177
  const listenGracefulShutdown$4 = singleshot(() => {
2174
2178
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2175
2179
  });
2176
- const main$5 = async () => {
2180
+ const main$6 = async () => {
2177
2181
  if (!getEntry(import.meta.url)) {
2178
2182
  return;
2179
2183
  }
@@ -2184,7 +2188,7 @@ const main$5 = async () => {
2184
2188
  await cli.backtestMainService.connect();
2185
2189
  listenGracefulShutdown$4();
2186
2190
  };
2187
- main$5();
2191
+ main$6();
2188
2192
 
2189
2193
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
2190
2194
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2205,7 +2209,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
2205
2209
  const listenGracefulShutdown$3 = singleshot(() => {
2206
2210
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2207
2211
  });
2208
- const main$4 = async () => {
2212
+ const main$5 = async () => {
2209
2213
  if (!getEntry(import.meta.url)) {
2210
2214
  return;
2211
2215
  }
@@ -2216,7 +2220,7 @@ const main$4 = async () => {
2216
2220
  cli.paperMainService.connect();
2217
2221
  listenGracefulShutdown$3();
2218
2222
  };
2219
- main$4();
2223
+ main$5();
2220
2224
 
2221
2225
  const BEFORE_EXIT_FN$2 = singleshot(async () => {
2222
2226
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2237,7 +2241,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
2237
2241
  const listenGracefulShutdown$2 = singleshot(() => {
2238
2242
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2239
2243
  });
2240
- const main$3 = async () => {
2244
+ const main$4 = async () => {
2241
2245
  if (!getEntry(import.meta.url)) {
2242
2246
  return;
2243
2247
  }
@@ -2248,7 +2252,7 @@ const main$3 = async () => {
2248
2252
  await cli.liveMainService.connect();
2249
2253
  listenGracefulShutdown$2();
2250
2254
  };
2251
- main$3();
2255
+ main$4();
2252
2256
 
2253
2257
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
2254
2258
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2258,7 +2262,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
2258
2262
  const listenGracefulShutdown$1 = singleshot(() => {
2259
2263
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2260
2264
  });
2261
- const main$2 = async () => {
2265
+ const main$3 = async () => {
2262
2266
  if (!getEntry(import.meta.url)) {
2263
2267
  return;
2264
2268
  }
@@ -2268,7 +2272,7 @@ const main$2 = async () => {
2268
2272
  }
2269
2273
  listenGracefulShutdown$1();
2270
2274
  };
2271
- main$2();
2275
+ main$3();
2272
2276
 
2273
2277
  const BEFORE_EXIT_FN = singleshot(async () => {
2274
2278
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2278,7 +2282,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
2278
2282
  const listenGracefulShutdown = singleshot(() => {
2279
2283
  process.on("SIGINT", BEFORE_EXIT_FN);
2280
2284
  });
2281
- const main$1 = async () => {
2285
+ const main$2 = async () => {
2282
2286
  if (!getEntry(import.meta.url)) {
2283
2287
  return;
2284
2288
  }
@@ -2288,7 +2292,7 @@ const main$1 = async () => {
2288
2292
  }
2289
2293
  listenGracefulShutdown();
2290
2294
  };
2291
- main$1();
2295
+ main$2();
2292
2296
 
2293
2297
  const EXTRACT_ROWS_FN = (plots, schema) => {
2294
2298
  const keys = Object.keys(schema);
@@ -2310,7 +2314,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2310
2314
  }
2311
2315
  return rows;
2312
2316
  };
2313
- const main = async () => {
2317
+ const main$1 = async () => {
2314
2318
  if (!getEntry(import.meta.url)) {
2315
2319
  return;
2316
2320
  }
@@ -2359,23 +2363,76 @@ const main = async () => {
2359
2363
  const dumpDir = join(process.cwd(), "dump");
2360
2364
  if (values.json) {
2361
2365
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2366
+ const filePath = resolve(dumpDir, `${dumpName}.json`);
2362
2367
  await mkdir(dumpDir, { recursive: true });
2363
- await writeFile(join(dumpDir, `${dumpName}.json`), JSON.stringify(rows, null, 2), "utf-8");
2368
+ await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2369
+ console.log(`Saved: ${filePath}`);
2364
2370
  return;
2365
2371
  }
2366
2372
  if (values.jsonl) {
2367
2373
  const rows = EXTRACT_ROWS_FN(plots, signalSchema);
2374
+ const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
2368
2375
  await mkdir(dumpDir, { recursive: true });
2369
- await writeFile(join(dumpDir, `${dumpName}.jsonl`), rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2376
+ await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2377
+ console.log(`Saved: ${filePath}`);
2370
2378
  return;
2371
2379
  }
2372
2380
  if (values.markdown) {
2381
+ const filePath = resolve(dumpDir, `${dumpName}.md`);
2373
2382
  await mkdir(dumpDir, { recursive: true });
2374
- await writeFile(join(dumpDir, `${dumpName}.md`), await toMarkdown(signalId, plots, signalSchema), "utf-8");
2383
+ await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
2384
+ console.log(`Saved: ${filePath}`);
2375
2385
  return;
2376
2386
  }
2377
2387
  console.log(await toMarkdown(signalId, plots, signalSchema));
2378
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
+ };
2379
2436
  main();
2380
2437
 
2381
2438
  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.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",