@backtest-kit/cli 3.3.2 → 3.3.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
@@ -340,6 +340,10 @@ export default class {
340
340
  onBreakeven(event) {
341
341
  console.log('Breakeven triggered', event.symbol);
342
342
  }
343
+
344
+ onAverageBuy(event) {
345
+ console.log('Cost averaging (DCA)', event.symbol);
346
+ }
343
347
  }
344
348
  ```
345
349
 
@@ -356,6 +360,57 @@ export default class MyModule implements ILiveModule {
356
360
  }
357
361
  ```
358
362
 
363
+ ## 📦 Supported Entry Point Formats
364
+
365
+ `@backtest-kit/cli` automatically detects the format of your strategy file and loads it with the appropriate runtime — no flags or configuration required.
366
+
367
+ | Format | Extension | Runtime | Use Case |
368
+ |--------|-----------|---------|----------|
369
+ | **TypeScript** | `.ts` | [`tsx`](https://tsx.is/) via `tsImport()` | TypeScript strategies with cross-imports (ESM ↔ CJS) |
370
+ | **ES Module** | `.mjs` | Native `import()` | Modern JavaScript with top-level `await` and ESM syntax |
371
+ | **CommonJS** | `.cjs` | Native `require()` | Legacy or dual-package strategies |
372
+
373
+ ### TypeScript (`.ts`)
374
+
375
+ Run TypeScript strategy files directly — no `tsc` compilation step needed. Powered by `tsx`, which handles cross-format imports transparently:
376
+
377
+ ```json
378
+ {
379
+ "scripts": {
380
+ "backtest": "npx @backtest-kit/cli --backtest ./src/index.ts"
381
+ },
382
+ "dependencies": {
383
+ "@backtest-kit/cli": "latest",
384
+ "backtest-kit": "latest",
385
+ "tsx": "latest"
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### ES Module (`.mjs`)
391
+
392
+ Standard ESM format. Supports top-level `await`, named exports, and `import` syntax:
393
+
394
+ ```json
395
+ {
396
+ "scripts": {
397
+ "backtest": "npx @backtest-kit/cli --backtest ./src/index.mjs"
398
+ }
399
+ }
400
+ ```
401
+
402
+ ### CommonJS (`.cjs`)
403
+
404
+ For projects that compile to or use CommonJS. Loaded via `require()`:
405
+
406
+ ```json
407
+ {
408
+ "scripts": {
409
+ "backtest": "npx @backtest-kit/cli --backtest ./dist/index.cjs"
410
+ }
411
+ }
412
+ ```
413
+
359
414
  ## 🌍 Environment Variables
360
415
 
361
416
  Create a `.env` file in your project root:
package/build/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var backtestKit = require('backtest-kit');
4
+ var BacktestKit = require('backtest-kit');
5
5
  var functoolsKit = require('functools-kit');
6
6
  var fs = require('fs');
7
7
  var stackTrace = require('stack-trace');
@@ -23,6 +23,8 @@ var MarkdownIt = require('markdown-it');
23
23
  var sanitizeHtml = require('sanitize-html');
24
24
  var jsdom = require('jsdom');
25
25
  var Mustache = require('mustache');
26
+ var standalone = require('@babel/standalone');
27
+ var pluginUMD = require('@babel/plugin-transform-modules-umd');
26
28
 
27
29
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
28
30
  function _interopNamespaceDefault(e) {
@@ -42,6 +44,7 @@ function _interopNamespaceDefault(e) {
42
44
  return Object.freeze(n);
43
45
  }
44
46
 
47
+ var BacktestKit__namespace = /*#__PURE__*/_interopNamespaceDefault(BacktestKit);
45
48
  var stackTrace__namespace = /*#__PURE__*/_interopNamespaceDefault(stackTrace);
46
49
 
47
50
  /**
@@ -75,22 +78,22 @@ var stackTrace__namespace = /*#__PURE__*/_interopNamespaceDefault(stackTrace);
75
78
  }
76
79
 
77
80
  {
78
- backtestKit.Storage.enable();
79
- backtestKit.Notification.enable();
81
+ BacktestKit.Storage.enable();
82
+ BacktestKit.Notification.enable();
80
83
  }
81
84
  {
82
- backtestKit.Markdown.disable();
83
- backtestKit.Report.enable();
85
+ BacktestKit.Markdown.disable();
86
+ BacktestKit.Report.enable();
84
87
  }
85
88
  {
86
- backtestKit.StorageLive.usePersist();
87
- backtestKit.StorageBacktest.useMemory();
89
+ BacktestKit.StorageLive.usePersist();
90
+ BacktestKit.StorageBacktest.useMemory();
88
91
  }
89
92
  {
90
- backtestKit.NotificationLive.usePersist();
91
- backtestKit.NotificationBacktest.useMemory();
93
+ BacktestKit.NotificationLive.usePersist();
94
+ BacktestKit.NotificationBacktest.useMemory();
92
95
  }
93
- backtestKit.setConfig({
96
+ BacktestKit.setConfig({
94
97
  CC_MAX_NOTIFICATIONS: 5000,
95
98
  CC_MAX_SIGNALS: 750,
96
99
  });
@@ -191,6 +194,7 @@ const baseServices$1 = {
191
194
  errorService: Symbol('errorService'),
192
195
  loggerService: Symbol('loggerService'),
193
196
  resolveService: Symbol('resolveService'),
197
+ babelService: Symbol('babelService'),
194
198
  };
195
199
  const connectionServices$1 = {
196
200
  moduleConnectionService: Symbol('moduleConnectionService'),
@@ -234,8 +238,8 @@ const TYPES = {
234
238
 
235
239
  const entrySubject = new functoolsKit.BehaviorSubject();
236
240
 
237
- 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)));
238
- const __dirname$1 = path.dirname(__filename$1);
241
+ const __filename$2 = 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)));
242
+ const __dirname$1 = path.dirname(__filename$2);
239
243
  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)));
240
244
  const REQUIRE_ENTRY_FACTORY = (filePath) => {
241
245
  try {
@@ -247,17 +251,37 @@ const REQUIRE_ENTRY_FACTORY = (filePath) => {
247
251
  }
248
252
  };
249
253
  const IMPORT_ENTRY_FACTORY = async (filePath) => {
250
- await import(url.pathToFileURL(filePath).href);
254
+ {
255
+ return false;
256
+ }
257
+ };
258
+ const BABEL_ENTRY_FACTORY = async (filePath, self) => {
259
+ const code = await fs$1.readFile(filePath, "utf-8");
260
+ try {
261
+ await self.babelService.transpileAndRun(code);
262
+ return true;
263
+ }
264
+ catch {
265
+ return false;
266
+ }
251
267
  };
252
- const LOAD_ENTRY_FN = async (filePath) => {
253
- if (!REQUIRE_ENTRY_FACTORY(filePath)) {
254
- await IMPORT_ENTRY_FACTORY(filePath);
268
+ const LOAD_ENTRY_FN = async (filePath, self) => {
269
+ if (REQUIRE_ENTRY_FACTORY(filePath)) {
270
+ return;
271
+ }
272
+ if (await IMPORT_ENTRY_FACTORY()) {
273
+ return;
255
274
  }
275
+ if (await BABEL_ENTRY_FACTORY(filePath, self)) {
276
+ return;
277
+ }
278
+ throw new Error(`Failed to load entry point: ${filePath}`);
256
279
  };
257
280
  let _is_launched = false;
258
281
  class ResolveService {
259
282
  constructor() {
260
283
  this.loggerService = inject(TYPES.loggerService);
284
+ this.babelService = inject(TYPES.babelService);
261
285
  this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$1, '..', 'template');
262
286
  this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
263
287
  this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
@@ -274,7 +298,7 @@ class ResolveService {
274
298
  process.chdir(moduleRoot);
275
299
  dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
276
300
  dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
277
- await LOAD_ENTRY_FN(absolutePath);
301
+ await LOAD_ENTRY_FN(absolutePath, this);
278
302
  await entrySubject.next(absolutePath);
279
303
  }
280
304
  _is_launched = true;
@@ -304,7 +328,7 @@ var ExchangeName$1 = ExchangeName;
304
328
  const ADD_EXCHANGE_FN = (self) => {
305
329
  self.loggerService.log("Adding CCXT Binance as a default exchange schema");
306
330
  console.warn("Warning: The default exchange schema is set to CCXT Binance. Please make sure to update it according to your needs using --exchange cli param.");
307
- backtestKit.addExchangeSchema({
331
+ BacktestKit.addExchangeSchema({
308
332
  exchangeName: ExchangeName$1.DefaultExchange,
309
333
  getCandles: async (symbol, interval, since, limit) => {
310
334
  const exchange = await getExchange();
@@ -323,7 +347,7 @@ const ADD_EXCHANGE_FN = (self) => {
323
347
  const market = exchange.market(symbol);
324
348
  const tickSize = market.limits?.price?.min || market.precision?.price;
325
349
  if (tickSize !== undefined) {
326
- return backtestKit.roundTicks(price, tickSize);
350
+ return BacktestKit.roundTicks(price, tickSize);
327
351
  }
328
352
  return exchange.priceToPrecision(symbol, price);
329
353
  },
@@ -332,7 +356,7 @@ const ADD_EXCHANGE_FN = (self) => {
332
356
  const market = exchange.market(symbol);
333
357
  const stepSize = market.limits?.amount?.min || market.precision?.amount;
334
358
  if (stepSize !== undefined) {
335
- return backtestKit.roundTicks(quantity, stepSize);
359
+ return BacktestKit.roundTicks(quantity, stepSize);
336
360
  }
337
361
  return exchange.amountToPrecision(symbol, quantity);
338
362
  },
@@ -361,7 +385,7 @@ class ExchangeSchemaService {
361
385
  this.loggerService = inject(TYPES.loggerService);
362
386
  this.addSchema = functoolsKit.singleshot(async () => {
363
387
  this.loggerService.log("exchangeSchemaService addSchema");
364
- const { length } = await backtestKit.listExchangeSchema();
388
+ const { length } = await BacktestKit.listExchangeSchema();
365
389
  !length && ADD_EXCHANGE_FN(this);
366
390
  });
367
391
  }
@@ -438,7 +462,7 @@ const getArgs = functoolsKit.singleshot(() => {
438
462
  const ADD_FRAME_FN = (self) => {
439
463
  self.loggerService.log("Adding February 2024 as a default frame schema");
440
464
  console.warn("Warning: The default frame schema is set to February 2024. Please make sure to update it according to your needs using --frame cli param.");
441
- backtestKit.addFrameSchema({
465
+ BacktestKit.addFrameSchema({
442
466
  frameName: FrameName$1.DefaultFrame,
443
467
  interval: "1m",
444
468
  startDate: new Date("2024-02-01T00:00:00Z"),
@@ -453,7 +477,7 @@ class FrameSchemaService {
453
477
  if (!getArgs().values.backtest) {
454
478
  return;
455
479
  }
456
- const { length } = await backtestKit.listFrameSchema();
480
+ const { length } = await BacktestKit.listFrameSchema();
457
481
  !length && ADD_FRAME_FN(this);
458
482
  });
459
483
  }
@@ -473,11 +497,11 @@ class SymbolSchemaService {
473
497
 
474
498
  const notifyFinish = functoolsKit.singleshot(() => {
475
499
  let disposeRef;
476
- const unLive = backtestKit.listenDoneLive(() => {
500
+ const unLive = BacktestKit.listenDoneLive(() => {
477
501
  console.log("Live trading finished");
478
502
  disposeRef && disposeRef();
479
503
  });
480
- const unBacktest = backtestKit.listenDoneBacktest(() => {
504
+ const unBacktest = BacktestKit.listenDoneBacktest(() => {
481
505
  console.log("Backtest trading finished");
482
506
  disposeRef && disposeRef();
483
507
  });
@@ -491,7 +515,7 @@ const getEntry = (metaUrl) => {
491
515
 
492
516
  const notifyVerbose = functoolsKit.singleshot(() => {
493
517
  console.log("Using verbose logging...");
494
- backtestKit.listenSignal((event) => {
518
+ BacktestKit.listenSignal((event) => {
495
519
  if (event.action === "scheduled") {
496
520
  console.log(`[POSITION SCHEDULED] ${event.symbol}`);
497
521
  console.log(` Strategy: ${event.strategyName}`);
@@ -571,9 +595,9 @@ class BacktestMainService {
571
595
  this.frameSchemaService.addSchema();
572
596
  }
573
597
  const symbol = payload.symbol || "BTCUSDT";
574
- const [defaultStrategyName = null] = await backtestKit.listStrategySchema();
575
- const [defaultExchangeName = null] = await backtestKit.listExchangeSchema();
576
- const [defaultFrameName = null] = await backtestKit.listFrameSchema();
598
+ const [defaultStrategyName = null] = await BacktestKit.listStrategySchema();
599
+ const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
600
+ const [defaultFrameName = null] = await BacktestKit.listFrameSchema();
577
601
  const strategyName = payload.strategy || defaultStrategyName?.strategyName;
578
602
  if (!strategyName) {
579
603
  throw new Error("Strategy name is required");
@@ -594,7 +618,7 @@ class BacktestMainService {
594
618
  });
595
619
  }
596
620
  if (payload.verbose) {
597
- backtestKit.overrideExchangeSchema({
621
+ BacktestKit.overrideExchangeSchema({
598
622
  exchangeName,
599
623
  callbacks: {
600
624
  onCandleData(symbol, interval, since) {
@@ -604,7 +628,7 @@ class BacktestMainService {
604
628
  });
605
629
  notifyVerbose();
606
630
  }
607
- backtestKit.Backtest.background(symbol, {
631
+ BacktestKit.Backtest.background(symbol, {
608
632
  strategyName,
609
633
  frameName,
610
634
  exchangeName,
@@ -663,8 +687,8 @@ class LiveMainService {
663
687
  this.symbolSchemaService.addSchema();
664
688
  }
665
689
  const symbol = payload.symbol || "BTCUSDT";
666
- const [defaultStrategyName = null] = await backtestKit.listStrategySchema();
667
- const [defaultExchangeName = null] = await backtestKit.listExchangeSchema();
690
+ const [defaultStrategyName = null] = await BacktestKit.listStrategySchema();
691
+ const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
668
692
  const strategyName = payload.strategy || defaultStrategyName?.strategyName;
669
693
  if (!strategyName) {
670
694
  throw new Error("Strategy name is required");
@@ -674,7 +698,7 @@ class LiveMainService {
674
698
  throw new Error("Exchange name is required");
675
699
  }
676
700
  if (payload.verbose) {
677
- backtestKit.overrideExchangeSchema({
701
+ BacktestKit.overrideExchangeSchema({
678
702
  exchangeName,
679
703
  callbacks: {
680
704
  onCandleData(symbol, interval, since) {
@@ -684,7 +708,7 @@ class LiveMainService {
684
708
  });
685
709
  notifyVerbose();
686
710
  }
687
- backtestKit.Live.background(symbol, {
711
+ BacktestKit.Live.background(symbol, {
688
712
  strategyName,
689
713
  exchangeName,
690
714
  });
@@ -734,8 +758,8 @@ class PaperMainService {
734
758
  this.symbolSchemaService.addSchema();
735
759
  }
736
760
  const symbol = payload.symbol || "BTCUSDT";
737
- const [defaultStrategyName = null] = await backtestKit.listStrategySchema();
738
- const [defaultExchangeName = null] = await backtestKit.listExchangeSchema();
761
+ const [defaultStrategyName = null] = await BacktestKit.listStrategySchema();
762
+ const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
739
763
  const strategyName = payload.strategy || defaultStrategyName?.strategyName;
740
764
  if (!strategyName) {
741
765
  throw new Error("Strategy name is required");
@@ -745,7 +769,7 @@ class PaperMainService {
745
769
  throw new Error("Exchange name is required");
746
770
  }
747
771
  if (payload.verbose) {
748
- backtestKit.overrideExchangeSchema({
772
+ BacktestKit.overrideExchangeSchema({
749
773
  exchangeName,
750
774
  callbacks: {
751
775
  onCandleData(symbol, interval, since) {
@@ -755,7 +779,7 @@ class PaperMainService {
755
779
  });
756
780
  notifyVerbose();
757
781
  }
758
- backtestKit.Live.background(symbol, {
782
+ BacktestKit.Live.background(symbol, {
759
783
  strategyName,
760
784
  exchangeName,
761
785
  });
@@ -865,7 +889,7 @@ class TelegramProviderService {
865
889
 
866
890
  const CANDLES_LIMIT = 160;
867
891
  const GET_CONFIG_FN = async (symbol, interval) => {
868
- const candles = await backtestKit.getCandles(symbol, interval, CANDLES_LIMIT);
892
+ const candles = await BacktestKit.getCandles(symbol, interval, CANDLES_LIMIT);
869
893
  const labels = candles.map(({ timestamp }) => new Date(timestamp).toLocaleTimeString("en-US", {
870
894
  hour: "2-digit",
871
895
  minute: "2-digit",
@@ -1281,7 +1305,7 @@ class TelegramWebService {
1281
1305
  }
1282
1306
 
1283
1307
  const GET_TIMEFRAME_RANGE_FN = async (frameName) => {
1284
- const frameList = await backtestKit.listFrameSchema();
1308
+ const frameList = await BacktestKit.listFrameSchema();
1285
1309
  const frameSchema = frameList.find((frameSchema) => frameSchema.frameName === frameName);
1286
1310
  if (!frameSchema) {
1287
1311
  throw new Error(`Frame with name ${frameName} not found`);
@@ -1292,7 +1316,7 @@ const GET_TIMEFRAME_RANGE_FN = async (frameName) => {
1292
1316
  const CACHE_CANDLES_FN = functoolsKit.retry(async (interval, dto) => {
1293
1317
  try {
1294
1318
  console.log(`Checking candles cache for ${dto.symbol} ${interval} from ${dto.from} to ${dto.to}`);
1295
- await backtestKit.checkCandles({
1319
+ await BacktestKit.checkCandles({
1296
1320
  exchangeName: dto.exchangeName,
1297
1321
  from: dto.from,
1298
1322
  to: dto.to,
@@ -1302,7 +1326,7 @@ const CACHE_CANDLES_FN = functoolsKit.retry(async (interval, dto) => {
1302
1326
  }
1303
1327
  catch (error) {
1304
1328
  console.log(`Caching candles for ${dto.symbol} ${interval} from ${dto.from} to ${dto.to}`);
1305
- await backtestKit.warmCandles({
1329
+ await BacktestKit.warmCandles({
1306
1330
  symbol: dto.symbol,
1307
1331
  exchangeName: dto.exchangeName,
1308
1332
  from: dto.from,
@@ -1459,10 +1483,10 @@ class TelegramLogicService {
1459
1483
  };
1460
1484
  this.connect = functoolsKit.singleshot(() => {
1461
1485
  this.loggerService.log("telegramLogicService connect");
1462
- const unRisk = backtestKit.listenRisk(async (event) => {
1486
+ const unRisk = BacktestKit.listenRisk(async (event) => {
1463
1487
  await this.notifyRisk(event);
1464
1488
  });
1465
- const unSignal = backtestKit.listenSignal(async (event) => {
1489
+ const unSignal = BacktestKit.listenSignal(async (event) => {
1466
1490
  if (event.action === "scheduled") {
1467
1491
  await this.notifyScheduled(event);
1468
1492
  return;
@@ -1480,7 +1504,7 @@ class TelegramLogicService {
1480
1504
  return;
1481
1505
  }
1482
1506
  });
1483
- const unCommit = backtestKit.listenStrategyCommit(async (event) => {
1507
+ const unCommit = BacktestKit.listenStrategyCommit(async (event) => {
1484
1508
  if (event.action === "trailing-take") {
1485
1509
  await this.notifyTrailingTake(event);
1486
1510
  return;
@@ -1623,9 +1647,16 @@ const REQUIRE_MODULE_FACTORY = (fileName) => {
1623
1647
  return null;
1624
1648
  };
1625
1649
  const IMPORT_MODULE_FACTORY = async (fileName) => {
1650
+ {
1651
+ return null;
1652
+ }
1653
+ };
1654
+ const BABEL_MODULE_FACTORY = async (fileName, self) => {
1626
1655
  for (const variant of getExtVariants(fileName)) {
1627
1656
  try {
1628
- return await import(variant);
1657
+ const code = await fs$1.readFile(variant, "utf-8");
1658
+ const exports = self.babelService.transpileAndRun(code);
1659
+ return exports.default ?? exports;
1629
1660
  }
1630
1661
  catch {
1631
1662
  continue;
@@ -1645,7 +1676,10 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
1645
1676
  if ((Ctor = REQUIRE_MODULE_FACTORY(resolvedFile))) {
1646
1677
  return typeof Ctor === "function" ? new Ctor() : Ctor;
1647
1678
  }
1648
- if ((Ctor = await IMPORT_MODULE_FACTORY(resolvedFile))) {
1679
+ if ((Ctor = await IMPORT_MODULE_FACTORY())) {
1680
+ return typeof Ctor === "function" ? new Ctor() : Ctor;
1681
+ }
1682
+ if ((Ctor = await BABEL_MODULE_FACTORY(resolvedFile, self))) {
1649
1683
  return typeof Ctor === "function" ? new Ctor() : Ctor;
1650
1684
  }
1651
1685
  throw new Error(`Module module import failed for file: ${resolvedFile}`);
@@ -1654,6 +1688,7 @@ class ModuleConnectionService {
1654
1688
  constructor() {
1655
1689
  this.loggerService = inject(TYPES.loggerService);
1656
1690
  this.resolveService = inject(TYPES.resolveService);
1691
+ this.babelService = inject(TYPES.babelService);
1657
1692
  this.getInstance = functoolsKit.memoize(([fileName]) => `${fileName}`, async (fileName) => {
1658
1693
  this.loggerService.log("moduleConnectionService getInstance", {
1659
1694
  fileName,
@@ -1780,10 +1815,10 @@ class LiveProviderService {
1780
1815
  console.log("No ./modules/live.module.mjs found, live trading failed to initialize");
1781
1816
  process.exit(-1);
1782
1817
  });
1783
- const unRisk = backtestKit.listenRisk(async (event) => {
1818
+ const unRisk = BacktestKit.listenRisk(async (event) => {
1784
1819
  await this.handleRisk(event);
1785
1820
  });
1786
- const unSignal = backtestKit.listenSignal(async (event) => {
1821
+ const unSignal = BacktestKit.listenSignal(async (event) => {
1787
1822
  if (event.action === "scheduled") {
1788
1823
  await this.handleScheduled(event);
1789
1824
  return;
@@ -1801,7 +1836,7 @@ class LiveProviderService {
1801
1836
  return;
1802
1837
  }
1803
1838
  });
1804
- const unCommit = backtestKit.listenStrategyCommit(async (event) => {
1839
+ const unCommit = BacktestKit.listenStrategyCommit(async (event) => {
1805
1840
  if (event.action === "trailing-take") {
1806
1841
  await this.handleTrailingTake(event);
1807
1842
  return;
@@ -1847,6 +1882,55 @@ class LiveProviderService {
1847
1882
  }
1848
1883
  }
1849
1884
 
1885
+ standalone.registerPlugin("plugin-transform-modules-umd", pluginUMD);
1886
+ 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)));
1887
+ 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)));
1888
+ path.dirname(__filename$1);
1889
+ const BacktestKitCli = new Proxy({}, {
1890
+ get(_target, prop) {
1891
+ throw new Error(`@backtest-kit/cli is not available in this context (accessed: ${String(prop)})`);
1892
+ },
1893
+ });
1894
+ class BabelService {
1895
+ constructor() {
1896
+ this.loggerService = inject(TYPES.loggerService);
1897
+ this.transpile = (code) => {
1898
+ this.loggerService.log("babelService transpile", { codeLen: code.length });
1899
+ const result = standalone.transform(code, {
1900
+ filename: "index.ts",
1901
+ presets: ["env", "typescript"],
1902
+ plugins: [
1903
+ [
1904
+ "plugin-transform-modules-umd",
1905
+ {
1906
+ globals: {
1907
+ "backtest-kit": "BacktestKit",
1908
+ "@backtest-kit/cli": "BacktestKitCli",
1909
+ },
1910
+ moduleId: "Executor",
1911
+ },
1912
+ ],
1913
+ ],
1914
+ parserOpts: { strictMode: false },
1915
+ });
1916
+ if (!result.code) {
1917
+ throw new Error("BabelService transpile failed");
1918
+ }
1919
+ return result.code;
1920
+ };
1921
+ this.transpileAndRun = (code) => {
1922
+ this.loggerService.log("babelService transpileAndRun", {
1923
+ codeLen: code.length,
1924
+ });
1925
+ const module = { exports: {} };
1926
+ eval(this.transpile(code));
1927
+ return module.exports;
1928
+ };
1929
+ }
1930
+ }
1931
+ globalThis.BacktestKit = BacktestKit__namespace;
1932
+ globalThis.BacktestKitCli = BacktestKitCli;
1933
+
1850
1934
  {
1851
1935
  provide(TYPES.quickchartApiService, () => new QuickchartApiService());
1852
1936
  provide(TYPES.telegramApiService, () => new TelegramApiService());
@@ -1855,6 +1939,7 @@ class LiveProviderService {
1855
1939
  provide(TYPES.errorService, () => new ErrorService());
1856
1940
  provide(TYPES.loggerService, () => new LoggerService());
1857
1941
  provide(TYPES.resolveService, () => new ResolveService());
1942
+ provide(TYPES.babelService, () => new BabelService());
1858
1943
  }
1859
1944
  {
1860
1945
  provide(TYPES.moduleConnectionService, () => new ModuleConnectionService());
@@ -1893,6 +1978,7 @@ const baseServices = {
1893
1978
  errorService: inject(TYPES.errorService),
1894
1979
  loggerService: inject(TYPES.loggerService),
1895
1980
  resolveService: inject(TYPES.resolveService),
1981
+ babelService: inject(TYPES.babelService),
1896
1982
  };
1897
1983
  const connectionServices = {
1898
1984
  moduleConnectionService: inject(TYPES.moduleConnectionService),
@@ -1941,7 +2027,7 @@ const notifyShutdown = functoolsKit.singleshot(async () => {
1941
2027
 
1942
2028
  const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
1943
2029
  process.off("SIGINT", BEFORE_EXIT_FN$4);
1944
- const [running = null] = await backtestKit.Backtest.list();
2030
+ const [running = null] = await BacktestKit.Backtest.list();
1945
2031
  if (!running) {
1946
2032
  return;
1947
2033
  }
@@ -1950,7 +2036,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
1950
2036
  if (status === "fulfilled") {
1951
2037
  return;
1952
2038
  }
1953
- backtestKit.Backtest.stop(symbol, {
2039
+ BacktestKit.Backtest.stop(symbol, {
1954
2040
  exchangeName,
1955
2041
  strategyName,
1956
2042
  frameName,
@@ -1974,7 +2060,7 @@ main$4();
1974
2060
 
1975
2061
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
1976
2062
  process.off("SIGINT", BEFORE_EXIT_FN$3);
1977
- const [running = null] = await backtestKit.Live.list();
2063
+ const [running = null] = await BacktestKit.Live.list();
1978
2064
  if (!running) {
1979
2065
  return;
1980
2066
  }
@@ -1983,7 +2069,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
1983
2069
  if (status === "fulfilled") {
1984
2070
  return;
1985
2071
  }
1986
- backtestKit.Live.stop(symbol, {
2072
+ BacktestKit.Live.stop(symbol, {
1987
2073
  exchangeName,
1988
2074
  strategyName,
1989
2075
  });
@@ -2006,7 +2092,7 @@ main$3();
2006
2092
 
2007
2093
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2008
2094
  process.off("SIGINT", BEFORE_EXIT_FN$2);
2009
- const [running = null] = await backtestKit.Live.list();
2095
+ const [running = null] = await BacktestKit.Live.list();
2010
2096
  if (!running) {
2011
2097
  return;
2012
2098
  }
@@ -2015,11 +2101,11 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2015
2101
  if (status === "fulfilled") {
2016
2102
  return;
2017
2103
  }
2018
- backtestKit.Live.stop(symbol, {
2104
+ BacktestKit.Live.stop(symbol, {
2019
2105
  exchangeName,
2020
2106
  strategyName,
2021
2107
  });
2022
- backtestKit.listenDoneLive(cli.liveProviderService.disable);
2108
+ BacktestKit.listenDoneLive(cli.liveProviderService.disable);
2023
2109
  });
2024
2110
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2025
2111
  process.on("SIGINT", BEFORE_EXIT_FN$2);
package/build/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import * as BacktestKit from 'backtest-kit';
2
3
  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
4
  import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString, createAwaiter, TIMEOUT_SYMBOL, typo, retry, memoize, trycatch } from 'functools-kit';
4
5
  import fs, { constants } from 'fs';
@@ -21,6 +22,8 @@ import MarkdownIt from 'markdown-it';
21
22
  import sanitizeHtml from 'sanitize-html';
22
23
  import { JSDOM } from 'jsdom';
23
24
  import Mustache from 'mustache';
25
+ import { registerPlugin, transform } from '@babel/standalone';
26
+ import pluginUMD from '@babel/plugin-transform-modules-umd';
24
27
 
25
28
  /**
26
29
  * Fix for `Attempted to assign to readonly property (at redactToken)`
@@ -169,6 +172,7 @@ const baseServices$1 = {
169
172
  errorService: Symbol('errorService'),
170
173
  loggerService: Symbol('loggerService'),
171
174
  resolveService: Symbol('resolveService'),
175
+ babelService: Symbol('babelService'),
172
176
  };
173
177
  const connectionServices$1 = {
174
178
  moduleConnectionService: Symbol('moduleConnectionService'),
@@ -212,30 +216,50 @@ const TYPES = {
212
216
 
213
217
  const entrySubject = new BehaviorSubject();
214
218
 
215
- const __filename = fileURLToPath(import.meta.url);
216
- const __dirname = path.dirname(__filename);
217
- const require$1 = createRequire(import.meta.url);
219
+ const __filename$1 = fileURLToPath(import.meta.url);
220
+ const __dirname = path.dirname(__filename$1);
221
+ createRequire(import.meta.url);
218
222
  const REQUIRE_ENTRY_FACTORY = (filePath) => {
223
+ {
224
+ return false;
225
+ }
226
+ };
227
+ const IMPORT_ENTRY_FACTORY = async (filePath) => {
219
228
  try {
220
- require$1(filePath);
229
+ await import(pathToFileURL(filePath).href);
221
230
  return true;
222
231
  }
223
232
  catch {
224
233
  return false;
225
234
  }
226
235
  };
227
- const IMPORT_ENTRY_FACTORY = async (filePath) => {
228
- await import(pathToFileURL(filePath).href);
236
+ const BABEL_ENTRY_FACTORY = async (filePath, self) => {
237
+ const code = await fs$1.readFile(filePath, "utf-8");
238
+ try {
239
+ await self.babelService.transpileAndRun(code);
240
+ return true;
241
+ }
242
+ catch {
243
+ return false;
244
+ }
229
245
  };
230
- const LOAD_ENTRY_FN = async (filePath) => {
231
- if (!REQUIRE_ENTRY_FACTORY(filePath)) {
232
- await IMPORT_ENTRY_FACTORY(filePath);
246
+ const LOAD_ENTRY_FN = async (filePath, self) => {
247
+ if (REQUIRE_ENTRY_FACTORY()) {
248
+ return;
249
+ }
250
+ if (await IMPORT_ENTRY_FACTORY(filePath)) {
251
+ return;
252
+ }
253
+ if (await BABEL_ENTRY_FACTORY(filePath, self)) {
254
+ return;
233
255
  }
256
+ throw new Error(`Failed to load entry point: ${filePath}`);
234
257
  };
235
258
  let _is_launched = false;
236
259
  class ResolveService {
237
260
  constructor() {
238
261
  this.loggerService = inject(TYPES.loggerService);
262
+ this.babelService = inject(TYPES.babelService);
239
263
  this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');
240
264
  this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
241
265
  this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
@@ -252,7 +276,7 @@ class ResolveService {
252
276
  process.chdir(moduleRoot);
253
277
  dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
254
278
  dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
255
- await LOAD_ENTRY_FN(absolutePath);
279
+ await LOAD_ENTRY_FN(absolutePath, this);
256
280
  await entrySubject.next(absolutePath);
257
281
  }
258
282
  _is_launched = true;
@@ -1583,16 +1607,21 @@ class TelegramTemplateService {
1583
1607
  }
1584
1608
  }
1585
1609
 
1586
- const require = createRequire(import.meta.url);
1610
+ createRequire(import.meta.url);
1587
1611
  const getExtVariants = (fileName) => {
1588
1612
  const ext = path.extname(fileName);
1589
1613
  const base = ext ? fileName.slice(0, -ext.length) : fileName;
1590
1614
  return [fileName, `${base}.cjs`, `${base}.mjs`];
1591
1615
  };
1592
1616
  const REQUIRE_MODULE_FACTORY = (fileName) => {
1617
+ {
1618
+ return null;
1619
+ }
1620
+ };
1621
+ const IMPORT_MODULE_FACTORY = async (fileName) => {
1593
1622
  for (const variant of getExtVariants(fileName)) {
1594
1623
  try {
1595
- return require(variant);
1624
+ return await import(pathToFileURL(variant).href);
1596
1625
  }
1597
1626
  catch {
1598
1627
  continue;
@@ -1600,10 +1629,12 @@ const REQUIRE_MODULE_FACTORY = (fileName) => {
1600
1629
  }
1601
1630
  return null;
1602
1631
  };
1603
- const IMPORT_MODULE_FACTORY = async (fileName) => {
1632
+ const BABEL_MODULE_FACTORY = async (fileName, self) => {
1604
1633
  for (const variant of getExtVariants(fileName)) {
1605
1634
  try {
1606
- return await import(variant);
1635
+ const code = await fs$1.readFile(variant, "utf-8");
1636
+ const exports = self.babelService.transpileAndRun(code);
1637
+ return exports.default ?? exports;
1607
1638
  }
1608
1639
  catch {
1609
1640
  continue;
@@ -1620,18 +1651,22 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
1620
1651
  .then(() => true)
1621
1652
  .catch(() => false);
1622
1653
  const resolvedFile = hasOverride ? overridePath : targetPath;
1623
- if ((Ctor = REQUIRE_MODULE_FACTORY(resolvedFile))) {
1654
+ if ((Ctor = REQUIRE_MODULE_FACTORY())) {
1624
1655
  return typeof Ctor === "function" ? new Ctor() : Ctor;
1625
1656
  }
1626
1657
  if ((Ctor = await IMPORT_MODULE_FACTORY(resolvedFile))) {
1627
1658
  return typeof Ctor === "function" ? new Ctor() : Ctor;
1628
1659
  }
1660
+ if ((Ctor = await BABEL_MODULE_FACTORY(resolvedFile, self))) {
1661
+ return typeof Ctor === "function" ? new Ctor() : Ctor;
1662
+ }
1629
1663
  throw new Error(`Module module import failed for file: ${resolvedFile}`);
1630
1664
  };
1631
1665
  class ModuleConnectionService {
1632
1666
  constructor() {
1633
1667
  this.loggerService = inject(TYPES.loggerService);
1634
1668
  this.resolveService = inject(TYPES.resolveService);
1669
+ this.babelService = inject(TYPES.babelService);
1635
1670
  this.getInstance = memoize(([fileName]) => `${fileName}`, async (fileName) => {
1636
1671
  this.loggerService.log("moduleConnectionService getInstance", {
1637
1672
  fileName,
@@ -1825,6 +1860,55 @@ class LiveProviderService {
1825
1860
  }
1826
1861
  }
1827
1862
 
1863
+ registerPlugin("plugin-transform-modules-umd", pluginUMD);
1864
+ createRequire(import.meta.url);
1865
+ const __filename = fileURLToPath(import.meta.url);
1866
+ path.dirname(__filename);
1867
+ const BacktestKitCli = new Proxy({}, {
1868
+ get(_target, prop) {
1869
+ throw new Error(`@backtest-kit/cli is not available in this context (accessed: ${String(prop)})`);
1870
+ },
1871
+ });
1872
+ class BabelService {
1873
+ constructor() {
1874
+ this.loggerService = inject(TYPES.loggerService);
1875
+ this.transpile = (code) => {
1876
+ this.loggerService.log("babelService transpile", { codeLen: code.length });
1877
+ const result = transform(code, {
1878
+ filename: "index.ts",
1879
+ presets: ["env", "typescript"],
1880
+ plugins: [
1881
+ [
1882
+ "plugin-transform-modules-umd",
1883
+ {
1884
+ globals: {
1885
+ "backtest-kit": "BacktestKit",
1886
+ "@backtest-kit/cli": "BacktestKitCli",
1887
+ },
1888
+ moduleId: "Executor",
1889
+ },
1890
+ ],
1891
+ ],
1892
+ parserOpts: { strictMode: false },
1893
+ });
1894
+ if (!result.code) {
1895
+ throw new Error("BabelService transpile failed");
1896
+ }
1897
+ return result.code;
1898
+ };
1899
+ this.transpileAndRun = (code) => {
1900
+ this.loggerService.log("babelService transpileAndRun", {
1901
+ codeLen: code.length,
1902
+ });
1903
+ const module = { exports: {} };
1904
+ eval(this.transpile(code));
1905
+ return module.exports;
1906
+ };
1907
+ }
1908
+ }
1909
+ globalThis.BacktestKit = BacktestKit;
1910
+ globalThis.BacktestKitCli = BacktestKitCli;
1911
+
1828
1912
  {
1829
1913
  provide(TYPES.quickchartApiService, () => new QuickchartApiService());
1830
1914
  provide(TYPES.telegramApiService, () => new TelegramApiService());
@@ -1833,6 +1917,7 @@ class LiveProviderService {
1833
1917
  provide(TYPES.errorService, () => new ErrorService());
1834
1918
  provide(TYPES.loggerService, () => new LoggerService());
1835
1919
  provide(TYPES.resolveService, () => new ResolveService());
1920
+ provide(TYPES.babelService, () => new BabelService());
1836
1921
  }
1837
1922
  {
1838
1923
  provide(TYPES.moduleConnectionService, () => new ModuleConnectionService());
@@ -1871,6 +1956,7 @@ const baseServices = {
1871
1956
  errorService: inject(TYPES.errorService),
1872
1957
  loggerService: inject(TYPES.loggerService),
1873
1958
  resolveService: inject(TYPES.resolveService),
1959
+ babelService: inject(TYPES.babelService),
1874
1960
  };
1875
1961
  const connectionServices = {
1876
1962
  moduleConnectionService: inject(TYPES.moduleConnectionService),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "3.3.2",
3
+ "version": "3.3.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",
@@ -58,8 +58,10 @@
58
58
  "default": "./build/index.cjs"
59
59
  },
60
60
  "devDependencies": {
61
+ "@babel/plugin-transform-modules-umd": "7.27.1",
62
+ "@babel/standalone": "7.29.1",
61
63
  "@backtest-kit/ui": "3.3.2",
62
- "markdown-it": "14.1.1",
64
+ "@rollup/plugin-replace": "6.0.3",
63
65
  "@rollup/plugin-typescript": "11.1.6",
64
66
  "@types/image-size": "0.7.0",
65
67
  "@types/jsdom": "21.1.7",
@@ -68,6 +70,7 @@
68
70
  "@types/stack-trace": "0.0.33",
69
71
  "backtest-kit": "3.3.2",
70
72
  "glob": "11.0.1",
73
+ "markdown-it": "14.1.1",
71
74
  "rimraf": "6.0.1",
72
75
  "rollup": "3.29.5",
73
76
  "rollup-plugin-dts": "6.1.1",
@@ -78,10 +81,12 @@
78
81
  "worker-testbed": "1.0.12"
79
82
  },
80
83
  "peerDependencies": {
81
- "typescript": "^5.0.0",
84
+ "@babel/plugin-transform-modules-umd": "^7.27.1",
85
+ "@babel/standalone": "^7.29.1",
82
86
  "@backtest-kit/ui": "^3.3.2",
83
87
  "backtest-kit": "^3.3.2",
84
- "markdown-it": "^14.1.1"
88
+ "markdown-it": "^14.1.1",
89
+ "typescript": "^5.0.0"
85
90
  },
86
91
  "dependencies": {
87
92
  "ccxt": "4.5.39",
package/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as functools_kit from 'functools-kit';
2
+ import * as BacktestKit from 'backtest-kit';
2
3
  import { CandleInterval, TrailingTakeCommit, TrailingStopCommit, BreakevenCommit, PartialProfitCommit, PartialLossCommit, IStrategyTickResultScheduled, IStrategyTickResultCancelled, IStrategyTickResultOpened, IStrategyTickResultClosed, RiskContract, AverageBuyCommit } from 'backtest-kit';
3
4
  import { Input } from 'telegraf';
4
5
 
@@ -85,8 +86,22 @@ declare class FrameSchemaService {
85
86
  addSchema: (() => Promise<void>) & functools_kit.ISingleshotClearable;
86
87
  }
87
88
 
89
+ declare const BacktestKitCli: {};
90
+ declare global {
91
+ interface Window {
92
+ BacktestKit: typeof BacktestKit;
93
+ BacktestKitCli: typeof BacktestKitCli;
94
+ }
95
+ }
96
+ declare class BabelService {
97
+ readonly loggerService: LoggerService;
98
+ transpile: (code: string) => any;
99
+ transpileAndRun: (code: string) => Record<string, unknown>;
100
+ }
101
+
88
102
  declare class ResolveService {
89
- private readonly loggerService;
103
+ readonly loggerService: LoggerService;
104
+ readonly babelService: BabelService;
90
105
  readonly DEFAULT_TEMPLATE_DIR: string;
91
106
  readonly OVERRIDE_TEMPLATE_DIR: string;
92
107
  readonly OVERRIDE_MODULES_DIR: string;
@@ -203,6 +218,7 @@ type TBaseModuleCtor = new () => BaseModule;
203
218
  declare class ModuleConnectionService {
204
219
  readonly loggerService: LoggerService;
205
220
  readonly resolveService: ResolveService;
221
+ readonly babelService: BabelService;
206
222
  getInstance: ((fileName: string) => Promise<BaseModule>) & functools_kit.IClearableMemoize<string> & functools_kit.IControlMemoize<string, Promise<Partial<ILiveModule>>>;
207
223
  }
208
224
 
@@ -243,6 +259,7 @@ declare const cli: {
243
259
  errorService: ErrorService;
244
260
  loggerService: LoggerService;
245
261
  resolveService: ResolveService;
262
+ babelService: BabelService;
246
263
  telegramApiService: TelegramApiService;
247
264
  quickchartApiService: QuickchartApiService;
248
265
  };