@backtest-kit/cli 3.2.0 โ†’ 3.3.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
@@ -49,9 +49,9 @@ npm install @backtest-kit/cli
49
49
  ```json
50
50
  {
51
51
  "scripts": {
52
- "backtest": "@backtest-kit/cli --backtest ./src/index.mjs",
53
- "paper": "@backtest-kit/cli --paper ./src/index.mjs",
54
- "start": "@backtest-kit/cli --live ./src/index.mjs"
52
+ "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs",
53
+ "paper": "npx @backtest-kit/cli --paper ./src/index.mjs",
54
+ "start": "npx @backtest-kit/cli --live ./src/index.mjs"
55
55
  },
56
56
  "dependencies": {
57
57
  "@backtest-kit/cli": "latest",
@@ -149,7 +149,7 @@ npm start -- --symbol BTCUSDT --ui
149
149
  ```json
150
150
  {
151
151
  "scripts": {
152
- "backtest": "@backtest-kit/cli --backtest ./src/index.mjs"
152
+ "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
153
153
  }
154
154
  }
155
155
  ```
@@ -163,7 +163,7 @@ Runs the strategy against historical candle data using a registered `FrameSchema
163
163
  ```json
164
164
  {
165
165
  "scripts": {
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
+ "backtest": "npx @backtest-kit/cli --backtest --symbol ETHUSDT --strategy my-strategy --exchange binance --frame feb-2024 --cacheInterval \"1m, 15m, 1h, 4h\" ./src/index.mjs"
167
167
  }
168
168
  }
169
169
  ```
@@ -181,7 +181,7 @@ Connects to the live exchange but does not place real orders. Identical code pat
181
181
  ```json
182
182
  {
183
183
  "scripts": {
184
- "paper": "@backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs"
184
+ "paper": "npx @backtest-kit/cli --paper --symbol BTCUSDT ./src/index.mjs"
185
185
  }
186
186
  }
187
187
  ```
@@ -197,7 +197,7 @@ Deploys a real trading bot. Requires exchange API keys configured in your `.env`
197
197
  ```json
198
198
  {
199
199
  "scripts": {
200
- "start": "@backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs"
200
+ "start": "npx @backtest-kit/cli --live --ui --telegram --symbol BTCUSDT ./src/index.mjs"
201
201
  }
202
202
  }
203
203
  ```
@@ -246,8 +246,8 @@ monorepo/
246
246
  ```json
247
247
  {
248
248
  "scripts": {
249
- "backtest:oct": "@backtest-kit/cli --backtest ./strategies/oct_2025/index.mjs",
250
- "backtest:dec": "@backtest-kit/cli --backtest ./strategies/dec_2025/index.mjs"
249
+ "backtest:oct": "npx @backtest-kit/cli --backtest ./strategies/oct_2025/index.mjs",
250
+ "backtest:dec": "npx @backtest-kit/cli --backtest ./strategies/dec_2025/index.mjs"
251
251
  },
252
252
  "dependencies": {
253
253
  "@backtest-kit/cli": "latest",
@@ -506,7 +506,7 @@ Markdown.disable();
506
506
  **โœ… With @backtest-kit/cli (one script)**
507
507
 
508
508
  ```json
509
- { "scripts": { "backtest": "@backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
509
+ { "scripts": { "backtest": "npx @backtest-kit/cli --backtest --ui --telegram ./src/index.mjs" } }
510
510
  ```
511
511
 
512
512
  ```bash
package/build/index.cjs CHANGED
@@ -90,6 +90,10 @@ var stackTrace__namespace = /*#__PURE__*/_interopNamespaceDefault(stackTrace);
90
90
  backtestKit.NotificationLive.usePersist();
91
91
  backtestKit.NotificationBacktest.useMemory();
92
92
  }
93
+ backtestKit.setConfig({
94
+ CC_MAX_NOTIFICATIONS: 5000,
95
+ CC_MAX_SIGNALS: 750,
96
+ });
93
97
 
94
98
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
95
99
  function dumpStackTrace() {
@@ -1443,6 +1447,16 @@ class TelegramLogicService {
1443
1447
  markdown,
1444
1448
  });
1445
1449
  };
1450
+ this.notifyAverageBuy = async (event) => {
1451
+ this.loggerService.log("telegramLogicService notifyAverageBuy", {
1452
+ event,
1453
+ });
1454
+ const markdown = await this.telegramTemplateService.getAverageBuyMarkdown(event);
1455
+ await this.telegramWebService.publishNotify({
1456
+ symbol: event.symbol,
1457
+ markdown,
1458
+ });
1459
+ };
1446
1460
  this.connect = functoolsKit.singleshot(() => {
1447
1461
  this.loggerService.log("telegramLogicService connect");
1448
1462
  const unRisk = backtestKit.listenRisk(async (event) => {
@@ -1487,6 +1501,10 @@ class TelegramLogicService {
1487
1501
  await this.notifyPartialLoss(event);
1488
1502
  return;
1489
1503
  }
1504
+ if (event.action === "average-buy") {
1505
+ await this.notifyAverageBuy(event);
1506
+ return;
1507
+ }
1490
1508
  });
1491
1509
  const unConnect = () => this.connect.clear();
1492
1510
  const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
@@ -1578,6 +1596,12 @@ class TelegramTemplateService {
1578
1596
  });
1579
1597
  return await RENDER_TEMPLATE_FN("risk.mustache", event, this);
1580
1598
  };
1599
+ this.getAverageBuyMarkdown = async (event) => {
1600
+ this.loggerService.log("telegramTemplateService getAverageBuyMarkdown", {
1601
+ event,
1602
+ });
1603
+ return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
1604
+ };
1581
1605
  }
1582
1606
  }
1583
1607
 
@@ -1737,6 +1761,15 @@ class LiveProviderService {
1737
1761
  await instance.onRisk(event);
1738
1762
  }
1739
1763
  };
1764
+ this.handleAverageBuy = async (event) => {
1765
+ this.loggerService.log("liveProviderService handleAverageBuy", {
1766
+ event,
1767
+ });
1768
+ const instance = await LOAD_INSTANCE_FN(this);
1769
+ if (instance?.onAverageBuy) {
1770
+ await instance.onAverageBuy(event);
1771
+ }
1772
+ };
1740
1773
  this.enable = functoolsKit.singleshot(() => {
1741
1774
  this.loggerService.log("liveProviderService enable");
1742
1775
  LOAD_INSTANCE_FN(this).then((module) => {
@@ -1789,6 +1822,10 @@ class LiveProviderService {
1789
1822
  await this.handlePartialLoss(event);
1790
1823
  return;
1791
1824
  }
1825
+ if (event.action === "average-buy") {
1826
+ await this.handleAverageBuy(event);
1827
+ return;
1828
+ }
1792
1829
  });
1793
1830
  const unConnect = () => this.enable.clear();
1794
1831
  return functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
package/build/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
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
+ import { Storage, Notification, Markdown, Report, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, setConfig, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit } from 'backtest-kit';
3
3
  import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString, createAwaiter, TIMEOUT_SYMBOL, typo, retry, memoize, trycatch } from 'functools-kit';
4
4
  import fs, { constants } from 'fs';
5
5
  import * as stackTrace from 'stack-trace';
@@ -68,6 +68,10 @@ import Mustache from 'mustache';
68
68
  NotificationLive.usePersist();
69
69
  NotificationBacktest.useMemory();
70
70
  }
71
+ setConfig({
72
+ CC_MAX_NOTIFICATIONS: 5000,
73
+ CC_MAX_SIGNALS: 750,
74
+ });
71
75
 
72
76
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
73
77
  function dumpStackTrace() {
@@ -1421,6 +1425,16 @@ class TelegramLogicService {
1421
1425
  markdown,
1422
1426
  });
1423
1427
  };
1428
+ this.notifyAverageBuy = async (event) => {
1429
+ this.loggerService.log("telegramLogicService notifyAverageBuy", {
1430
+ event,
1431
+ });
1432
+ const markdown = await this.telegramTemplateService.getAverageBuyMarkdown(event);
1433
+ await this.telegramWebService.publishNotify({
1434
+ symbol: event.symbol,
1435
+ markdown,
1436
+ });
1437
+ };
1424
1438
  this.connect = singleshot(() => {
1425
1439
  this.loggerService.log("telegramLogicService connect");
1426
1440
  const unRisk = listenRisk(async (event) => {
@@ -1465,6 +1479,10 @@ class TelegramLogicService {
1465
1479
  await this.notifyPartialLoss(event);
1466
1480
  return;
1467
1481
  }
1482
+ if (event.action === "average-buy") {
1483
+ await this.notifyAverageBuy(event);
1484
+ return;
1485
+ }
1468
1486
  });
1469
1487
  const unConnect = () => this.connect.clear();
1470
1488
  const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
@@ -1556,6 +1574,12 @@ class TelegramTemplateService {
1556
1574
  });
1557
1575
  return await RENDER_TEMPLATE_FN("risk.mustache", event, this);
1558
1576
  };
1577
+ this.getAverageBuyMarkdown = async (event) => {
1578
+ this.loggerService.log("telegramTemplateService getAverageBuyMarkdown", {
1579
+ event,
1580
+ });
1581
+ return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
1582
+ };
1559
1583
  }
1560
1584
  }
1561
1585
 
@@ -1715,6 +1739,15 @@ class LiveProviderService {
1715
1739
  await instance.onRisk(event);
1716
1740
  }
1717
1741
  };
1742
+ this.handleAverageBuy = async (event) => {
1743
+ this.loggerService.log("liveProviderService handleAverageBuy", {
1744
+ event,
1745
+ });
1746
+ const instance = await LOAD_INSTANCE_FN(this);
1747
+ if (instance?.onAverageBuy) {
1748
+ await instance.onAverageBuy(event);
1749
+ }
1750
+ };
1718
1751
  this.enable = singleshot(() => {
1719
1752
  this.loggerService.log("liveProviderService enable");
1720
1753
  LOAD_INSTANCE_FN(this).then((module) => {
@@ -1767,6 +1800,10 @@ class LiveProviderService {
1767
1800
  await this.handlePartialLoss(event);
1768
1801
  return;
1769
1802
  }
1803
+ if (event.action === "average-buy") {
1804
+ await this.handleAverageBuy(event);
1805
+ return;
1806
+ }
1770
1807
  });
1771
1808
  const unConnect = () => this.enable.clear();
1772
1809
  return compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "3.2.0",
3
+ "version": "3.3.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",
@@ -58,7 +58,7 @@
58
58
  "default": "./build/index.cjs"
59
59
  },
60
60
  "devDependencies": {
61
- "@backtest-kit/ui": "3.2.0",
61
+ "@backtest-kit/ui": "3.3.2",
62
62
  "markdown-it": "14.1.1",
63
63
  "@rollup/plugin-typescript": "11.1.6",
64
64
  "@types/image-size": "0.7.0",
@@ -66,7 +66,7 @@
66
66
  "@types/mustache": "4.2.6",
67
67
  "@types/node": "22.9.0",
68
68
  "@types/stack-trace": "0.0.33",
69
- "backtest-kit": "3.2.0",
69
+ "backtest-kit": "3.3.2",
70
70
  "glob": "11.0.1",
71
71
  "rimraf": "6.0.1",
72
72
  "rollup": "3.29.5",
@@ -79,8 +79,8 @@
79
79
  },
80
80
  "peerDependencies": {
81
81
  "typescript": "^5.0.0",
82
- "@backtest-kit/ui": "^3.2.0",
83
- "backtest-kit": "^3.2.0",
82
+ "@backtest-kit/ui": "^3.3.2",
83
+ "backtest-kit": "^3.3.2",
84
84
  "markdown-it": "^14.1.1"
85
85
  },
86
86
  "dependencies": {
@@ -0,0 +1,16 @@
1
+ **๐Ÿช“ Averaging Entry (DCA)**
2
+
3
+ **Symbol:** `{{symbol}}` ({{position}})
4
+
5
+ **Current:** `{{currentPrice}}`
6
+ **New Avg Entry:** `{{effectivePriceOpen}}`
7
+ **Orig Entry:** `{{originalPriceOpen}}`
8
+ **DCA Entries:** `{{totalEntries}}`
9
+ **Take Profit:** `{{priceTakeProfit}}`
10
+ **Stop Loss:** `{{priceStopLoss}}`
11
+ **Signal ID:** `{{signalId}}`
12
+ **Time:** `{{timestamp}}`
13
+
14
+ {{#backtest}}
15
+ _๐Ÿงช Backtest_
16
+ {{/backtest}}
@@ -6,6 +6,8 @@
6
6
  **Entry:** `{{priceOpen}}`
7
7
  **Take Profit:** `{{priceTakeProfit}}`
8
8
  **Stop Loss:** `{{priceStopLoss}}`
9
+ **Orig Entry:** `{{originalPriceOpen}}`
10
+ **DCA Entries:** `{{totalEntries}}`
9
11
  **Signal ID:** `{{signalId}}`
10
12
  **Time:** `{{timestamp}}`
11
13
 
@@ -5,6 +5,8 @@
5
5
  **Reason:** `{{reason}}`
6
6
  **Entry:** `{{signal.priceOpen}}`
7
7
  **Current:** `{{currentPrice}}`
8
+ **Orig Entry:** `{{signal.originalPriceOpen}}`
9
+ **DCA Entries:** `{{signal.totalEntries}}`
8
10
  **Signal ID:** `{{signal.id}}`
9
11
  {{#cancelId}}**Cancel ID:** `{{cancelId}}`
10
12
  {{/cancelId}}**Time:** `{{createdAt}}`
@@ -6,6 +6,8 @@
6
6
  **Entry:** `{{pnl.priceOpen}}`
7
7
  **Exit:** `{{pnl.priceClose}}`
8
8
  **PnL:** {{pnl.pnlPercentage}}%
9
+ **Orig Entry:** `{{signal.originalPriceOpen}}`
10
+ **DCA Entries:** `{{signal.totalEntries}}`
9
11
  **Signal ID:** `{{signal.id}}`
10
12
  {{#closeId}}**Close ID:** `{{closeId}}`
11
13
  {{/closeId}}**Time:** `{{createdAt}}`
@@ -6,6 +6,8 @@
6
6
  **Entry:** `{{signal.priceOpen}}`
7
7
  **Take Profit:** `{{signal.priceTakeProfit}}`
8
8
  **Stop Loss:** `{{signal.priceStopLoss}}`
9
+ **Orig Entry:** `{{signal.originalPriceOpen}}`
10
+ **DCA Entries:** `{{signal.totalEntries}}`
9
11
  **TTL:** {{signal.minuteEstimatedTime}} min
10
12
  **Signal ID:** `{{signal.id}}`
11
13
  **Time:** `{{createdAt}}`
@@ -7,6 +7,8 @@
7
7
  **Closed:** {{percentToClose}}%
8
8
  **Take Profit:** `{{priceTakeProfit}}`
9
9
  **Stop Loss:** `{{priceStopLoss}}`
10
+ **Orig Entry:** `{{originalPriceOpen}}`
11
+ **DCA Entries:** `{{totalEntries}}`
10
12
  **Signal ID:** `{{signalId}}`
11
13
  **Time:** `{{timestamp}}`
12
14
 
@@ -7,6 +7,8 @@
7
7
  **Closed:** {{percentToClose}}%
8
8
  **Take Profit:** `{{priceTakeProfit}}`
9
9
  **Stop Loss:** `{{priceStopLoss}}`
10
+ **Orig Entry:** `{{originalPriceOpen}}`
11
+ **DCA Entries:** `{{totalEntries}}`
10
12
  **Signal ID:** `{{signalId}}`
11
13
  **Time:** `{{timestamp}}`
12
14
 
@@ -6,6 +6,8 @@
6
6
  **Entry:** `{{currentSignal.priceOpen}}`
7
7
  **Take Profit:** `{{currentSignal.priceTakeProfit}}`
8
8
  **Stop Loss:** `{{currentSignal.priceStopLoss}}`
9
+ **Orig Entry:** `{{currentSignal.originalPriceOpen}}`
10
+ **DCA Entries:** `{{currentSignal.totalEntries}}`
9
11
  **Active positions:** {{activePositionCount}}
10
12
  {{#rejectionId}}**Rejection ID:** `{{rejectionId}}`
11
13
  {{/rejectionId}}**Time:** `{{timestamp}}`
@@ -6,6 +6,8 @@
6
6
  **Entry:** `{{signal.priceOpen}}`
7
7
  **Take Profit:** `{{signal.priceTakeProfit}}`
8
8
  **Stop Loss:** `{{signal.priceStopLoss}}`
9
+ **Orig Entry:** `{{signal.originalPriceOpen}}`
10
+ **DCA Entries:** `{{signal.totalEntries}}`
9
11
  **TTL:** {{signal.minuteEstimatedTime}} min
10
12
  **Signal ID:** `{{signal.id}}`
11
13
  **Time:** `{{createdAt}}`
@@ -7,6 +7,8 @@
7
7
  **Take Profit:** `{{priceTakeProfit}}`
8
8
  **Old Stop Loss:** `{{originalPriceStopLoss}}`
9
9
  **New Stop Loss:** `{{priceStopLoss}}` ({{percentShift}}%)
10
+ **Orig Entry:** `{{originalPriceOpen}}`
11
+ **DCA Entries:** `{{totalEntries}}`
10
12
  **Signal ID:** `{{signalId}}`
11
13
  **Time:** `{{timestamp}}`
12
14
 
@@ -7,6 +7,8 @@
7
7
  **Old Take Profit:** `{{originalPriceTakeProfit}}`
8
8
  **New Take Profit:** `{{priceTakeProfit}}` ({{percentShift}}%)
9
9
  **Stop Loss:** `{{priceStopLoss}}`
10
+ **Orig Entry:** `{{originalPriceOpen}}`
11
+ **DCA Entries:** `{{totalEntries}}`
10
12
  **Signal ID:** `{{signalId}}`
11
13
  **Time:** `{{timestamp}}`
12
14
 
package/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as functools_kit from 'functools-kit';
2
- import { CandleInterval, TrailingTakeCommit, TrailingStopCommit, BreakevenCommit, PartialProfitCommit, PartialLossCommit, IStrategyTickResultScheduled, IStrategyTickResultCancelled, IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract } from 'backtest-kit';
2
+ import { CandleInterval, TrailingTakeCommit, TrailingStopCommit, BreakevenCommit, PartialProfitCommit, PartialLossCommit, IStrategyTickResultScheduled, IStrategyTickResultCancelled, IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract, AverageBuyCommit } from 'backtest-kit';
3
3
  import { Input } from 'telegraf';
4
4
 
5
5
  interface ILogger {
@@ -163,6 +163,7 @@ declare class TelegramLogicService {
163
163
  private notifyOpened;
164
164
  private notifyClosed;
165
165
  private notifyRisk;
166
+ private notifyAverageBuy;
166
167
  connect: (() => () => void) & functools_kit.ISingleshotClearable;
167
168
  }
168
169
 
@@ -179,6 +180,7 @@ declare class TelegramTemplateService {
179
180
  getOpenedMarkdown: (event: IStrategyTickResultOpened) => Promise<string>;
180
181
  getClosedMarkdown: (event: IStrategyTickResultClosed) => Promise<string>;
181
182
  getRiskMarkdown: (event: RiskContract) => Promise<string>;
183
+ getAverageBuyMarkdown: (event: AverageBuyCommit) => Promise<string>;
182
184
  }
183
185
 
184
186
  interface ILiveModule {
@@ -192,6 +194,7 @@ interface ILiveModule {
192
194
  onOpened(event: IStrategyTickResultOpened): Promise<void> | void;
193
195
  onClosed(event: IStrategyTickResultClosed): Promise<void> | void;
194
196
  onRisk(event: RiskContract): Promise<void> | void;
197
+ onAverageBuy(event: AverageBuyCommit): Promise<void> | void;
195
198
  }
196
199
  type LiveModule = Partial<ILiveModule>;
197
200
  type BaseModule = LiveModule;
@@ -216,6 +219,7 @@ declare class LiveProviderService {
216
219
  private handleOpened;
217
220
  private handleClosed;
218
221
  private handleRisk;
222
+ private handleAverageBuy;
219
223
  enable: (() => (...args: any[]) => any) & functools_kit.ISingleshotClearable;
220
224
  disable: () => void;
221
225
  connect: (() => Promise<() => void>) & functools_kit.ISingleshotClearable;