@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 +92 -0
- package/build/index.cjs +71 -14
- package/build/index.mjs +73 -16
- package/package.json +1 -1
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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(
|
|
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(
|
|
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(
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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",
|