backtest-kit 1.1.3 → 1.1.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/build/index.cjs CHANGED
@@ -1104,6 +1104,37 @@ class PersistSignalUtils {
1104
1104
  */
1105
1105
  const PersistSignalAdaper = new PersistSignalUtils();
1106
1106
 
1107
+ /**
1108
+ * Global signal emitter for all trading events (live + backtest).
1109
+ * Emits all signal events regardless of execution mode.
1110
+ */
1111
+ const signalEmitter = new functoolsKit.Subject();
1112
+ /**
1113
+ * Live trading signal emitter.
1114
+ * Emits only signals from live trading execution.
1115
+ */
1116
+ const signalLiveEmitter = new functoolsKit.Subject();
1117
+ /**
1118
+ * Backtest signal emitter.
1119
+ * Emits only signals from backtest execution.
1120
+ */
1121
+ const signalBacktestEmitter = new functoolsKit.Subject();
1122
+ /**
1123
+ * Error emitter for background execution errors.
1124
+ * Emits errors caught in background tasks (Live.background, Backtest.background).
1125
+ */
1126
+ const errorEmitter = new functoolsKit.Subject();
1127
+ /**
1128
+ * Done emitter for background execution completion.
1129
+ * Emits when background tasks complete (Live.background, Backtest.background).
1130
+ */
1131
+ const doneEmitter = new functoolsKit.Subject();
1132
+ /**
1133
+ * Progress emitter for backtest execution progress.
1134
+ * Emits progress updates during backtest execution.
1135
+ */
1136
+ const progressEmitter = new functoolsKit.Subject();
1137
+
1107
1138
  const INTERVAL_MINUTES$1 = {
1108
1139
  "1m": 1,
1109
1140
  "3m": 3,
@@ -1155,6 +1186,9 @@ const VALIDATE_SIGNAL_FN = (signal) => {
1155
1186
  }
1156
1187
  };
1157
1188
  const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
1189
+ if (self._isStopped) {
1190
+ return null;
1191
+ }
1158
1192
  const currentTime = self.params.execution.context.when.getTime();
1159
1193
  {
1160
1194
  const intervalMinutes = INTERVAL_MINUTES$1[self.params.interval];
@@ -1183,6 +1217,13 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
1183
1217
  return signalRow;
1184
1218
  }, {
1185
1219
  defaultValue: null,
1220
+ fallback: (error) => {
1221
+ backtest$1.loggerService.warn("ClientStrategy exception thrown", {
1222
+ error: functoolsKit.errorData(error),
1223
+ message: functoolsKit.getErrorMessage(error),
1224
+ });
1225
+ errorEmitter.next(error);
1226
+ },
1186
1227
  });
1187
1228
  const GET_AVG_PRICE_FN = (candles) => {
1188
1229
  const sumPriceVolume = candles.reduce((acc, c) => {
@@ -1241,6 +1282,7 @@ const WAIT_FOR_INIT_FN = async (self) => {
1241
1282
  class ClientStrategy {
1242
1283
  constructor(params) {
1243
1284
  this.params = params;
1285
+ this._isStopped = false;
1244
1286
  this._pendingSignal = null;
1245
1287
  this._lastSignalTimestamp = null;
1246
1288
  /**
@@ -1563,34 +1605,32 @@ class ClientStrategy {
1563
1605
  }
1564
1606
  return result;
1565
1607
  }
1608
+ /**
1609
+ * Stops the strategy from generating new signals.
1610
+ *
1611
+ * Sets internal flag to prevent getSignal from being called.
1612
+ * Does NOT close active pending signals - they continue monitoring until TP/SL/time_expired.
1613
+ *
1614
+ * Use case: Graceful shutdown in live trading without forcing position closure.
1615
+ *
1616
+ * @returns Promise that resolves immediately when stop flag is set
1617
+ *
1618
+ * @example
1619
+ * ```typescript
1620
+ * // In Live.background() cancellation
1621
+ * await strategy.stop();
1622
+ * // Existing signal will continue until natural close
1623
+ * ```
1624
+ */
1625
+ stop() {
1626
+ this.params.logger.debug("ClientStrategy stop", {
1627
+ hasPendingSignal: this._pendingSignal !== null,
1628
+ });
1629
+ this._isStopped = true;
1630
+ return Promise.resolve();
1631
+ }
1566
1632
  }
1567
1633
 
1568
- /**
1569
- * Global signal emitter for all trading events (live + backtest).
1570
- * Emits all signal events regardless of execution mode.
1571
- */
1572
- const signalEmitter = new functoolsKit.Subject();
1573
- /**
1574
- * Live trading signal emitter.
1575
- * Emits only signals from live trading execution.
1576
- */
1577
- const signalLiveEmitter = new functoolsKit.Subject();
1578
- /**
1579
- * Backtest signal emitter.
1580
- * Emits only signals from backtest execution.
1581
- */
1582
- const signalBacktestEmitter = new functoolsKit.Subject();
1583
- /**
1584
- * Error emitter for background execution errors.
1585
- * Emits errors caught in background tasks (Live.background, Backtest.background).
1586
- */
1587
- const errorEmitter = new functoolsKit.Subject();
1588
- /**
1589
- * Done emitter for background execution completion.
1590
- * Emits when background tasks complete (Live.background, Backtest.background).
1591
- */
1592
- const doneEmitter = new functoolsKit.Subject();
1593
-
1594
1634
  /**
1595
1635
  * Connection service routing strategy operations to correct ClientStrategy instance.
1596
1636
  *
@@ -1689,6 +1729,36 @@ class StrategyConnectionService {
1689
1729
  }
1690
1730
  return tick;
1691
1731
  };
1732
+ /**
1733
+ * Stops the specified strategy from generating new signals.
1734
+ *
1735
+ * Delegates to ClientStrategy.stop() which sets internal flag to prevent
1736
+ * getSignal from being called on subsequent ticks.
1737
+ *
1738
+ * @param strategyName - Name of strategy to stop
1739
+ * @returns Promise that resolves when stop flag is set
1740
+ */
1741
+ this.stop = async (strategyName) => {
1742
+ this.loggerService.log("strategyConnectionService stop", {
1743
+ strategyName,
1744
+ });
1745
+ const strategy = this.getStrategy(strategyName);
1746
+ await strategy.stop();
1747
+ };
1748
+ /**
1749
+ * Clears the memoized ClientStrategy instance from cache.
1750
+ *
1751
+ * Forces re-initialization of strategy on next getStrategy call.
1752
+ * Useful for resetting strategy state or releasing resources.
1753
+ *
1754
+ * @param strategyName - Name of strategy to clear from cache
1755
+ */
1756
+ this.clear = async (strategyName) => {
1757
+ this.loggerService.log("strategyConnectionService clear", {
1758
+ strategyName,
1759
+ });
1760
+ this.getStrategy.clear(strategyName);
1761
+ };
1692
1762
  }
1693
1763
  }
1694
1764
 
@@ -2032,6 +2102,35 @@ class StrategyGlobalService {
2032
2102
  backtest,
2033
2103
  });
2034
2104
  };
2105
+ /**
2106
+ * Stops the strategy from generating new signals.
2107
+ *
2108
+ * Delegates to StrategyConnectionService.stop() to set internal flag.
2109
+ * Does not require execution context.
2110
+ *
2111
+ * @param strategyName - Name of strategy to stop
2112
+ * @returns Promise that resolves when stop flag is set
2113
+ */
2114
+ this.stop = async (strategyName) => {
2115
+ this.loggerService.log("strategyGlobalService stop", {
2116
+ strategyName,
2117
+ });
2118
+ return await this.strategyConnectionService.stop(strategyName);
2119
+ };
2120
+ /**
2121
+ * Clears the memoized ClientStrategy instance from cache.
2122
+ *
2123
+ * Delegates to StrategyConnectionService.clear() to remove strategy from cache.
2124
+ * Forces re-initialization of strategy on next operation.
2125
+ *
2126
+ * @param strategyName - Name of strategy to clear from cache
2127
+ */
2128
+ this.clear = async (strategyName) => {
2129
+ this.loggerService.log("strategyGlobalService clear", {
2130
+ strategyName,
2131
+ });
2132
+ return await this.strategyConnectionService.clear(strategyName);
2133
+ };
2035
2134
  }
2036
2135
  }
2037
2136
 
@@ -2262,7 +2361,7 @@ class FrameSchemaService {
2262
2361
  register(key, value) {
2263
2362
  this.loggerService.log(`frameSchemaService register`, { key });
2264
2363
  this.validateShallow(value);
2265
- this._registry.register(key, value);
2364
+ this._registry = this._registry.register(key, value);
2266
2365
  }
2267
2366
  /**
2268
2367
  * Overrides an existing frame schema with partial updates.
@@ -2273,7 +2372,8 @@ class FrameSchemaService {
2273
2372
  */
2274
2373
  override(key, value) {
2275
2374
  this.loggerService.log(`frameSchemaService override`, { key });
2276
- this._registry.override(key, value);
2375
+ this._registry = this._registry.override(key, value);
2376
+ return this._registry.get(key);
2277
2377
  }
2278
2378
  /**
2279
2379
  * Retrieves a frame schema by name.
@@ -2307,6 +2407,7 @@ class BacktestLogicPrivateService {
2307
2407
  this.strategyGlobalService = inject(TYPES.strategyGlobalService);
2308
2408
  this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
2309
2409
  this.frameGlobalService = inject(TYPES.frameGlobalService);
2410
+ this.methodContextService = inject(TYPES.methodContextService);
2310
2411
  }
2311
2412
  /**
2312
2413
  * Runs backtest for a symbol, streaming closed signals as async generator.
@@ -2327,9 +2428,21 @@ class BacktestLogicPrivateService {
2327
2428
  symbol,
2328
2429
  });
2329
2430
  const timeframes = await this.frameGlobalService.getTimeframe(symbol);
2431
+ const totalFrames = timeframes.length;
2330
2432
  let i = 0;
2331
2433
  while (i < timeframes.length) {
2332
2434
  const when = timeframes[i];
2435
+ // Emit progress event if context is available
2436
+ {
2437
+ await progressEmitter.next({
2438
+ exchangeName: this.methodContextService.context.exchangeName,
2439
+ strategyName: this.methodContextService.context.strategyName,
2440
+ symbol,
2441
+ totalFrames,
2442
+ processedFrames: i,
2443
+ progress: totalFrames > 0 ? i / totalFrames : 0,
2444
+ });
2445
+ }
2333
2446
  const result = await this.strategyGlobalService.tick(symbol, when, true);
2334
2447
  // Если сигнал открыт, вызываем backtest
2335
2448
  if (result.action === "opened") {
@@ -2366,6 +2479,17 @@ class BacktestLogicPrivateService {
2366
2479
  }
2367
2480
  i++;
2368
2481
  }
2482
+ // Emit final progress event (100%)
2483
+ {
2484
+ await progressEmitter.next({
2485
+ exchangeName: this.methodContextService.context.exchangeName,
2486
+ strategyName: this.methodContextService.context.strategyName,
2487
+ symbol,
2488
+ totalFrames,
2489
+ processedFrames: totalFrames,
2490
+ progress: 1.0,
2491
+ });
2492
+ }
2369
2493
  }
2370
2494
  }
2371
2495
 
@@ -2720,10 +2844,17 @@ let ReportStorage$1 = class ReportStorage {
2720
2844
  return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
2721
2845
  }
2722
2846
  const header = columns$1.map((col) => col.label);
2847
+ const separator = columns$1.map(() => "---");
2723
2848
  const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
2724
- const tableData = [header, ...rows];
2725
- const table = functoolsKit.str.table(tableData);
2726
- return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", `Total signals: ${this._signalList.length}`, "", table, "", "", `*Generated: ${new Date().toISOString()}*`);
2849
+ const tableData = [header, separator, ...rows];
2850
+ const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
2851
+ // Calculate statistics
2852
+ const totalSignals = this._signalList.length;
2853
+ const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
2854
+ const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
2855
+ const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
2856
+ const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
2857
+ return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${totalSignals}`, `**Closed signals:** ${totalSignals}`, `**Win rate:** ${((winCount / totalSignals) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`, `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`, `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`);
2727
2858
  }
2728
2859
  /**
2729
2860
  * Saves strategy report to disk.
@@ -3118,9 +3249,10 @@ class ReportStorage {
3118
3249
  return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
3119
3250
  }
3120
3251
  const header = columns.map((col) => col.label);
3252
+ const separator = columns.map(() => "---");
3121
3253
  const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
3122
- const tableData = [header, ...rows];
3123
- const table = functoolsKit.str.table(tableData);
3254
+ const tableData = [header, separator, ...rows];
3255
+ const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
3124
3256
  // Calculate statistics
3125
3257
  const closedEvents = this._eventList.filter((e) => e.action === "closed");
3126
3258
  const totalClosed = closedEvents.length;
@@ -3129,11 +3261,14 @@ class ReportStorage {
3129
3261
  const avgPnl = totalClosed > 0
3130
3262
  ? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
3131
3263
  : 0;
3132
- return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", `Total events: ${this._eventList.length}`, `Closed signals: ${totalClosed}`, totalClosed > 0
3133
- ? `Win rate: ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
3264
+ const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
3265
+ return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
3266
+ ? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
3267
+ : "", totalClosed > 0
3268
+ ? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
3134
3269
  : "", totalClosed > 0
3135
- ? `Average PNL: ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
3136
- : "", "", table, "", "", `*Generated: ${new Date().toISOString()}*`);
3270
+ ? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
3271
+ : "");
3137
3272
  }
3138
3273
  /**
3139
3274
  * Saves strategy report to disk.
@@ -3373,6 +3508,15 @@ class ExchangeValidationService {
3373
3508
  }
3374
3509
  return true;
3375
3510
  });
3511
+ /**
3512
+ * Returns a list of all registered exchange schemas
3513
+ * @public
3514
+ * @returns Array of exchange schemas with their configurations
3515
+ */
3516
+ this.list = async () => {
3517
+ this.loggerService.log("exchangeValidationService list");
3518
+ return Array.from(this._exchangeMap.values());
3519
+ };
3376
3520
  }
3377
3521
  }
3378
3522
 
@@ -3425,6 +3569,15 @@ class StrategyValidationService {
3425
3569
  }
3426
3570
  return true;
3427
3571
  });
3572
+ /**
3573
+ * Returns a list of all registered strategy schemas
3574
+ * @public
3575
+ * @returns Array of strategy schemas with their configurations
3576
+ */
3577
+ this.list = async () => {
3578
+ this.loggerService.log("strategyValidationService list");
3579
+ return Array.from(this._strategyMap.values());
3580
+ };
3428
3581
  }
3429
3582
  }
3430
3583
 
@@ -3477,6 +3630,15 @@ class FrameValidationService {
3477
3630
  }
3478
3631
  return true;
3479
3632
  });
3633
+ /**
3634
+ * Returns a list of all registered frame schemas
3635
+ * @public
3636
+ * @returns Array of frame schemas with their configurations
3637
+ */
3638
+ this.list = async () => {
3639
+ this.loggerService.log("frameValidationService list");
3640
+ return Array.from(this._frameMap.values());
3641
+ };
3480
3642
  }
3481
3643
  }
3482
3644
 
@@ -3722,6 +3884,102 @@ function addFrame(frameSchema) {
3722
3884
  backtest$1.frameSchemaService.register(frameSchema.frameName, frameSchema);
3723
3885
  }
3724
3886
 
3887
+ const LIST_EXCHANGES_METHOD_NAME = "list.listExchanges";
3888
+ const LIST_STRATEGIES_METHOD_NAME = "list.listStrategies";
3889
+ const LIST_FRAMES_METHOD_NAME = "list.listFrames";
3890
+ /**
3891
+ * Returns a list of all registered exchange schemas.
3892
+ *
3893
+ * Retrieves all exchanges that have been registered via addExchange().
3894
+ * Useful for debugging, documentation, or building dynamic UIs.
3895
+ *
3896
+ * @returns Array of exchange schemas with their configurations
3897
+ *
3898
+ * @example
3899
+ * ```typescript
3900
+ * import { listExchanges, addExchange } from "backtest-kit";
3901
+ *
3902
+ * addExchange({
3903
+ * exchangeName: "binance",
3904
+ * note: "Binance cryptocurrency exchange",
3905
+ * getCandles: async (symbol, interval, since, limit) => [...],
3906
+ * formatPrice: async (symbol, price) => price.toFixed(2),
3907
+ * formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
3908
+ * });
3909
+ *
3910
+ * const exchanges = listExchanges();
3911
+ * console.log(exchanges);
3912
+ * // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
3913
+ * ```
3914
+ */
3915
+ async function listExchanges() {
3916
+ backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
3917
+ return await backtest$1.exchangeValidationService.list();
3918
+ }
3919
+ /**
3920
+ * Returns a list of all registered strategy schemas.
3921
+ *
3922
+ * Retrieves all strategies that have been registered via addStrategy().
3923
+ * Useful for debugging, documentation, or building dynamic UIs.
3924
+ *
3925
+ * @returns Array of strategy schemas with their configurations
3926
+ *
3927
+ * @example
3928
+ * ```typescript
3929
+ * import { listStrategies, addStrategy } from "backtest-kit";
3930
+ *
3931
+ * addStrategy({
3932
+ * strategyName: "my-strategy",
3933
+ * note: "Simple moving average crossover strategy",
3934
+ * interval: "5m",
3935
+ * getSignal: async (symbol) => ({
3936
+ * position: "long",
3937
+ * priceOpen: 50000,
3938
+ * priceTakeProfit: 51000,
3939
+ * priceStopLoss: 49000,
3940
+ * minuteEstimatedTime: 60,
3941
+ * }),
3942
+ * });
3943
+ *
3944
+ * const strategies = listStrategies();
3945
+ * console.log(strategies);
3946
+ * // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
3947
+ * ```
3948
+ */
3949
+ async function listStrategies() {
3950
+ backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
3951
+ return await backtest$1.strategyValidationService.list();
3952
+ }
3953
+ /**
3954
+ * Returns a list of all registered frame schemas.
3955
+ *
3956
+ * Retrieves all frames that have been registered via addFrame().
3957
+ * Useful for debugging, documentation, or building dynamic UIs.
3958
+ *
3959
+ * @returns Array of frame schemas with their configurations
3960
+ *
3961
+ * @example
3962
+ * ```typescript
3963
+ * import { listFrames, addFrame } from "backtest-kit";
3964
+ *
3965
+ * addFrame({
3966
+ * frameName: "1d-backtest",
3967
+ * note: "One day backtest period for testing",
3968
+ * interval: "1m",
3969
+ * startDate: new Date("2024-01-01T00:00:00Z"),
3970
+ * endDate: new Date("2024-01-02T00:00:00Z"),
3971
+ * });
3972
+ *
3973
+ * const frames = listFrames();
3974
+ * console.log(frames);
3975
+ * // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
3976
+ * ```
3977
+ */
3978
+ async function listFrames() {
3979
+ backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
3980
+ return await backtest$1.frameValidationService.list();
3981
+ }
3982
+
3725
3983
  const LISTEN_SIGNAL_METHOD_NAME = "event.listenSignal";
3726
3984
  const LISTEN_SIGNAL_ONCE_METHOD_NAME = "event.listenSignalOnce";
3727
3985
  const LISTEN_SIGNAL_LIVE_METHOD_NAME = "event.listenSignalLive";
@@ -3731,6 +3989,7 @@ const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce"
3731
3989
  const LISTEN_ERROR_METHOD_NAME = "event.listenError";
3732
3990
  const LISTEN_DONE_METHOD_NAME = "event.listenDone";
3733
3991
  const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
3992
+ const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
3734
3993
  /**
3735
3994
  * Subscribes to all signal events with queued async processing.
3736
3995
  *
@@ -3986,6 +4245,40 @@ function listenDoneOnce(filterFn, fn) {
3986
4245
  backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
3987
4246
  return doneEmitter.filter(filterFn).once(fn);
3988
4247
  }
4248
+ /**
4249
+ * Subscribes to backtest progress events with queued async processing.
4250
+ *
4251
+ * Emits during Backtest.background() execution to track progress.
4252
+ * Events are processed sequentially in order received, even if callback is async.
4253
+ * Uses queued wrapper to prevent concurrent execution of the callback.
4254
+ *
4255
+ * @param fn - Callback function to handle progress events
4256
+ * @returns Unsubscribe function to stop listening to events
4257
+ *
4258
+ * @example
4259
+ * ```typescript
4260
+ * import { listenProgress, Backtest } from "backtest-kit";
4261
+ *
4262
+ * const unsubscribe = listenProgress((event) => {
4263
+ * console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
4264
+ * console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
4265
+ * console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
4266
+ * });
4267
+ *
4268
+ * Backtest.background("BTCUSDT", {
4269
+ * strategyName: "my-strategy",
4270
+ * exchangeName: "binance",
4271
+ * frameName: "1d-backtest"
4272
+ * });
4273
+ *
4274
+ * // Later: stop listening
4275
+ * unsubscribe();
4276
+ * ```
4277
+ */
4278
+ function listenProgress(fn) {
4279
+ backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
4280
+ return progressEmitter.subscribe(functoolsKit.queued(async (event) => fn(event)));
4281
+ }
3989
4282
 
3990
4283
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
3991
4284
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -4164,6 +4457,7 @@ class BacktestUtils {
4164
4457
  context,
4165
4458
  });
4166
4459
  backtest$1.backtestMarkdownService.clear(context.strategyName);
4460
+ backtest$1.strategyGlobalService.clear(context.strategyName);
4167
4461
  return backtest$1.backtestGlobalService.run(symbol, context);
4168
4462
  };
4169
4463
  /**
@@ -4192,14 +4486,9 @@ class BacktestUtils {
4192
4486
  symbol,
4193
4487
  context,
4194
4488
  });
4195
- const iterator = this.run(symbol, context);
4196
4489
  let isStopped = false;
4197
4490
  const task = async () => {
4198
- while (true) {
4199
- const { done } = await iterator.next();
4200
- if (done) {
4201
- break;
4202
- }
4491
+ for await (const _ of this.run(symbol, context)) {
4203
4492
  if (isStopped) {
4204
4493
  break;
4205
4494
  }
@@ -4213,6 +4502,7 @@ class BacktestUtils {
4213
4502
  };
4214
4503
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
4215
4504
  return () => {
4505
+ backtest$1.strategyGlobalService.stop(context.strategyName);
4216
4506
  isStopped = true;
4217
4507
  };
4218
4508
  };
@@ -4329,6 +4619,7 @@ class LiveUtils {
4329
4619
  context,
4330
4620
  });
4331
4621
  backtest$1.liveMarkdownService.clear(context.strategyName);
4622
+ backtest$1.strategyGlobalService.clear(context.strategyName);
4332
4623
  return backtest$1.liveGlobalService.run(symbol, context);
4333
4624
  };
4334
4625
  /**
@@ -4357,19 +4648,10 @@ class LiveUtils {
4357
4648
  symbol,
4358
4649
  context,
4359
4650
  });
4360
- const iterator = this.run(symbol, context);
4361
4651
  let isStopped = false;
4362
- let lastValue = null;
4363
4652
  const task = async () => {
4364
- while (true) {
4365
- const { value, done } = await iterator.next();
4366
- if (value) {
4367
- lastValue = value;
4368
- }
4369
- if (done) {
4370
- break;
4371
- }
4372
- if (lastValue?.action === "closed" && isStopped) {
4653
+ for await (const signal of this.run(symbol, context)) {
4654
+ if (signal?.action === "closed" && isStopped) {
4373
4655
  break;
4374
4656
  }
4375
4657
  }
@@ -4382,6 +4664,7 @@ class LiveUtils {
4382
4664
  };
4383
4665
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
4384
4666
  return () => {
4667
+ backtest$1.strategyGlobalService.stop(context.strategyName);
4385
4668
  isStopped = true;
4386
4669
  };
4387
4670
  };
@@ -4460,9 +4743,13 @@ exports.getCandles = getCandles;
4460
4743
  exports.getDate = getDate;
4461
4744
  exports.getMode = getMode;
4462
4745
  exports.lib = backtest;
4746
+ exports.listExchanges = listExchanges;
4747
+ exports.listFrames = listFrames;
4748
+ exports.listStrategies = listStrategies;
4463
4749
  exports.listenDone = listenDone;
4464
4750
  exports.listenDoneOnce = listenDoneOnce;
4465
4751
  exports.listenError = listenError;
4752
+ exports.listenProgress = listenProgress;
4466
4753
  exports.listenSignal = listenSignal;
4467
4754
  exports.listenSignalBacktest = listenSignalBacktest;
4468
4755
  exports.listenSignalBacktestOnce = listenSignalBacktestOnce;