@backtest-kit/cli 0.0.3 → 0.0.5

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
@@ -30,13 +30,13 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
30
30
 
31
31
  `@backtest-kit/cli` wraps the `backtest-kit` engine and resolves all scaffolding automatically:
32
32
 
33
- | Mode | Flag | Description |
34
- |------------------|---------------|----------------------------------------------|
35
- | **Backtest** | `--backtest` | Run strategy on historical candle data |
36
- | **Paper** | `--paper` | Live prices, no real orders |
37
- | **Live** | `--live` | Real trades via exchange API |
38
- | **UI Dashboard** | `--ui` | Web dashboard at `http://localhost:60050` |
39
- | **Telegram** | `--telegram` | Trade notifications with price charts |
33
+ | Mode | Command Line Args | Description |
34
+ |------------------|----------------------------|----------------------------------------------|
35
+ | **Backtest** | `--backtest` | Run strategy on historical candle data |
36
+ | **Paper** | `--paper` | Live prices, no real orders |
37
+ | **Live** | `--live` | Real trades via exchange API |
38
+ | **UI Dashboard** | `--ui` | Web dashboard at `http://localhost:60050` |
39
+ | **Telegram** | `--telegram` | Trade notifications with price charts |
40
40
 
41
41
  ## 🚀 Installation
42
42
 
@@ -129,19 +129,20 @@ npm start -- --symbol BTCUSDT --ui
129
129
 
130
130
  ## 🎛️ CLI Flags
131
131
 
132
- | Flag | Type | Description |
133
- |---------------|---------|--------------------------------------------------------------------|
134
- | `--backtest` | boolean | Run historical backtest (default: `false`) |
135
- | `--paper` | boolean | Paper trading (live prices, no orders) (default: `false`) |
136
- | `--live` | boolean | Run live trading (default: `false`) |
137
- | `--ui` | boolean | Start web UI dashboard (default: `false`) |
138
- | `--telegram` | boolean | Enable Telegram notifications (default: `false`) |
139
- | `--verbose` | boolean | Log each candle fetch (default: `false`) |
140
- | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
141
- | `--strategy` | string | Strategy name (default: first registered) |
142
- | `--exchange` | string | Exchange name (default: first registered) |
143
- | `--frame` | string | Backtest frame name (default: first registered) |
144
- | `--cache` | string | Intervals to pre-cache before backtest (default: `"1m, 15m, 30m, 4h"`) |
132
+ | Command Line Args | Type | Description |
133
+ |---------------------------|---------|--------------------------------------------------------------------|
134
+ | `--backtest` | boolean | Run historical backtest (default: `false`) |
135
+ | `--paper` | boolean | Paper trading (live prices, no orders) (default: `false`) |
136
+ | `--live` | boolean | Run live trading (default: `false`) |
137
+ | `--ui` | boolean | Start web UI dashboard (default: `false`) |
138
+ | `--telegram` | boolean | Enable Telegram notifications (default: `false`) |
139
+ | `--verbose` | boolean | Log each candle fetch (default: `false`) |
140
+ | `--noCache` | boolean | Skip candle cache warming before backtest (default: `false`) |
141
+ | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
142
+ | `--strategy` | string | Strategy name (default: first registered) |
143
+ | `--exchange` | string | Exchange name (default: first registered) |
144
+ | `--frame` | string | Backtest frame name (default: first registered) |
145
+ | `--cacheInterval` | string | Intervals to pre-cache before backtest (default: `"1m, 15m, 30m, 4h"`) |
145
146
 
146
147
  **Positional argument (required):** path to your strategy entry point file (set once in `package.json` scripts).
147
148
 
@@ -162,7 +163,7 @@ Runs the strategy against historical candle data using a registered `FrameSchema
162
163
  ```json
163
164
  {
164
165
  "scripts": {
165
- "backtest": "@backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cache \"1m, 15m, 1h, 4h\" ./src/index.mjs"
166
+ "backtest": "@backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs"
166
167
  }
167
168
  }
168
169
  ```
@@ -171,7 +172,7 @@ Runs the strategy against historical candle data using a registered `FrameSchema
171
172
  npm run backtest
172
173
  ```
173
174
 
174
- Before running, the CLI warms the candle cache for every interval in `--cache`. On the next run, cached data is used directly — no API calls needed.
175
+ Before running, the CLI warms the candle cache for every interval in `--cacheInterval`. On the next run, cached data is used directly — no API calls needed. Pass `--noCache` to skip this step entirely.
175
176
 
176
177
  ### Paper Trading
177
178
 
@@ -389,7 +390,7 @@ When your strategy module does not register an exchange, frame, or strategy name
389
390
  | **Exchange** | CCXT Binance (`default_exchange`) | `Warning: The default exchange schema is set to CCXT Binance...` |
390
391
  | **Frame** | February 2024 (`default_frame`) | `Warning: The default frame schema is set to February 2024...` |
391
392
  | **Symbol** | `BTCUSDT` | — |
392
- | **Cache intervals** | `1m, 15m, 30m, 4h` | Shown if `--cache` not provided |
393
+ | **Cache intervals** | `1m, 15m, 30m, 4h` | Used if `--cacheInterval` not provided; skip entirely with `--noCache` |
393
394
 
394
395
  > **Note:** The default exchange schema **does not support order book fetching in backtest mode**. If your strategy calls `getOrderBook()` during backtest, you must register a custom exchange schema with your own snapshot storage.
395
396
 
@@ -424,7 +425,8 @@ await run(mode, args);
424
425
  | `strategy` | `string` | Strategy name (default: first registered) |
425
426
  | `exchange` | `string` | Exchange name (default: first registered) |
426
427
  | `frame` | `string` | Frame name (default: first registered) |
427
- | `cacheList` | `CandleInterval[]` | Intervals to pre-cache (default: `["1m","15m","30m","1h","4h"]`) |
428
+ | `cacheInterval` | `CandleInterval[]` | Intervals to pre-cache (default: `["1m","15m","30m","1h","4h"]`) |
429
+ | `noCache` | `boolean` | Skip candle cache warming (default: `false`) |
428
430
  | `verbose` | `boolean` | Log each candle fetch (default: `false`) |
429
431
 
430
432
  **Paper** and **Live** (`mode: "paper"` / `mode: "live"`):
@@ -448,7 +450,7 @@ await run('backtest', {
448
450
  entryPoint: './src/index.mjs',
449
451
  symbol: 'ETHUSDT',
450
452
  frame: 'feb-2024',
451
- cacheList: ['1m', '15m', '1h'],
453
+ cacheInterval: ['1m', '15m', '1h'],
452
454
  verbose: true,
453
455
  });
454
456
  ```
package/build/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  'use strict';
2
3
 
3
4
  var backtestKit = require('backtest-kit');
@@ -5,6 +6,7 @@ var functoolsKit = require('functools-kit');
5
6
  var fs = require('fs');
6
7
  var stackTrace = require('stack-trace');
7
8
  var url = require('url');
9
+ var module$1 = require('module');
8
10
  var path = require('path');
9
11
  var fs$1 = require('fs/promises');
10
12
  var dotenv = require('dotenv');
@@ -21,7 +23,6 @@ var MarkdownIt = require('markdown-it');
21
23
  var sanitizeHtml = require('sanitize-html');
22
24
  var jsdom = require('jsdom');
23
25
  var Mustache = require('mustache');
24
- var module$1 = require('module');
25
26
 
26
27
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
27
28
  function _interopNamespaceDefault(e) {
@@ -83,7 +84,7 @@ var stackTrace__namespace = /*#__PURE__*/_interopNamespaceDefault(stackTrace);
83
84
  }
84
85
  {
85
86
  backtestKit.StorageLive.usePersist();
86
- backtestKit.StorageBacktest.useDummy();
87
+ backtestKit.StorageBacktest.useMemory();
87
88
  }
88
89
  {
89
90
  backtestKit.NotificationLive.useDummy();
@@ -231,6 +232,24 @@ const entrySubject = new functoolsKit.BehaviorSubject();
231
232
 
232
233
  const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
233
234
  const __dirname$1 = path.dirname(__filename$1);
235
+ const require$2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
236
+ const REQUIRE_ENTRY_FACTORY = (filePath) => {
237
+ try {
238
+ require$2(filePath);
239
+ return true;
240
+ }
241
+ catch {
242
+ return false;
243
+ }
244
+ };
245
+ const IMPORT_ENTRY_FACTORY = async (filePath) => {
246
+ await import(url.pathToFileURL(filePath).href);
247
+ };
248
+ const LOAD_ENTRY_FN = async (filePath) => {
249
+ if (!REQUIRE_ENTRY_FACTORY(filePath)) {
250
+ await IMPORT_ENTRY_FACTORY(filePath);
251
+ }
252
+ };
234
253
  let _is_launched = false;
235
254
  class ResolveService {
236
255
  constructor() {
@@ -249,9 +268,9 @@ class ResolveService {
249
268
  {
250
269
  const cwd = process.cwd();
251
270
  process.chdir(moduleRoot);
252
- dotenv.config({ path: path.join(cwd, '.env') });
253
- dotenv.config({ path: path.join(moduleRoot, '.env'), override: true });
254
- await import(url.pathToFileURL(absolutePath).href);
271
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
272
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
273
+ await LOAD_ENTRY_FN(absolutePath);
255
274
  await entrySubject.next(absolutePath);
256
275
  }
257
276
  _is_launched = true;
@@ -394,7 +413,11 @@ const getArgs = functoolsKit.singleshot(() => {
394
413
  type: "boolean",
395
414
  default: false,
396
415
  },
397
- cache: {
416
+ noCache: {
417
+ type: "boolean",
418
+ default: false,
419
+ },
420
+ cacheInterval: {
398
421
  type: "string",
399
422
  default: "1m, 15m, 30m, 4h",
400
423
  },
@@ -458,17 +481,64 @@ const notifyFinish = functoolsKit.singleshot(() => {
458
481
  });
459
482
 
460
483
  const getEntry = (metaUrl) => {
461
- return process.argv[1] === new URL(metaUrl).pathname;
484
+ const metaPath = url.fileURLToPath(metaUrl);
485
+ return path.resolve(process.argv[1]) === path.resolve(metaPath);
462
486
  };
463
487
 
488
+ const notifyVerbose = functoolsKit.singleshot(() => {
489
+ console.log("Using verbose logging...");
490
+ backtestKit.listenSignal((event) => {
491
+ if (event.action === "scheduled") {
492
+ console.log(`[POSITION SCHEDULED] ${event.symbol}`);
493
+ console.log(` Strategy: ${event.strategyName}`);
494
+ console.log(` Current Price: ${event.currentPrice}`);
495
+ console.log(` Entry Price: ${event.signal.priceOpen}`);
496
+ console.log(` Signal ID: ${event.signal.id}`);
497
+ console.log(` Direction: ${event.signal.position}`);
498
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
499
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
500
+ return;
501
+ }
502
+ if (event.action === "opened") {
503
+ console.log(`[POSITION OPENED] ${event.symbol}`);
504
+ console.log(` Strategy: ${event.strategyName}`);
505
+ console.log(` Entry Price: ${event.currentPrice}`);
506
+ console.log(` Signal ID: ${event.signal.id}`);
507
+ console.log(` Direction: ${event.signal.position}`);
508
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
509
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
510
+ return;
511
+ }
512
+ if (event.action === "closed") {
513
+ console.log(`[POSITION CLOSED] ${event.symbol}`);
514
+ console.log(` Strategy: ${event.strategyName}`);
515
+ console.log(` Entry Price (adj): ${event.pnl.priceOpen}`);
516
+ console.log(` Exit Price (adj): ${event.pnl.priceClose}`);
517
+ console.log(` Signal ID: ${event.signal.id}`);
518
+ console.log(` Close Reason: ${event.closeReason}`);
519
+ console.log(` PnL: ${event.pnl.pnlPercentage.toFixed(2)}%`);
520
+ console.log(` Win: ${event.pnl.pnlPercentage > 0 ? "YES" : "NO"}`);
521
+ return;
522
+ }
523
+ if (event.action === "cancelled") {
524
+ console.log(`[POSITION CANCELLED] ${event.symbol}`);
525
+ console.log(` Strategy: ${event.strategyName}`);
526
+ console.log(` Signal ID: ${event.signal.id}`);
527
+ console.log(` Current Price: ${event.currentPrice}`);
528
+ console.log(` Cancel Reason: ${event.reason}`);
529
+ console.log(` Cancelled At: ${new Date(event.closeTimestamp).toISOString()}`);
530
+ return;
531
+ }
532
+ });
533
+ });
534
+
464
535
  const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
465
- const GET_CACHE_LIST_FN = () => {
536
+ const GET_CACHE_INTERVAL_LIST_FN = () => {
466
537
  const { values } = getArgs();
467
- if (!values.cache) {
468
- console.warn(`Warning: No cache timeframes provided. Using default timeframes: ${DEFAULT_CACHE_LIST.join(", ")}`);
538
+ if (!values.cacheInterval) {
469
539
  return DEFAULT_CACHE_LIST;
470
540
  }
471
- return String(values.cache)
541
+ return String(values.cacheInterval)
472
542
  .split(",")
473
543
  .map((timeframe) => timeframe.trim());
474
544
  };
@@ -512,8 +582,8 @@ class BacktestMainService {
512
582
  if (!frameName) {
513
583
  throw new Error("Frame name is required");
514
584
  }
515
- {
516
- await this.cacheLogicService.execute(payload.cacheList, {
585
+ if (!payload.noCache) {
586
+ await this.cacheLogicService.execute(payload.cacheInterval, {
517
587
  exchangeName,
518
588
  frameName,
519
589
  symbol,
@@ -528,6 +598,7 @@ class BacktestMainService {
528
598
  },
529
599
  },
530
600
  });
601
+ notifyVerbose();
531
602
  }
532
603
  backtestKit.Backtest.background(symbol, {
533
604
  strategyName,
@@ -545,19 +616,20 @@ class BacktestMainService {
545
616
  if (!values.backtest) {
546
617
  return;
547
618
  }
548
- const [entryPoint = null] = positionals;
619
+ const [entryPoint = null] = positionals.slice(-1);
549
620
  if (!entryPoint) {
550
621
  throw new Error("Entry point is required");
551
622
  }
552
- const cacheList = GET_CACHE_LIST_FN();
623
+ const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
553
624
  return await this.run({
554
625
  symbol: values.symbol,
555
626
  entryPoint,
556
- cacheList,
627
+ cacheInterval,
557
628
  exchange: values.exchange,
558
629
  frame: values.frame,
559
630
  strategy: values.strategy,
560
631
  verbose: values.verbose,
632
+ noCache: values.noCache,
561
633
  });
562
634
  });
563
635
  }
@@ -606,6 +678,7 @@ class LiveMainService {
606
678
  },
607
679
  },
608
680
  });
681
+ notifyVerbose();
609
682
  }
610
683
  backtestKit.Live.background(symbol, {
611
684
  strategyName,
@@ -622,7 +695,7 @@ class LiveMainService {
622
695
  if (!values.live) {
623
696
  return;
624
697
  }
625
- const [entryPoint = null] = positionals;
698
+ const [entryPoint = null] = positionals.slice(-1);
626
699
  if (!entryPoint) {
627
700
  throw new Error("Entry point is required");
628
701
  }
@@ -676,6 +749,7 @@ class PaperMainService {
676
749
  },
677
750
  },
678
751
  });
752
+ notifyVerbose();
679
753
  }
680
754
  backtestKit.Live.background(symbol, {
681
755
  strategyName,
@@ -692,7 +766,7 @@ class PaperMainService {
692
766
  if (!values.paper) {
693
767
  return;
694
768
  }
695
- const [entryPoint = null] = positionals;
769
+ const [entryPoint = null] = positionals.slice(-1);
696
770
  if (!entryPoint) {
697
771
  throw new Error("Entry point is required");
698
772
  }
package/build/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
- import { Storage, Notification, Markdown, Report, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenSignal, listenStrategyCommit } from 'backtest-kit';
1
+ #!/usr/bin/env node
2
+ import { Storage, Notification, Markdown, Report, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit } from 'backtest-kit';
2
3
  import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString, createAwaiter, TIMEOUT_SYMBOL, typo, retry, memoize, trycatch } from 'functools-kit';
3
4
  import fs, { constants } from 'fs';
4
5
  import * as stackTrace from 'stack-trace';
5
6
  import { fileURLToPath, pathToFileURL } from 'url';
7
+ import { createRequire } from 'module';
6
8
  import path from 'path';
7
9
  import fs$1, { access } from 'fs/promises';
8
10
  import dotenv from 'dotenv';
@@ -19,7 +21,6 @@ import MarkdownIt from 'markdown-it';
19
21
  import sanitizeHtml from 'sanitize-html';
20
22
  import { JSDOM } from 'jsdom';
21
23
  import Mustache from 'mustache';
22
- import { createRequire } from 'module';
23
24
 
24
25
  /**
25
26
  * Fix for `Attempted to assign to readonly property (at redactToken)`
@@ -61,7 +62,7 @@ import { createRequire } from 'module';
61
62
  }
62
63
  {
63
64
  StorageLive.usePersist();
64
- StorageBacktest.useDummy();
65
+ StorageBacktest.useMemory();
65
66
  }
66
67
  {
67
68
  NotificationLive.useDummy();
@@ -209,6 +210,24 @@ const entrySubject = new BehaviorSubject();
209
210
 
210
211
  const __filename = fileURLToPath(import.meta.url);
211
212
  const __dirname = path.dirname(__filename);
213
+ const require$1 = createRequire(import.meta.url);
214
+ const REQUIRE_ENTRY_FACTORY = (filePath) => {
215
+ try {
216
+ require$1(filePath);
217
+ return true;
218
+ }
219
+ catch {
220
+ return false;
221
+ }
222
+ };
223
+ const IMPORT_ENTRY_FACTORY = async (filePath) => {
224
+ await import(pathToFileURL(filePath).href);
225
+ };
226
+ const LOAD_ENTRY_FN = async (filePath) => {
227
+ if (!REQUIRE_ENTRY_FACTORY(filePath)) {
228
+ await IMPORT_ENTRY_FACTORY(filePath);
229
+ }
230
+ };
212
231
  let _is_launched = false;
213
232
  class ResolveService {
214
233
  constructor() {
@@ -227,9 +246,9 @@ class ResolveService {
227
246
  {
228
247
  const cwd = process.cwd();
229
248
  process.chdir(moduleRoot);
230
- dotenv.config({ path: path.join(cwd, '.env') });
231
- dotenv.config({ path: path.join(moduleRoot, '.env'), override: true });
232
- await import(pathToFileURL(absolutePath).href);
249
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
250
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
251
+ await LOAD_ENTRY_FN(absolutePath);
233
252
  await entrySubject.next(absolutePath);
234
253
  }
235
254
  _is_launched = true;
@@ -372,7 +391,11 @@ const getArgs = singleshot(() => {
372
391
  type: "boolean",
373
392
  default: false,
374
393
  },
375
- cache: {
394
+ noCache: {
395
+ type: "boolean",
396
+ default: false,
397
+ },
398
+ cacheInterval: {
376
399
  type: "string",
377
400
  default: "1m, 15m, 30m, 4h",
378
401
  },
@@ -436,17 +459,64 @@ const notifyFinish = singleshot(() => {
436
459
  });
437
460
 
438
461
  const getEntry = (metaUrl) => {
439
- return process.argv[1] === new URL(metaUrl).pathname;
462
+ const metaPath = fileURLToPath(metaUrl);
463
+ return path.resolve(process.argv[1]) === path.resolve(metaPath);
440
464
  };
441
465
 
466
+ const notifyVerbose = singleshot(() => {
467
+ console.log("Using verbose logging...");
468
+ listenSignal((event) => {
469
+ if (event.action === "scheduled") {
470
+ console.log(`[POSITION SCHEDULED] ${event.symbol}`);
471
+ console.log(` Strategy: ${event.strategyName}`);
472
+ console.log(` Current Price: ${event.currentPrice}`);
473
+ console.log(` Entry Price: ${event.signal.priceOpen}`);
474
+ console.log(` Signal ID: ${event.signal.id}`);
475
+ console.log(` Direction: ${event.signal.position}`);
476
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
477
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
478
+ return;
479
+ }
480
+ if (event.action === "opened") {
481
+ console.log(`[POSITION OPENED] ${event.symbol}`);
482
+ console.log(` Strategy: ${event.strategyName}`);
483
+ console.log(` Entry Price: ${event.currentPrice}`);
484
+ console.log(` Signal ID: ${event.signal.id}`);
485
+ console.log(` Direction: ${event.signal.position}`);
486
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
487
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
488
+ return;
489
+ }
490
+ if (event.action === "closed") {
491
+ console.log(`[POSITION CLOSED] ${event.symbol}`);
492
+ console.log(` Strategy: ${event.strategyName}`);
493
+ console.log(` Entry Price (adj): ${event.pnl.priceOpen}`);
494
+ console.log(` Exit Price (adj): ${event.pnl.priceClose}`);
495
+ console.log(` Signal ID: ${event.signal.id}`);
496
+ console.log(` Close Reason: ${event.closeReason}`);
497
+ console.log(` PnL: ${event.pnl.pnlPercentage.toFixed(2)}%`);
498
+ console.log(` Win: ${event.pnl.pnlPercentage > 0 ? "YES" : "NO"}`);
499
+ return;
500
+ }
501
+ if (event.action === "cancelled") {
502
+ console.log(`[POSITION CANCELLED] ${event.symbol}`);
503
+ console.log(` Strategy: ${event.strategyName}`);
504
+ console.log(` Signal ID: ${event.signal.id}`);
505
+ console.log(` Current Price: ${event.currentPrice}`);
506
+ console.log(` Cancel Reason: ${event.reason}`);
507
+ console.log(` Cancelled At: ${new Date(event.closeTimestamp).toISOString()}`);
508
+ return;
509
+ }
510
+ });
511
+ });
512
+
442
513
  const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
443
- const GET_CACHE_LIST_FN = () => {
514
+ const GET_CACHE_INTERVAL_LIST_FN = () => {
444
515
  const { values } = getArgs();
445
- if (!values.cache) {
446
- console.warn(`Warning: No cache timeframes provided. Using default timeframes: ${DEFAULT_CACHE_LIST.join(", ")}`);
516
+ if (!values.cacheInterval) {
447
517
  return DEFAULT_CACHE_LIST;
448
518
  }
449
- return String(values.cache)
519
+ return String(values.cacheInterval)
450
520
  .split(",")
451
521
  .map((timeframe) => timeframe.trim());
452
522
  };
@@ -490,8 +560,8 @@ class BacktestMainService {
490
560
  if (!frameName) {
491
561
  throw new Error("Frame name is required");
492
562
  }
493
- {
494
- await this.cacheLogicService.execute(payload.cacheList, {
563
+ if (!payload.noCache) {
564
+ await this.cacheLogicService.execute(payload.cacheInterval, {
495
565
  exchangeName,
496
566
  frameName,
497
567
  symbol,
@@ -506,6 +576,7 @@ class BacktestMainService {
506
576
  },
507
577
  },
508
578
  });
579
+ notifyVerbose();
509
580
  }
510
581
  Backtest.background(symbol, {
511
582
  strategyName,
@@ -523,19 +594,20 @@ class BacktestMainService {
523
594
  if (!values.backtest) {
524
595
  return;
525
596
  }
526
- const [entryPoint = null] = positionals;
597
+ const [entryPoint = null] = positionals.slice(-1);
527
598
  if (!entryPoint) {
528
599
  throw new Error("Entry point is required");
529
600
  }
530
- const cacheList = GET_CACHE_LIST_FN();
601
+ const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
531
602
  return await this.run({
532
603
  symbol: values.symbol,
533
604
  entryPoint,
534
- cacheList,
605
+ cacheInterval,
535
606
  exchange: values.exchange,
536
607
  frame: values.frame,
537
608
  strategy: values.strategy,
538
609
  verbose: values.verbose,
610
+ noCache: values.noCache,
539
611
  });
540
612
  });
541
613
  }
@@ -584,6 +656,7 @@ class LiveMainService {
584
656
  },
585
657
  },
586
658
  });
659
+ notifyVerbose();
587
660
  }
588
661
  Live.background(symbol, {
589
662
  strategyName,
@@ -600,7 +673,7 @@ class LiveMainService {
600
673
  if (!values.live) {
601
674
  return;
602
675
  }
603
- const [entryPoint = null] = positionals;
676
+ const [entryPoint = null] = positionals.slice(-1);
604
677
  if (!entryPoint) {
605
678
  throw new Error("Entry point is required");
606
679
  }
@@ -654,6 +727,7 @@ class PaperMainService {
654
727
  },
655
728
  },
656
729
  });
730
+ notifyVerbose();
657
731
  }
658
732
  Live.background(symbol, {
659
733
  strategyName,
@@ -670,7 +744,7 @@ class PaperMainService {
670
744
  if (!values.paper) {
671
745
  return;
672
746
  }
673
- const [entryPoint = null] = positionals;
747
+ const [entryPoint = null] = positionals.slice(-1);
674
748
  if (!entryPoint) {
675
749
  throw new Error("Entry point is required");
676
750
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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",
@@ -59,6 +59,7 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@backtest-kit/ui": "3.1.0",
62
+ "markdown-it": "14.1.1",
62
63
  "@rollup/plugin-typescript": "11.1.6",
63
64
  "@types/image-size": "0.7.0",
64
65
  "@types/jsdom": "21.1.7",
@@ -77,7 +78,9 @@
77
78
  "worker-testbed": "1.0.12"
78
79
  },
79
80
  "peerDependencies": {
80
- "typescript": "^5.0.0"
81
+ "typescript": "^5.0.0",
82
+ "@backtest-kit/ui": "^3.1.0",
83
+ "markdown-it": "^14.1.1"
81
84
  },
82
85
  "dependencies": {
83
86
  "ccxt": "4.5.39",
package/types.d.ts CHANGED
@@ -68,8 +68,9 @@ declare class BacktestMainService {
68
68
  strategy: string;
69
69
  exchange: string;
70
70
  frame: string;
71
- cacheList: string[];
71
+ cacheInterval: string[];
72
72
  verbose: boolean;
73
+ noCache: boolean;
73
74
  }) => Promise<void>) & functools_kit.ISingleshotClearable;
74
75
  connect: (() => Promise<void>) & functools_kit.ISingleshotClearable;
75
76
  }