@backtest-kit/cli 3.8.0 → 4.0.1

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
@@ -22,7 +22,8 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
22
22
  - 🌐 **Web Dashboard**: Launch `@backtest-kit/ui` with a single `--ui` flag
23
23
  - 📬 **Telegram Alerts**: Send formatted trade notifications with charts via `--telegram`
24
24
  - 🔌 **Default Binance**: CCXT Binance exchange schema registered automatically when none is provided
25
- - 🧩 **Module Hooks**: Drop a `modules/live.module.mjs` file to handle every position lifecycle event
25
+ - 🧩 **Module Hooks**: Drop a `live.module.mjs`, `paper.module.mjs`, or `backtest.module.mjs` to register a `Broker` adapter. No manual wiring needed.
26
+ - 🗃️ **Transactional Live Orders**: Broker adapter intercepts every trade mutation before internal state changes — exchange rejection rolls back the operation atomically.
26
27
  - 🔑 **Pluggable Logger**: Override the built-in logger with `setLogger()` from your strategy module
27
28
  - 🛑 **Graceful Shutdown**: SIGINT stops the active run and cleans up all subscriptions safely
28
29
 
@@ -230,11 +231,14 @@ monorepo/
230
231
  ├── .env # shared API keys (exchange, Telegram, etc.)
231
232
  └── strategies/
232
233
  ├── oct_2025/
233
- │ ├── index.mjs # entry point — registers exchange/frame/strategy schemas
234
- │ ├── .env # overrides root .env for this strategy (optional)
235
- │ ├── modules/ # live.module.mjs specific to this strategy
236
- │ ├── template/ # custom Mustache templates (optional)
237
- └── dump/ # auto-created: candle cache + backtest reports
234
+ │ ├── index.mjs # entry point — registers exchange/frame/strategy schemas
235
+ │ ├── .env # overrides root .env for this strategy
236
+ │ ├── modules (optional)
237
+ | ├── live.module.mjs # broker adapter for --live mode (optional)
238
+ | ├── paper.module.mjs # broker adapter for --paper mode (optional)
239
+ │ | ├── backtest.module.mjs # broker adapter for --backtest mode (optional)
240
+ │ ├── template/ # custom Mustache templates (optional)
241
+ │ └── dump/ # auto-created: candle cache + backtest reports
238
242
  └── dec_2025/
239
243
  ├── index.mjs
240
244
  ├── .env
@@ -264,13 +268,15 @@ npm run backtest:dec
264
268
 
265
269
  ### Isolated Resources Per Strategy
266
270
 
267
- | Resource | Path (relative to strategy dir) | Isolated |
268
- |---------------------|-----------------------------------|------------------|
269
- | Candle cache | `./dump/data/candle/` | ✅ per-strategy |
270
- | Backtest reports | `./dump/` | ✅ per-strategy |
271
- | Live module | `./modules/live.module.mjs` | ✅ per-strategy |
272
- | Telegram templates | `./template/*.mustache` | ✅ per-strategy |
273
- | Environment variables | `./.env` (overrides root) | ✅ per-strategy |
271
+ | Resource | Path (relative to strategy dir) | Isolated |
272
+ |--------------------------|-----------------------------------|------------------|
273
+ | Candle cache | `./dump/data/candle/` | ✅ per-strategy |
274
+ | Backtest reports | `./dump/` | ✅ per-strategy |
275
+ | Broker module (live) | `./modules/live.module.mjs` | ✅ per-strategy |
276
+ | Broker module (paper) | `./modules/paper.module.mjs` | ✅ per-strategy |
277
+ | Broker module (backtest) | `./modules/backtest.module.mjs` | ✅ per-strategy |
278
+ | Telegram templates | `./template/*.mustache` | ✅ per-strategy |
279
+ | Environment variables | `./.env` (overrides root) | ✅ per-strategy |
274
280
 
275
281
  Each strategy run produces its own `dump/` directory, making it straightforward to compare results across time periods — both by inspection and by pointing an AI agent at a specific strategy folder.
276
282
 
@@ -292,72 +298,91 @@ Sends formatted HTML messages with 1m / 15m / 1h price charts to your Telegram c
292
298
 
293
299
  Requires `CC_TELEGRAM_TOKEN` and `CC_TELEGRAM_CHANNEL` in your environment.
294
300
 
295
- ## 🧩 Live Module Hooks
301
+ ## 🧩 Module Hooks (Broker Adapter)
296
302
 
297
- Create a `modules/live.module.mjs` file in your **project root** to receive lifecycle callbacks for every trading event:
303
+ The CLI supports **mode-specific module files** that are loaded as side-effect imports before the strategy starts. Each file is expected to call `Broker.useBrokerAdapter()` from `backtest-kit` to register a broker adapter.
298
304
 
299
- ```javascript
300
- // modules/live.module.mjs
305
+ | Mode | Module file | Loaded before |
306
+ |---------------|---------------------------------|-----------------------------|
307
+ | `--live` | `./modules/live.module.mjs` | `Live.background()` |
308
+ | `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
309
+ | `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
301
310
 
302
- export default class {
311
+ > File is resolved relative to `cwd` (the strategy directory). All of `.mjs`, `.cjs`, `.ts` extensions are tried automatically. Missing module is a soft warning — not an error.
303
312
 
304
- onOpened(event) {
305
- console.log('Position opened', event.symbol, event.priceOpen);
306
- }
313
+ ### How It Works
307
314
 
308
- onClosed(event) {
309
- console.log('Position closed', event.symbol, event.priceClosed);
310
- }
315
+ The module file is a side-effect import. When the CLI loads it, your code runs and registers the adapter. From that point on, `backtest-kit` intercepts every trade-mutating call through the adapter **before** updating internal state — if the adapter throws, the position state is never changed.
311
316
 
312
- onScheduled(event) {
313
- console.log('Signal scheduled', event.id);
314
- }
317
+ ```javascript
318
+ // live.module.mjs
319
+ import { Broker } from 'backtest-kit';
320
+ import { myExchange } from './exchange.mjs';
315
321
 
316
- onCancelled(event) {
317
- console.log('Signal cancelled', event.id);
322
+ class MyBroker {
323
+ async onSignalOpenCommit({ symbol, priceOpen, direction }) {
324
+ await myExchange.openPosition(symbol, direction, priceOpen);
318
325
  }
319
326
 
320
- onRisk(event) {
321
- console.warn('Risk rejection', event.reason);
327
+ async onSignalCloseCommit({ symbol, priceClosed }) {
328
+ await myExchange.closePosition(symbol, priceClosed);
322
329
  }
323
330
 
324
- onPartialProfit(event) {
325
- console.log('Partial profit taken', event.symbol);
331
+ async onPartialProfitCommit({ symbol, cost, currentPrice }) {
332
+ await myExchange.createOrder({
333
+ symbol,
334
+ side: 'sell',
335
+ quantity: cost / currentPrice,
336
+ });
326
337
  }
327
338
 
328
- onPartialLoss(event) {
329
- console.log('Partial loss taken', event.symbol);
339
+ async onAverageBuyCommit({ symbol, cost, currentPrice }) {
340
+ await myExchange.createOrder({
341
+ symbol,
342
+ side: 'buy',
343
+ quantity: cost / currentPrice,
344
+ });
330
345
  }
346
+ }
331
347
 
332
- onTrailingTake(event) {
333
- console.log('Trailing take adjusted', event.symbol);
334
- }
348
+ Broker.useBrokerAdapter(MyBroker);
335
349
 
336
- onTrailingStop(event) {
337
- console.log('Trailing stop adjusted', event.symbol);
338
- }
350
+ Broker.enable();
351
+ ```
339
352
 
340
- onBreakeven(event) {
341
- console.log('Breakeven triggered', event.symbol);
342
- }
353
+ ### Available Broker Hooks
343
354
 
344
- onAverageBuy(event) {
345
- console.log('Cost averaging (DCA)', event.symbol);
346
- }
347
- }
348
- ```
355
+ | Method | Payload type | Triggered on |
356
+ |--------------------------|------------------------------|---------------------------|
357
+ | `onSignalOpenCommit` | `BrokerSignalOpenPayload` | Position activation |
358
+ | `onSignalCloseCommit` | `BrokerSignalClosePayload` | SL / TP / manual close |
359
+ | `onPartialProfitCommit` | `BrokerPartialProfitPayload` | PP |
360
+ | `onPartialLossCommit` | `BrokerPartialLossPayload` | PL |
361
+ | `onTrailingStopCommit` | `BrokerTrailingStopPayload` | SL adjustment |
362
+ | `onTrailingTakeCommit` | `BrokerTrailingTakePayload` | TP adjustment |
363
+ | `onBreakevenCommit` | `BrokerBreakevenPayload` | SL moved to entry |
364
+ | `onAverageBuyCommit` | `BrokerAverageBuyPayload` | DCA entry |
349
365
 
350
- All methods are optional implement only the events you care about. The module is loaded dynamically from `{cwd}/modules/live.module.mjs` (supports `.cjs` and `.mjs` extensions).
366
+ All methods are optional. Unimplemented hooks are silently skipped. In backtest mode all broker calls are skipped automatically no adapter code runs during backtests.
351
367
 
352
- ### TypeScript Interface
368
+ ### TypeScript
353
369
 
354
370
  ```typescript
355
- import type { ILiveModule } from '@backtest-kit/cli';
371
+ import { Broker, IBroker, BrokerSignalOpenPayload, BrokerSignalClosePayload } from 'backtest-kit';
356
372
 
357
- export default class MyModule implements ILiveModule {
358
- onOpened(event) { /* ... */ }
359
- onClosed(event) { /* ... */ }
373
+ class MyBroker implements Partial<IBroker> {
374
+ async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
375
+ // place open order on exchange
376
+ }
377
+
378
+ async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
379
+ // place close order on exchange
380
+ }
360
381
  }
382
+
383
+ Broker.useBrokerAdapter(MyBroker);
384
+
385
+ Broker.enable();
361
386
  ```
362
387
 
363
388
  ## 📦 Supported Entry Point Formats
@@ -576,7 +601,7 @@ npm run backtest
576
601
  - 📬 Telegram notifications with price charts — no chart code needed
577
602
  - 🛑 Graceful shutdown on SIGINT — no hanging processes
578
603
  - 🔌 Works with any `backtest-kit` strategy file as-is
579
- - 🧩 Module hooks for custom logic without touching the CLI internals
604
+ - 🧩 Broker adapter hooks via side-effect module files no CLI internals to touch
580
605
 
581
606
  ## 🤝 Contribute
582
607
 
package/build/index.cjs CHANGED
@@ -226,7 +226,6 @@ const schemaServices$1 = {
226
226
  const providerServices$1 = {
227
227
  frontendProviderService: Symbol('frontendProviderService'),
228
228
  telegramProviderService: Symbol('telegramProviderService'),
229
- liveProviderService: Symbol('liveProviderService'),
230
229
  };
231
230
  const webServices$1 = {
232
231
  telegramWebService: Symbol('telegramWebService'),
@@ -612,6 +611,7 @@ class BacktestMainService {
612
611
  this.cacheLogicService = inject(TYPES.cacheLogicService);
613
612
  this.frontendProviderService = inject(TYPES.frontendProviderService);
614
613
  this.telegramProviderService = inject(TYPES.telegramProviderService);
614
+ this.moduleConnectionService = inject(TYPES.moduleConnectionService);
615
615
  this.run = functoolsKit.singleshot(async (payload) => {
616
616
  this.loggerService.log("backtestMainService run", {
617
617
  payload,
@@ -660,6 +660,7 @@ class BacktestMainService {
660
660
  });
661
661
  notifyVerbose();
662
662
  }
663
+ await this.moduleConnectionService.loadModule("./backtest.module");
663
664
  BacktestKit.Backtest.background(symbol, {
664
665
  strategyName,
665
666
  frameName,
@@ -703,7 +704,7 @@ class LiveMainService {
703
704
  this.symbolSchemaService = inject(TYPES.symbolSchemaService);
704
705
  this.frontendProviderService = inject(TYPES.frontendProviderService);
705
706
  this.telegramProviderService = inject(TYPES.telegramProviderService);
706
- this.liveProviderService = inject(TYPES.liveProviderService);
707
+ this.moduleConnectionService = inject(TYPES.moduleConnectionService);
707
708
  this.run = functoolsKit.singleshot(async (payload) => {
708
709
  this.loggerService.log("liveMainService run", {
709
710
  payload,
@@ -711,7 +712,6 @@ class LiveMainService {
711
712
  {
712
713
  this.frontendProviderService.connect();
713
714
  this.telegramProviderService.connect();
714
- this.liveProviderService.connect();
715
715
  }
716
716
  await this.resolveService.attachEntryPoint(payload.entryPoint);
717
717
  {
@@ -740,6 +740,7 @@ class LiveMainService {
740
740
  });
741
741
  notifyVerbose();
742
742
  }
743
+ await this.moduleConnectionService.loadModule("./live.module");
743
744
  BacktestKit.Live.background(symbol, {
744
745
  strategyName,
745
746
  exchangeName,
@@ -778,6 +779,7 @@ class PaperMainService {
778
779
  this.symbolSchemaService = inject(TYPES.symbolSchemaService);
779
780
  this.frontendProviderService = inject(TYPES.frontendProviderService);
780
781
  this.telegramProviderService = inject(TYPES.telegramProviderService);
782
+ this.moduleConnectionService = inject(TYPES.moduleConnectionService);
781
783
  this.run = functoolsKit.singleshot(async (payload) => {
782
784
  this.loggerService.log("paperMainService init");
783
785
  {
@@ -811,6 +813,7 @@ class PaperMainService {
811
813
  });
812
814
  notifyVerbose();
813
815
  }
816
+ await this.moduleConnectionService.loadModule("./paper.module");
814
817
  BacktestKit.Live.background(symbol, {
815
818
  strategyName,
816
819
  exchangeName,
@@ -1513,6 +1516,26 @@ class TelegramLogicService {
1513
1516
  markdown,
1514
1517
  });
1515
1518
  };
1519
+ this.notifySignalOpen = functoolsKit.trycatch(async (event) => {
1520
+ this.loggerService.log("telegramLogicService notifySignalOpen", {
1521
+ event,
1522
+ });
1523
+ const markdown = await this.telegramTemplateService.getSignalOpenMarkdown(event);
1524
+ await this.telegramWebService.publishNotify({
1525
+ symbol: event.symbol,
1526
+ markdown,
1527
+ });
1528
+ });
1529
+ this.notifySignalClose = functoolsKit.trycatch(async (event) => {
1530
+ this.loggerService.log("telegramLogicService notifySignalClose", {
1531
+ event,
1532
+ });
1533
+ const markdown = await this.telegramTemplateService.getSignalCloseMarkdown(event);
1534
+ await this.telegramWebService.publishNotify({
1535
+ symbol: event.symbol,
1536
+ markdown,
1537
+ });
1538
+ });
1516
1539
  this.connect = functoolsKit.singleshot(() => {
1517
1540
  this.loggerService.log("telegramLogicService connect");
1518
1541
  const unRisk = BacktestKit.listenRisk(async (event) => {
@@ -1562,8 +1585,18 @@ class TelegramLogicService {
1562
1585
  return;
1563
1586
  }
1564
1587
  });
1588
+ const unSync = BacktestKit.listenSync(async (event) => {
1589
+ if (event.action === "signal-open") {
1590
+ await this.notifySignalOpen(event);
1591
+ return;
1592
+ }
1593
+ if (event.action === "signal-close") {
1594
+ await this.notifySignalClose(event);
1595
+ return;
1596
+ }
1597
+ });
1565
1598
  const unConnect = () => this.connect.clear();
1566
- const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
1599
+ const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unSync(), () => unConnect());
1567
1600
  return () => {
1568
1601
  STOP_BOT_FN();
1569
1602
  unListen();
@@ -1658,6 +1691,18 @@ class TelegramTemplateService {
1658
1691
  });
1659
1692
  return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
1660
1693
  };
1694
+ this.getSignalOpenMarkdown = async (event) => {
1695
+ this.loggerService.log("telegramTemplateService getSignalOpenMarkdown", {
1696
+ event,
1697
+ });
1698
+ return await RENDER_TEMPLATE_FN("signal-open.mustache", event, this);
1699
+ };
1700
+ this.getSignalCloseMarkdown = async (event) => {
1701
+ this.loggerService.log("telegramTemplateService getSignalCloseMarkdown", {
1702
+ event,
1703
+ });
1704
+ return await RENDER_TEMPLATE_FN("signal-close.mustache", event, this);
1705
+ };
1661
1706
  }
1662
1707
  }
1663
1708
 
@@ -1677,38 +1722,35 @@ const getExtVariants = (fileName) => {
1677
1722
  const REQUIRE_MODULE_FACTORY = (fileName) => {
1678
1723
  for (const variant of getExtVariants(fileName)) {
1679
1724
  try {
1680
- return require$2(variant);
1725
+ require$2(variant);
1726
+ return true;
1681
1727
  }
1682
1728
  catch {
1683
1729
  continue;
1684
1730
  }
1685
1731
  }
1686
- return null;
1732
+ return false;
1687
1733
  };
1688
1734
  const IMPORT_MODULE_FACTORY = async (fileName) => {
1689
1735
  {
1690
- return null;
1736
+ return false;
1691
1737
  }
1692
1738
  };
1693
1739
  const BABEL_MODULE_FACTORY = async (fileName, self) => {
1694
1740
  for (const variant of getExtVariants(fileName)) {
1695
1741
  try {
1696
1742
  const code = await fs$1.readFile(variant, "utf-8");
1697
- const { exports } = self.babelService.transpileAndRun(code);
1698
- if (exports.default) {
1699
- return exports.default;
1700
- }
1701
- return exports;
1743
+ self.babelService.transpileAndRun(code);
1744
+ return true;
1702
1745
  }
1703
1746
  catch (error) {
1704
1747
  console.log(functoolsKit.getErrorMessage(error));
1705
1748
  continue;
1706
1749
  }
1707
1750
  }
1708
- return null;
1751
+ return false;
1709
1752
  };
1710
1753
  const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
1711
- let Ctor = null;
1712
1754
  const overridePath = path.join(self.resolveService.OVERRIDE_MODULES_DIR, fileName);
1713
1755
  const targetPath = path.join(process.cwd(), "modules", fileName);
1714
1756
  const hasOverride = await fs$1
@@ -1716,212 +1758,29 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
1716
1758
  .then(() => true)
1717
1759
  .catch(() => false);
1718
1760
  const resolvedFile = hasOverride ? overridePath : targetPath;
1719
- if ((Ctor = REQUIRE_MODULE_FACTORY(resolvedFile))) {
1720
- return typeof Ctor === "function" ? new Ctor() : Ctor;
1761
+ if (REQUIRE_MODULE_FACTORY(resolvedFile)) {
1762
+ return true;
1721
1763
  }
1722
- if ((Ctor = await IMPORT_MODULE_FACTORY())) {
1723
- return typeof Ctor === "function" ? new Ctor() : Ctor;
1764
+ if (await IMPORT_MODULE_FACTORY()) {
1765
+ return true;
1724
1766
  }
1725
- if ((Ctor = await BABEL_MODULE_FACTORY(resolvedFile, self))) {
1726
- return typeof Ctor === "function" ? new Ctor() : Ctor;
1767
+ if (await BABEL_MODULE_FACTORY(resolvedFile, self)) {
1768
+ return true;
1727
1769
  }
1728
- throw new Error(`Module module import failed for file: ${resolvedFile}`);
1770
+ console.warn(`Module module import failed for file: ${resolvedFile}`);
1771
+ return false;
1729
1772
  };
1730
1773
  class ModuleConnectionService {
1731
1774
  constructor() {
1732
1775
  this.loggerService = inject(TYPES.loggerService);
1733
1776
  this.resolveService = inject(TYPES.resolveService);
1734
1777
  this.babelService = inject(TYPES.babelService);
1735
- this.getInstance = functoolsKit.memoize(([fileName]) => `${fileName}`, async (fileName) => {
1778
+ this.loadModule = async (fileName) => {
1736
1779
  this.loggerService.log("moduleConnectionService getInstance", {
1737
1780
  fileName,
1738
1781
  });
1739
1782
  return await LOAD_MODULE_MODULE_FN(fileName, this);
1740
- });
1741
- }
1742
- }
1743
-
1744
- const LOAD_INSTANCE_FN = functoolsKit.singleshot(functoolsKit.trycatch(async (self) => {
1745
- const module = (await self.moduleConnectionService.getInstance("./live.module"));
1746
- return module;
1747
- }, { defaultValue: null }));
1748
- class LiveProviderService {
1749
- constructor() {
1750
- this.loggerService = inject(TYPES.loggerService);
1751
- this.moduleConnectionService = inject(TYPES.moduleConnectionService);
1752
- this.handleTrailingTake = async (event) => {
1753
- this.loggerService.log("liveProviderService handleTrailingTake", {
1754
- event,
1755
- });
1756
- const instance = await LOAD_INSTANCE_FN(this);
1757
- if (instance?.onTrailingTake) {
1758
- await instance.onTrailingTake(event);
1759
- }
1760
- };
1761
- this.handleTrailingStop = async (event) => {
1762
- this.loggerService.log("liveProviderService handleTrailingStop", {
1763
- event,
1764
- });
1765
- const instance = await LOAD_INSTANCE_FN(this);
1766
- if (instance?.onTrailingStop) {
1767
- await instance.onTrailingStop(event);
1768
- }
1769
- };
1770
- this.handleBreakeven = async (event) => {
1771
- this.loggerService.log("liveProviderService handleBreakeven", {
1772
- event,
1773
- });
1774
- const instance = await LOAD_INSTANCE_FN(this);
1775
- if (instance?.onBreakeven) {
1776
- await instance.onBreakeven(event);
1777
- }
1778
1783
  };
1779
- this.handlePartialProfit = async (event) => {
1780
- this.loggerService.log("liveProviderService handlePartialProfit", {
1781
- event,
1782
- });
1783
- const instance = await LOAD_INSTANCE_FN(this);
1784
- if (instance?.onPartialProfit) {
1785
- await instance.onPartialProfit(event);
1786
- }
1787
- };
1788
- this.handlePartialLoss = async (event) => {
1789
- this.loggerService.log("liveProviderService handlePartialLoss", {
1790
- event,
1791
- });
1792
- const instance = await LOAD_INSTANCE_FN(this);
1793
- if (instance?.onPartialLoss) {
1794
- await instance.onPartialLoss(event);
1795
- }
1796
- };
1797
- this.handleScheduled = async (event) => {
1798
- this.loggerService.log("liveProviderService handleScheduled", {
1799
- event,
1800
- });
1801
- const instance = await LOAD_INSTANCE_FN(this);
1802
- if (instance?.onScheduled) {
1803
- await instance.onScheduled(event);
1804
- }
1805
- };
1806
- this.handleCancelled = async (event) => {
1807
- this.loggerService.log("liveProviderService handleCancelled", {
1808
- event,
1809
- });
1810
- const instance = await LOAD_INSTANCE_FN(this);
1811
- if (instance?.onCancelled) {
1812
- await instance.onCancelled(event);
1813
- }
1814
- };
1815
- this.handleOpened = async (event) => {
1816
- this.loggerService.log("liveProviderService handleOpened", {
1817
- event,
1818
- });
1819
- const instance = await LOAD_INSTANCE_FN(this);
1820
- if (instance?.onOpened) {
1821
- await instance.onOpened(event);
1822
- }
1823
- };
1824
- this.handleClosed = async (event) => {
1825
- this.loggerService.log("liveProviderService handleClosed", {
1826
- event,
1827
- });
1828
- const instance = await LOAD_INSTANCE_FN(this);
1829
- if (instance?.onClosed) {
1830
- await instance.onClosed(event);
1831
- }
1832
- };
1833
- this.handleRisk = async (event) => {
1834
- this.loggerService.log("liveProviderService handleClosed", {
1835
- event,
1836
- });
1837
- const instance = await LOAD_INSTANCE_FN(this);
1838
- if (instance?.onRisk) {
1839
- await instance.onRisk(event);
1840
- }
1841
- };
1842
- this.handleAverageBuy = async (event) => {
1843
- this.loggerService.log("liveProviderService handleAverageBuy", {
1844
- event,
1845
- });
1846
- const instance = await LOAD_INSTANCE_FN(this);
1847
- if (instance?.onAverageBuy) {
1848
- await instance.onAverageBuy(event);
1849
- }
1850
- };
1851
- this.enable = functoolsKit.singleshot(() => {
1852
- this.loggerService.log("liveProviderService enable");
1853
- LOAD_INSTANCE_FN(this).then((module) => {
1854
- if (module) {
1855
- this.loggerService.log("Live trading initialized successfully with ./modules/live.module.mjs");
1856
- return;
1857
- }
1858
- console.log("No ./modules/live.module.mjs found, live trading failed to initialize");
1859
- process.exit(-1);
1860
- });
1861
- const unRisk = BacktestKit.listenRisk(async (event) => {
1862
- await this.handleRisk(event);
1863
- });
1864
- const unSignal = BacktestKit.listenSignal(async (event) => {
1865
- if (event.action === "scheduled") {
1866
- await this.handleScheduled(event);
1867
- return;
1868
- }
1869
- if (event.action === "cancelled") {
1870
- await this.handleCancelled(event);
1871
- return;
1872
- }
1873
- if (event.action === "opened") {
1874
- await this.handleOpened(event);
1875
- return;
1876
- }
1877
- if (event.action === "closed") {
1878
- await this.handleClosed(event);
1879
- return;
1880
- }
1881
- });
1882
- const unCommit = BacktestKit.listenStrategyCommit(async (event) => {
1883
- if (event.action === "trailing-take") {
1884
- await this.handleTrailingTake(event);
1885
- return;
1886
- }
1887
- if (event.action === "trailing-stop") {
1888
- await this.handleTrailingStop(event);
1889
- return;
1890
- }
1891
- if (event.action === "breakeven") {
1892
- await this.handleBreakeven(event);
1893
- return;
1894
- }
1895
- if (event.action === "partial-profit") {
1896
- await this.handlePartialProfit(event);
1897
- return;
1898
- }
1899
- if (event.action === "partial-loss") {
1900
- await this.handlePartialLoss(event);
1901
- return;
1902
- }
1903
- if (event.action === "average-buy") {
1904
- await this.handleAverageBuy(event);
1905
- return;
1906
- }
1907
- });
1908
- const unConnect = () => this.enable.clear();
1909
- return functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unConnect());
1910
- });
1911
- this.disable = () => {
1912
- this.loggerService.log("liveProviderService disable");
1913
- if (this.enable.hasValue()) {
1914
- const lastSubscription = this.enable();
1915
- lastSubscription();
1916
- }
1917
- };
1918
- this.connect = functoolsKit.singleshot(async () => {
1919
- this.loggerService.log("liveProviderService connect");
1920
- if (!getArgs().values.live) {
1921
- return;
1922
- }
1923
- return entrySubject.subscribe(this.enable);
1924
- });
1925
1784
  }
1926
1785
  }
1927
1786
 
@@ -2044,7 +1903,6 @@ globalThis.BacktestKitSignals = BacktestKitSignals__namespace;
2044
1903
  {
2045
1904
  provide(TYPES.telegramProviderService, () => new TelegramProviderService());
2046
1905
  provide(TYPES.frontendProviderService, () => new FrontendProviderService());
2047
- provide(TYPES.liveProviderService, () => new LiveProviderService());
2048
1906
  }
2049
1907
  {
2050
1908
  provide(TYPES.telegramWebService, () => new TelegramWebService());
@@ -2083,7 +1941,6 @@ const schemaServices = {
2083
1941
  const providerServices = {
2084
1942
  frontendProviderService: inject(TYPES.frontendProviderService),
2085
1943
  telegramProviderService: inject(TYPES.telegramProviderService),
2086
- liveProviderService: inject(TYPES.liveProviderService),
2087
1944
  };
2088
1945
  const webServices = {
2089
1946
  telegramWebService: inject(TYPES.telegramWebService),
@@ -2188,7 +2045,6 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2188
2045
  exchangeName,
2189
2046
  strategyName,
2190
2047
  });
2191
- BacktestKit.listenDoneLive(cli.liveProviderService.disable);
2192
2048
  });
2193
2049
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2194
2050
  process.on("SIGINT", BEFORE_EXIT_FN$2);