@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 +81 -56
- package/build/index.cjs +65 -209
- package/build/index.mjs +67 -211
- package/package.json +13 -13
- package/template/average-buy.mustache +6 -1
- package/template/breakeven.mustache +7 -3
- package/template/cancelled.mustache +5 -1
- package/template/closed.mustache +7 -2
- package/template/opened.mustache +5 -1
- package/template/partial-loss.mustache +6 -2
- package/template/partial-profit.mustache +6 -2
- package/template/risk.mustache +5 -5
- package/template/scheduled.mustache +5 -1
- package/template/signal-close.mustache +21 -0
- package/template/signal-open.mustache +21 -0
- package/template/trailing-stop.mustache +5 -2
- package/template/trailing-take.mustache +5 -2
- package/types.d.ts +10 -41
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 `
|
|
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
|
|
234
|
-
│ ├── .env
|
|
235
|
-
│ ├── modules
|
|
236
|
-
│ ├──
|
|
237
|
-
│
|
|
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
|
|
268
|
-
|
|
269
|
-
| Candle cache
|
|
270
|
-
| Backtest reports
|
|
271
|
-
|
|
|
272
|
-
|
|
|
273
|
-
|
|
|
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
|
-
## 🧩
|
|
301
|
+
## 🧩 Module Hooks (Broker Adapter)
|
|
296
302
|
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
console.log('Position opened', event.symbol, event.priceOpen);
|
|
306
|
-
}
|
|
313
|
+
### How It Works
|
|
307
314
|
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
317
|
+
```javascript
|
|
318
|
+
// live.module.mjs
|
|
319
|
+
import { Broker } from 'backtest-kit';
|
|
320
|
+
import { myExchange } from './exchange.mjs';
|
|
315
321
|
|
|
316
|
-
|
|
317
|
-
|
|
322
|
+
class MyBroker {
|
|
323
|
+
async onSignalOpenCommit({ symbol, priceOpen, direction }) {
|
|
324
|
+
await myExchange.openPosition(symbol, direction, priceOpen);
|
|
318
325
|
}
|
|
319
326
|
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
async onSignalCloseCommit({ symbol, priceClosed }) {
|
|
328
|
+
await myExchange.closePosition(symbol, priceClosed);
|
|
322
329
|
}
|
|
323
330
|
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
329
|
-
|
|
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
|
-
|
|
333
|
-
console.log('Trailing take adjusted', event.symbol);
|
|
334
|
-
}
|
|
348
|
+
Broker.useBrokerAdapter(MyBroker);
|
|
335
349
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
350
|
+
Broker.enable();
|
|
351
|
+
```
|
|
339
352
|
|
|
340
|
-
|
|
341
|
-
console.log('Breakeven triggered', event.symbol);
|
|
342
|
-
}
|
|
353
|
+
### Available Broker Hooks
|
|
343
354
|
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
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
|
|
368
|
+
### TypeScript
|
|
353
369
|
|
|
354
370
|
```typescript
|
|
355
|
-
import
|
|
371
|
+
import { Broker, IBroker, BrokerSignalOpenPayload, BrokerSignalClosePayload } from 'backtest-kit';
|
|
356
372
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
- 🧩
|
|
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.
|
|
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
|
-
|
|
1725
|
+
require$2(variant);
|
|
1726
|
+
return true;
|
|
1681
1727
|
}
|
|
1682
1728
|
catch {
|
|
1683
1729
|
continue;
|
|
1684
1730
|
}
|
|
1685
1731
|
}
|
|
1686
|
-
return
|
|
1732
|
+
return false;
|
|
1687
1733
|
};
|
|
1688
1734
|
const IMPORT_MODULE_FACTORY = async (fileName) => {
|
|
1689
1735
|
{
|
|
1690
|
-
return
|
|
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
|
-
|
|
1698
|
-
|
|
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
|
|
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 (
|
|
1720
|
-
return
|
|
1761
|
+
if (REQUIRE_MODULE_FACTORY(resolvedFile)) {
|
|
1762
|
+
return true;
|
|
1721
1763
|
}
|
|
1722
|
-
if (
|
|
1723
|
-
return
|
|
1764
|
+
if (await IMPORT_MODULE_FACTORY()) {
|
|
1765
|
+
return true;
|
|
1724
1766
|
}
|
|
1725
|
-
if (
|
|
1726
|
-
return
|
|
1767
|
+
if (await BABEL_MODULE_FACTORY(resolvedFile, self)) {
|
|
1768
|
+
return true;
|
|
1727
1769
|
}
|
|
1728
|
-
|
|
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.
|
|
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);
|