backtest-kit 1.5.3 → 1.5.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
@@ -3179,15 +3179,17 @@ class ClientStrategy {
3179
3179
  backtest,
3180
3180
  });
3181
3181
  this._isStopped = true;
3182
- // Clear scheduled signal if exists
3183
- if (!this._scheduledSignal) {
3182
+ if (!this._pendingSignal) {
3184
3183
  return;
3185
3184
  }
3186
- this._scheduledSignal = null;
3185
+ this._pendingSignal = null;
3186
+ if (this.params.callbacks?.onWrite) {
3187
+ this.params.callbacks.onWrite(symbol, this._pendingSignal, backtest);
3188
+ }
3187
3189
  if (backtest) {
3188
3190
  return;
3189
3191
  }
3190
- await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, this.params.execution.context.symbol, this.params.strategyName);
3192
+ await PersistScheduleAdapter.writeScheduleData(this._pendingSignal, symbol, strategyName);
3191
3193
  }
3192
3194
  }
3193
3195
 
@@ -13402,17 +13404,50 @@ const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
13402
13404
  const BACKTEST_METHOD_NAME_STOP = "BacktestUtils.stop";
13403
13405
  const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
13404
13406
  const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
13407
+ const BACKTEST_METHOD_NAME_TASK = "BacktestUtils.task";
13408
+ const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
13405
13409
  /**
13406
- * Utility class for backtest operations.
13410
+ * Internal task function that runs backtest and handles completion.
13411
+ * Consumes backtest results and updates instance state flags.
13407
13412
  *
13408
- * Provides simplified access to backtestCommandService.run() with logging.
13409
- * Exported as singleton instance for convenient usage.
13413
+ * @param symbol - Trading pair symbol
13414
+ * @param context - Execution context with strategy, exchange, and frame names
13415
+ * @param self - BacktestInstance reference for state management
13416
+ * @returns Promise that resolves when backtest completes
13417
+ *
13418
+ * @internal
13419
+ */
13420
+ const INSTANCE_TASK_FN$2 = async (symbol, context, self) => {
13421
+ {
13422
+ self._isStopped = false;
13423
+ self._isDone = false;
13424
+ }
13425
+ for await (const _ of self.run(symbol, context)) {
13426
+ if (self._isStopped) {
13427
+ break;
13428
+ }
13429
+ }
13430
+ if (!self._isDone) {
13431
+ await doneBacktestSubject.next({
13432
+ exchangeName: context.exchangeName,
13433
+ strategyName: context.strategyName,
13434
+ backtest: true,
13435
+ symbol,
13436
+ });
13437
+ }
13438
+ self._isDone = true;
13439
+ };
13440
+ /**
13441
+ * Instance class for backtest operations on a specific symbol-strategy pair.
13442
+ *
13443
+ * Provides isolated backtest execution and reporting for a single symbol-strategy combination.
13444
+ * Each instance maintains its own state and context.
13410
13445
  *
13411
13446
  * @example
13412
13447
  * ```typescript
13413
- * import { Backtest } from "./classes/Backtest";
13448
+ * const instance = new BacktestInstance("BTCUSDT", "my-strategy");
13414
13449
  *
13415
- * for await (const result of Backtest.run("BTCUSDT", {
13450
+ * for await (const result of instance.run("BTCUSDT", {
13416
13451
  * strategyName: "my-strategy",
13417
13452
  * exchangeName: "my-exchange",
13418
13453
  * frameName: "1d-backtest"
@@ -13421,8 +13456,57 @@ const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
13421
13456
  * }
13422
13457
  * ```
13423
13458
  */
13424
- class BacktestUtils {
13425
- constructor() {
13459
+ class BacktestInstance {
13460
+ /**
13461
+ * Creates a new BacktestInstance for a specific symbol-strategy pair.
13462
+ *
13463
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13464
+ * @param strategyName - Strategy name for this backtest instance
13465
+ */
13466
+ constructor(symbol, strategyName) {
13467
+ this.symbol = symbol;
13468
+ this.strategyName = strategyName;
13469
+ /** Internal flag indicating if backtest was stopped manually */
13470
+ this._isStopped = false;
13471
+ /** Internal flag indicating if backtest task completed */
13472
+ this._isDone = false;
13473
+ /**
13474
+ * Internal singlerun task that executes the backtest.
13475
+ * Ensures only one backtest run per instance using singlerun wrapper.
13476
+ *
13477
+ * @param symbol - Trading pair symbol
13478
+ * @param context - Execution context with strategy, exchange, and frame names
13479
+ * @returns Promise that resolves when backtest completes
13480
+ *
13481
+ * @internal
13482
+ */
13483
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
13484
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_TASK, {
13485
+ symbol,
13486
+ context,
13487
+ });
13488
+ return await INSTANCE_TASK_FN$2(symbol, context, this);
13489
+ });
13490
+ /**
13491
+ * Gets the current status of this backtest instance.
13492
+ *
13493
+ * @returns Promise resolving to status object with symbol, strategyName, and task status
13494
+ *
13495
+ * @example
13496
+ * ```typescript
13497
+ * const instance = new BacktestInstance("BTCUSDT", "my-strategy");
13498
+ * const status = await instance.getStatus();
13499
+ * console.log(status.status); // "idle", "running", or "done"
13500
+ * ```
13501
+ */
13502
+ this.getStatus = async () => {
13503
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_STATUS);
13504
+ return {
13505
+ symbol: this.symbol,
13506
+ strategyName: this.strategyName,
13507
+ status: this.task.getStatus(),
13508
+ };
13509
+ };
13426
13510
  /**
13427
13511
  * Runs backtest for a symbol with context propagation.
13428
13512
  *
@@ -13460,13 +13544,12 @@ class BacktestUtils {
13460
13544
  *
13461
13545
  * @example
13462
13546
  * ```typescript
13463
- * // Run backtest silently, only callbacks will fire
13464
- * await Backtest.background("BTCUSDT", {
13547
+ * const instance = new BacktestInstance();
13548
+ * const cancel = instance.background("BTCUSDT", {
13465
13549
  * strategyName: "my-strategy",
13466
13550
  * exchangeName: "my-exchange",
13467
13551
  * frameName: "1d-backtest"
13468
13552
  * });
13469
- * console.log("Backtest completed");
13470
13553
  * ```
13471
13554
  */
13472
13555
  this.background = (symbol, context) => {
@@ -13474,25 +13557,7 @@ class BacktestUtils {
13474
13557
  symbol,
13475
13558
  context,
13476
13559
  });
13477
- let isStopped = false;
13478
- let isDone = false;
13479
- const task = async () => {
13480
- for await (const _ of this.run(symbol, context)) {
13481
- if (isStopped) {
13482
- break;
13483
- }
13484
- }
13485
- if (!isDone) {
13486
- await doneBacktestSubject.next({
13487
- exchangeName: context.exchangeName,
13488
- strategyName: context.strategyName,
13489
- backtest: true,
13490
- symbol,
13491
- });
13492
- }
13493
- isDone = true;
13494
- };
13495
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13560
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13496
13561
  return () => {
13497
13562
  backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13498
13563
  backtest$1.strategyGlobalService
@@ -13501,7 +13566,7 @@ class BacktestUtils {
13501
13566
  if (pendingSignal) {
13502
13567
  return;
13503
13568
  }
13504
- if (!isDone) {
13569
+ if (!this._isDone) {
13505
13570
  await doneBacktestSubject.next({
13506
13571
  exchangeName: context.exchangeName,
13507
13572
  strategyName: context.strategyName,
@@ -13509,9 +13574,9 @@ class BacktestUtils {
13509
13574
  symbol,
13510
13575
  });
13511
13576
  }
13512
- isDone = true;
13577
+ this._isDone = true;
13513
13578
  });
13514
- isStopped = true;
13579
+ this._isStopped = true;
13515
13580
  };
13516
13581
  };
13517
13582
  /**
@@ -13527,8 +13592,8 @@ class BacktestUtils {
13527
13592
  *
13528
13593
  * @example
13529
13594
  * ```typescript
13530
- * // Stop strategy after some condition
13531
- * await Backtest.stop("BTCUSDT", "my-strategy");
13595
+ * const instance = new BacktestInstance();
13596
+ * await instance.stop("BTCUSDT", "my-strategy");
13532
13597
  * ```
13533
13598
  */
13534
13599
  this.stop = async (symbol, strategyName) => {
@@ -13547,7 +13612,8 @@ class BacktestUtils {
13547
13612
  *
13548
13613
  * @example
13549
13614
  * ```typescript
13550
- * const stats = await Backtest.getData("BTCUSDT", "my-strategy");
13615
+ * const instance = new BacktestInstance();
13616
+ * const stats = await instance.getData("BTCUSDT", "my-strategy");
13551
13617
  * console.log(stats.sharpeRatio, stats.winRate);
13552
13618
  * ```
13553
13619
  */
@@ -13567,7 +13633,8 @@ class BacktestUtils {
13567
13633
  *
13568
13634
  * @example
13569
13635
  * ```typescript
13570
- * const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
13636
+ * const instance = new BacktestInstance();
13637
+ * const markdown = await instance.getReport("BTCUSDT", "my-strategy");
13571
13638
  * console.log(markdown);
13572
13639
  * ```
13573
13640
  */
@@ -13587,11 +13654,12 @@ class BacktestUtils {
13587
13654
  *
13588
13655
  * @example
13589
13656
  * ```typescript
13657
+ * const instance = new BacktestInstance();
13590
13658
  * // Save to default path: ./dump/backtest/my-strategy.md
13591
- * await Backtest.dump("BTCUSDT", "my-strategy");
13659
+ * await instance.dump("BTCUSDT", "my-strategy");
13592
13660
  *
13593
13661
  * // Save to custom path: ./custom/path/my-strategy.md
13594
- * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
13662
+ * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
13595
13663
  * ```
13596
13664
  */
13597
13665
  this.dump = async (symbol, strategyName, path) => {
@@ -13605,7 +13673,10 @@ class BacktestUtils {
13605
13673
  }
13606
13674
  }
13607
13675
  /**
13608
- * Singleton instance of BacktestUtils for convenient backtest operations.
13676
+ * Utility class for backtest operations.
13677
+ *
13678
+ * Provides simplified access to backtestCommandService.run() with logging.
13679
+ * Exported as singleton instance for convenient usage.
13609
13680
  *
13610
13681
  * @example
13611
13682
  * ```typescript
@@ -13616,150 +13687,59 @@ class BacktestUtils {
13616
13687
  * exchangeName: "my-exchange",
13617
13688
  * frameName: "1d-backtest"
13618
13689
  * })) {
13619
- * if (result.action === "closed") {
13620
- * console.log("PNL:", result.pnl.pnlPercentage);
13621
- * }
13622
- * }
13623
- * ```
13624
- */
13625
- const Backtest = new BacktestUtils();
13626
-
13627
- const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
13628
- const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
13629
- const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
13630
- const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
13631
- const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
13632
- /**
13633
- * Utility class for live trading operations.
13634
- *
13635
- * Provides simplified access to liveCommandService.run() with logging.
13636
- * Exported as singleton instance for convenient usage.
13637
- *
13638
- * Features:
13639
- * - Infinite async generator (never completes)
13640
- * - Crash recovery via persisted state
13641
- * - Real-time progression with Date.now()
13642
- *
13643
- * @example
13644
- * ```typescript
13645
- * import { Live } from "./classes/Live";
13646
- *
13647
- * // Infinite loop - use Ctrl+C to stop
13648
- * for await (const result of Live.run("BTCUSDT", {
13649
- * strategyName: "my-strategy",
13650
- * exchangeName: "my-exchange",
13651
- * frameName: ""
13652
- * })) {
13653
- * if (result.action === "opened") {
13654
- * console.log("Signal opened:", result.signal);
13655
- * } else if (result.action === "closed") {
13656
- * console.log("PNL:", result.pnl.pnlPercentage);
13657
- * }
13690
+ * console.log("Closed signal PNL:", result.pnl.pnlPercentage);
13658
13691
  * }
13659
13692
  * ```
13660
13693
  */
13661
- class LiveUtils {
13694
+ class BacktestUtils {
13662
13695
  constructor() {
13663
13696
  /**
13664
- * Runs live trading for a symbol with context propagation.
13665
- *
13666
- * Infinite async generator with crash recovery support.
13667
- * Process can crash and restart - state will be recovered from disk.
13697
+ * Memoized function to get or create BacktestInstance for a symbol-strategy pair.
13698
+ * Each symbol-strategy combination gets its own isolated instance.
13699
+ */
13700
+ this._getInstance = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new BacktestInstance(symbol, strategyName));
13701
+ /**
13702
+ * Runs backtest for a symbol with context propagation.
13668
13703
  *
13669
13704
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13670
- * @param context - Execution context with strategy and exchange names
13671
- * @returns Infinite async generator yielding opened and closed signals
13705
+ * @param context - Execution context with strategy, exchange, and frame names
13706
+ * @returns Async generator yielding closed signals with PNL
13672
13707
  */
13673
13708
  this.run = (symbol, context) => {
13674
- backtest$1.loggerService.info(LIVE_METHOD_NAME_RUN, {
13675
- symbol,
13676
- context,
13677
- });
13678
- {
13679
- backtest$1.liveMarkdownService.clear({ symbol, strategyName: context.strategyName });
13680
- backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13681
- }
13682
- {
13683
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13684
- }
13685
- {
13686
- const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
13687
- riskName && backtest$1.riskGlobalService.clear(riskName);
13688
- }
13689
- return backtest$1.liveCommandService.run(symbol, context);
13709
+ const instance = this._getInstance(symbol, context.strategyName);
13710
+ return instance.run(symbol, context);
13690
13711
  };
13691
13712
  /**
13692
- * Runs live trading in background without yielding results.
13713
+ * Runs backtest in background without yielding results.
13693
13714
  *
13694
- * Consumes all live trading results internally without exposing them.
13695
- * Infinite loop - will run until process is stopped or crashes.
13696
- * Useful for running live trading for side effects only (callbacks, persistence).
13715
+ * Consumes all backtest results internally without exposing them.
13716
+ * Useful for running backtests for side effects only (callbacks, logging).
13697
13717
  *
13698
13718
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13699
- * @param context - Execution context with strategy and exchange names
13719
+ * @param context - Execution context with strategy, exchange, and frame names
13700
13720
  * @returns Cancellation closure
13701
13721
  *
13702
13722
  * @example
13703
13723
  * ```typescript
13704
- * // Run live trading silently in background, only callbacks will fire
13705
- * // This will run forever until Ctrl+C
13706
- * await Live.background("BTCUSDT", {
13724
+ * // Run backtest silently, only callbacks will fire
13725
+ * await Backtest.background("BTCUSDT", {
13707
13726
  * strategyName: "my-strategy",
13708
- * exchangeName: "my-exchange"
13727
+ * exchangeName: "my-exchange",
13728
+ * frameName: "1d-backtest"
13709
13729
  * });
13730
+ * console.log("Backtest completed");
13710
13731
  * ```
13711
13732
  */
13712
13733
  this.background = (symbol, context) => {
13713
- backtest$1.loggerService.info(LIVE_METHOD_NAME_BACKGROUND, {
13714
- symbol,
13715
- context,
13716
- });
13717
- let isStopped = false;
13718
- let isDone = false;
13719
- const task = async () => {
13720
- for await (const signal of this.run(symbol, context)) {
13721
- if (signal?.action === "closed" && isStopped) {
13722
- break;
13723
- }
13724
- }
13725
- if (!isDone) {
13726
- await doneLiveSubject.next({
13727
- exchangeName: context.exchangeName,
13728
- strategyName: context.strategyName,
13729
- backtest: false,
13730
- symbol,
13731
- });
13732
- }
13733
- isDone = true;
13734
- };
13735
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13736
- return () => {
13737
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
13738
- backtest$1.strategyGlobalService
13739
- .getPendingSignal(symbol, context.strategyName)
13740
- .then(async (pendingSignal) => {
13741
- if (pendingSignal) {
13742
- return;
13743
- }
13744
- if (!isDone) {
13745
- await doneLiveSubject.next({
13746
- exchangeName: context.exchangeName,
13747
- strategyName: context.strategyName,
13748
- backtest: false,
13749
- symbol,
13750
- });
13751
- }
13752
- isDone = true;
13753
- });
13754
- isStopped = true;
13755
- };
13734
+ const instance = this._getInstance(symbol, context.strategyName);
13735
+ return instance.background(symbol, context);
13756
13736
  };
13757
13737
  /**
13758
13738
  * Stops the strategy from generating new signals.
13759
13739
  *
13760
13740
  * Sets internal flag to prevent strategy from opening new signals.
13761
13741
  * Current active signal (if any) will complete normally.
13762
- * Live trading will stop at the next safe point (idle/closed state).
13742
+ * Backtest will stop at the next safe point (idle state or after signal closes).
13763
13743
  *
13764
13744
  * @param symbol - Trading pair symbol
13765
13745
  * @param strategyName - Strategy name to stop
@@ -13767,19 +13747,16 @@ class LiveUtils {
13767
13747
  *
13768
13748
  * @example
13769
13749
  * ```typescript
13770
- * // Stop live trading gracefully
13771
- * await Live.stop("BTCUSDT", "my-strategy");
13750
+ * // Stop strategy after some condition
13751
+ * await Backtest.stop("BTCUSDT", "my-strategy");
13772
13752
  * ```
13773
13753
  */
13774
13754
  this.stop = async (symbol, strategyName) => {
13775
- backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
13776
- symbol,
13777
- strategyName,
13778
- });
13779
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
13755
+ const instance = this._getInstance(symbol, strategyName);
13756
+ return await instance.stop(symbol, strategyName);
13780
13757
  };
13781
13758
  /**
13782
- * Gets statistical data from all live trading events for a symbol-strategy pair.
13759
+ * Gets statistical data from all closed signals for a symbol-strategy pair.
13783
13760
  *
13784
13761
  * @param symbol - Trading pair symbol
13785
13762
  * @param strategyName - Strategy name to get data for
@@ -13787,19 +13764,16 @@ class LiveUtils {
13787
13764
  *
13788
13765
  * @example
13789
13766
  * ```typescript
13790
- * const stats = await Live.getData("BTCUSDT", "my-strategy");
13767
+ * const stats = await Backtest.getData("BTCUSDT", "my-strategy");
13791
13768
  * console.log(stats.sharpeRatio, stats.winRate);
13792
13769
  * ```
13793
13770
  */
13794
13771
  this.getData = async (symbol, strategyName) => {
13795
- backtest$1.loggerService.info("LiveUtils.getData", {
13796
- symbol,
13797
- strategyName,
13798
- });
13799
- return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
13772
+ const instance = this._getInstance(symbol, strategyName);
13773
+ return await instance.getData(symbol, strategyName);
13800
13774
  };
13801
13775
  /**
13802
- * Generates markdown report with all events for a symbol-strategy pair.
13776
+ * Generates markdown report with all closed signals for a symbol-strategy pair.
13803
13777
  *
13804
13778
  * @param symbol - Trading pair symbol
13805
13779
  * @param strategyName - Strategy name to generate report for
@@ -13807,59 +13781,535 @@ class LiveUtils {
13807
13781
  *
13808
13782
  * @example
13809
13783
  * ```typescript
13810
- * const markdown = await Live.getReport("BTCUSDT", "my-strategy");
13784
+ * const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
13811
13785
  * console.log(markdown);
13812
13786
  * ```
13813
13787
  */
13814
13788
  this.getReport = async (symbol, strategyName) => {
13815
- backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
13816
- symbol,
13817
- strategyName,
13818
- });
13819
- return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
13789
+ const instance = this._getInstance(symbol, strategyName);
13790
+ return await instance.getReport(symbol, strategyName);
13820
13791
  };
13821
13792
  /**
13822
13793
  * Saves strategy report to disk.
13823
13794
  *
13824
13795
  * @param symbol - Trading pair symbol
13825
13796
  * @param strategyName - Strategy name to save report for
13826
- * @param path - Optional directory path to save report (default: "./dump/live")
13797
+ * @param path - Optional directory path to save report (default: "./dump/backtest")
13827
13798
  *
13828
13799
  * @example
13829
13800
  * ```typescript
13830
- * // Save to default path: ./dump/live/my-strategy.md
13831
- * await Live.dump("BTCUSDT", "my-strategy");
13801
+ * // Save to default path: ./dump/backtest/my-strategy.md
13802
+ * await Backtest.dump("BTCUSDT", "my-strategy");
13832
13803
  *
13833
13804
  * // Save to custom path: ./custom/path/my-strategy.md
13834
- * await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
13805
+ * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
13835
13806
  * ```
13836
13807
  */
13837
13808
  this.dump = async (symbol, strategyName, path) => {
13838
- backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
13839
- symbol,
13840
- strategyName,
13841
- path,
13842
- });
13843
- await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
13809
+ const instance = this._getInstance(symbol, strategyName);
13810
+ return await instance.dump(symbol, strategyName, path);
13811
+ };
13812
+ /**
13813
+ * Lists all active backtest instances with their current status.
13814
+ *
13815
+ * @returns Promise resolving to array of status objects for all instances
13816
+ *
13817
+ * @example
13818
+ * ```typescript
13819
+ * const statusList = await Backtest.list();
13820
+ * statusList.forEach(status => {
13821
+ * console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
13822
+ * });
13823
+ * ```
13824
+ */
13825
+ this.list = async () => {
13826
+ const instanceList = this._getInstance.values();
13827
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
13844
13828
  };
13845
13829
  }
13846
13830
  }
13847
13831
  /**
13848
- * Singleton instance of LiveUtils for convenient live trading operations.
13832
+ * Singleton instance of BacktestUtils for convenient backtest operations.
13849
13833
  *
13850
13834
  * @example
13851
13835
  * ```typescript
13852
- * import { Live } from "./classes/Live";
13836
+ * import { Backtest } from "./classes/Backtest";
13853
13837
  *
13854
- * for await (const result of Live.run("BTCUSDT", {
13838
+ * for await (const result of Backtest.run("BTCUSDT", {
13855
13839
  * strategyName: "my-strategy",
13856
13840
  * exchangeName: "my-exchange",
13841
+ * frameName: "1d-backtest"
13857
13842
  * })) {
13858
- * console.log("Result:", result.action);
13843
+ * if (result.action === "closed") {
13844
+ * console.log("PNL:", result.pnl.pnlPercentage);
13845
+ * }
13859
13846
  * }
13860
13847
  * ```
13861
13848
  */
13862
- const Live = new LiveUtils();
13849
+ const Backtest = new BacktestUtils();
13850
+
13851
+ const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
13852
+ const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
13853
+ const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
13854
+ const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
13855
+ const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
13856
+ const LIVE_METHOD_NAME_TASK = "LiveUtils.task";
13857
+ const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
13858
+ /**
13859
+ * Internal task function that runs live trading and handles completion.
13860
+ * Consumes live trading results and updates instance state flags.
13861
+ *
13862
+ * @param symbol - Trading pair symbol
13863
+ * @param context - Execution context with strategy and exchange names
13864
+ * @param self - LiveInstance reference for state management
13865
+ * @returns Promise that resolves when live trading completes
13866
+ *
13867
+ * @internal
13868
+ */
13869
+ const INSTANCE_TASK_FN$1 = async (symbol, context, self) => {
13870
+ {
13871
+ self._isStopped = false;
13872
+ self._isDone = false;
13873
+ }
13874
+ for await (const signal of self.run(symbol, context)) {
13875
+ if (signal?.action === "closed" && self._isStopped) {
13876
+ break;
13877
+ }
13878
+ }
13879
+ if (!self._isDone) {
13880
+ await doneLiveSubject.next({
13881
+ exchangeName: context.exchangeName,
13882
+ strategyName: context.strategyName,
13883
+ backtest: false,
13884
+ symbol,
13885
+ });
13886
+ }
13887
+ self._isDone = true;
13888
+ };
13889
+ /**
13890
+ * Instance class for live trading operations on a specific symbol-strategy pair.
13891
+ *
13892
+ * Provides isolated live trading execution and reporting for a single symbol-strategy combination.
13893
+ * Each instance maintains its own state and context.
13894
+ *
13895
+ * @example
13896
+ * ```typescript
13897
+ * const instance = new LiveInstance("BTCUSDT", "my-strategy");
13898
+ *
13899
+ * for await (const result of instance.run("BTCUSDT", {
13900
+ * strategyName: "my-strategy",
13901
+ * exchangeName: "my-exchange"
13902
+ * })) {
13903
+ * if (result.action === "closed") {
13904
+ * console.log("Signal closed, PNL:", result.pnl.pnlPercentage);
13905
+ * }
13906
+ * }
13907
+ * ```
13908
+ */
13909
+ class LiveInstance {
13910
+ /**
13911
+ * Creates a new LiveInstance for a specific symbol-strategy pair.
13912
+ *
13913
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13914
+ * @param strategyName - Strategy name for this live trading instance
13915
+ */
13916
+ constructor(symbol, strategyName) {
13917
+ this.symbol = symbol;
13918
+ this.strategyName = strategyName;
13919
+ /** Internal flag indicating if live trading was stopped manually */
13920
+ this._isStopped = false;
13921
+ /** Internal flag indicating if live trading task completed */
13922
+ this._isDone = false;
13923
+ /**
13924
+ * Internal singlerun task that executes the live trading.
13925
+ * Ensures only one live trading run per instance using singlerun wrapper.
13926
+ *
13927
+ * @param symbol - Trading pair symbol
13928
+ * @param context - Execution context with strategy and exchange names
13929
+ * @returns Promise that resolves when live trading completes
13930
+ *
13931
+ * @internal
13932
+ */
13933
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
13934
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_TASK, {
13935
+ symbol,
13936
+ context,
13937
+ });
13938
+ return await INSTANCE_TASK_FN$1(symbol, context, this);
13939
+ });
13940
+ /**
13941
+ * Gets the current status of this live trading instance.
13942
+ *
13943
+ * @returns Promise resolving to status object with symbol, strategyName, and task status
13944
+ *
13945
+ * @example
13946
+ * ```typescript
13947
+ * const instance = new LiveInstance("BTCUSDT", "my-strategy");
13948
+ * const status = await instance.getStatus();
13949
+ * console.log(status.status); // "idle", "running", or "done"
13950
+ * ```
13951
+ */
13952
+ this.getStatus = async () => {
13953
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_STATUS);
13954
+ return {
13955
+ symbol: this.symbol,
13956
+ strategyName: this.strategyName,
13957
+ status: this.task.getStatus(),
13958
+ };
13959
+ };
13960
+ /**
13961
+ * Runs live trading for a symbol with context propagation.
13962
+ *
13963
+ * Infinite async generator with crash recovery support.
13964
+ * Process can crash and restart - state will be recovered from disk.
13965
+ *
13966
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13967
+ * @param context - Execution context with strategy and exchange names
13968
+ * @returns Infinite async generator yielding opened and closed signals
13969
+ */
13970
+ this.run = (symbol, context) => {
13971
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_RUN, {
13972
+ symbol,
13973
+ context,
13974
+ });
13975
+ {
13976
+ backtest$1.liveMarkdownService.clear({ symbol, strategyName: context.strategyName });
13977
+ backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13978
+ }
13979
+ {
13980
+ backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13981
+ }
13982
+ {
13983
+ const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
13984
+ riskName && backtest$1.riskGlobalService.clear(riskName);
13985
+ }
13986
+ return backtest$1.liveCommandService.run(symbol, context);
13987
+ };
13988
+ /**
13989
+ * Runs live trading in background without yielding results.
13990
+ *
13991
+ * Consumes all live trading results internally without exposing them.
13992
+ * Infinite loop - will run until process is stopped or crashes.
13993
+ * Useful for running live trading for side effects only (callbacks, persistence).
13994
+ *
13995
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13996
+ * @param context - Execution context with strategy and exchange names
13997
+ * @returns Cancellation closure
13998
+ *
13999
+ * @example
14000
+ * ```typescript
14001
+ * const instance = new LiveInstance();
14002
+ * const cancel = instance.background("BTCUSDT", {
14003
+ * strategyName: "my-strategy",
14004
+ * exchangeName: "my-exchange"
14005
+ * });
14006
+ * ```
14007
+ */
14008
+ this.background = (symbol, context) => {
14009
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_BACKGROUND, {
14010
+ symbol,
14011
+ context,
14012
+ });
14013
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14014
+ return () => {
14015
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
14016
+ backtest$1.strategyGlobalService
14017
+ .getPendingSignal(symbol, context.strategyName)
14018
+ .then(async (pendingSignal) => {
14019
+ if (pendingSignal) {
14020
+ return;
14021
+ }
14022
+ if (!this._isDone) {
14023
+ await doneLiveSubject.next({
14024
+ exchangeName: context.exchangeName,
14025
+ strategyName: context.strategyName,
14026
+ backtest: false,
14027
+ symbol,
14028
+ });
14029
+ }
14030
+ this._isDone = true;
14031
+ });
14032
+ this._isStopped = true;
14033
+ };
14034
+ };
14035
+ /**
14036
+ * Stops the strategy from generating new signals.
14037
+ *
14038
+ * Sets internal flag to prevent strategy from opening new signals.
14039
+ * Current active signal (if any) will complete normally.
14040
+ * Live trading will stop at the next safe point (idle/closed state).
14041
+ *
14042
+ * @param symbol - Trading pair symbol
14043
+ * @param strategyName - Strategy name to stop
14044
+ * @returns Promise that resolves when stop flag is set
14045
+ *
14046
+ * @example
14047
+ * ```typescript
14048
+ * const instance = new LiveInstance();
14049
+ * await instance.stop("BTCUSDT", "my-strategy");
14050
+ * ```
14051
+ */
14052
+ this.stop = async (symbol, strategyName) => {
14053
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
14054
+ symbol,
14055
+ strategyName,
14056
+ });
14057
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14058
+ };
14059
+ /**
14060
+ * Gets statistical data from all live trading events for a symbol-strategy pair.
14061
+ *
14062
+ * @param symbol - Trading pair symbol
14063
+ * @param strategyName - Strategy name to get data for
14064
+ * @returns Promise resolving to statistical data object
14065
+ *
14066
+ * @example
14067
+ * ```typescript
14068
+ * const instance = new LiveInstance();
14069
+ * const stats = await instance.getData("BTCUSDT", "my-strategy");
14070
+ * console.log(stats.sharpeRatio, stats.winRate);
14071
+ * ```
14072
+ */
14073
+ this.getData = async (symbol, strategyName) => {
14074
+ backtest$1.loggerService.info("LiveUtils.getData", {
14075
+ symbol,
14076
+ strategyName,
14077
+ });
14078
+ return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
14079
+ };
14080
+ /**
14081
+ * Generates markdown report with all events for a symbol-strategy pair.
14082
+ *
14083
+ * @param symbol - Trading pair symbol
14084
+ * @param strategyName - Strategy name to generate report for
14085
+ * @returns Promise resolving to markdown formatted report string
14086
+ *
14087
+ * @example
14088
+ * ```typescript
14089
+ * const instance = new LiveInstance();
14090
+ * const markdown = await instance.getReport("BTCUSDT", "my-strategy");
14091
+ * console.log(markdown);
14092
+ * ```
14093
+ */
14094
+ this.getReport = async (symbol, strategyName) => {
14095
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
14096
+ symbol,
14097
+ strategyName,
14098
+ });
14099
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
14100
+ };
14101
+ /**
14102
+ * Saves strategy report to disk.
14103
+ *
14104
+ * @param symbol - Trading pair symbol
14105
+ * @param strategyName - Strategy name to save report for
14106
+ * @param path - Optional directory path to save report (default: "./dump/live")
14107
+ *
14108
+ * @example
14109
+ * ```typescript
14110
+ * const instance = new LiveInstance();
14111
+ * // Save to default path: ./dump/live/my-strategy.md
14112
+ * await instance.dump("BTCUSDT", "my-strategy");
14113
+ *
14114
+ * // Save to custom path: ./custom/path/my-strategy.md
14115
+ * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
14116
+ * ```
14117
+ */
14118
+ this.dump = async (symbol, strategyName, path) => {
14119
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
14120
+ symbol,
14121
+ strategyName,
14122
+ path,
14123
+ });
14124
+ await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
14125
+ };
14126
+ }
14127
+ }
14128
+ /**
14129
+ * Utility class for live trading operations.
14130
+ *
14131
+ * Provides simplified access to liveCommandService.run() with logging.
14132
+ * Exported as singleton instance for convenient usage.
14133
+ *
14134
+ * Features:
14135
+ * - Infinite async generator (never completes)
14136
+ * - Crash recovery via persisted state
14137
+ * - Real-time progression with Date.now()
14138
+ *
14139
+ * @example
14140
+ * ```typescript
14141
+ * import { Live } from "./classes/Live";
14142
+ *
14143
+ * // Infinite loop - use Ctrl+C to stop
14144
+ * for await (const result of Live.run("BTCUSDT", {
14145
+ * strategyName: "my-strategy",
14146
+ * exchangeName: "my-exchange",
14147
+ * frameName: ""
14148
+ * })) {
14149
+ * if (result.action === "opened") {
14150
+ * console.log("Signal opened:", result.signal);
14151
+ * } else if (result.action === "closed") {
14152
+ * console.log("PNL:", result.pnl.pnlPercentage);
14153
+ * }
14154
+ * }
14155
+ * ```
14156
+ */
14157
+ class LiveUtils {
14158
+ constructor() {
14159
+ /**
14160
+ * Memoized function to get or create LiveInstance for a symbol-strategy pair.
14161
+ * Each symbol-strategy combination gets its own isolated instance.
14162
+ */
14163
+ this._getInstance = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new LiveInstance(symbol, strategyName));
14164
+ /**
14165
+ * Runs live trading for a symbol with context propagation.
14166
+ *
14167
+ * Infinite async generator with crash recovery support.
14168
+ * Process can crash and restart - state will be recovered from disk.
14169
+ *
14170
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14171
+ * @param context - Execution context with strategy and exchange names
14172
+ * @returns Infinite async generator yielding opened and closed signals
14173
+ */
14174
+ this.run = (symbol, context) => {
14175
+ const instance = this._getInstance(symbol, context.strategyName);
14176
+ return instance.run(symbol, context);
14177
+ };
14178
+ /**
14179
+ * Runs live trading in background without yielding results.
14180
+ *
14181
+ * Consumes all live trading results internally without exposing them.
14182
+ * Infinite loop - will run until process is stopped or crashes.
14183
+ * Useful for running live trading for side effects only (callbacks, persistence).
14184
+ *
14185
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14186
+ * @param context - Execution context with strategy and exchange names
14187
+ * @returns Cancellation closure
14188
+ *
14189
+ * @example
14190
+ * ```typescript
14191
+ * // Run live trading silently in background, only callbacks will fire
14192
+ * // This will run forever until Ctrl+C
14193
+ * await Live.background("BTCUSDT", {
14194
+ * strategyName: "my-strategy",
14195
+ * exchangeName: "my-exchange"
14196
+ * });
14197
+ * ```
14198
+ */
14199
+ this.background = (symbol, context) => {
14200
+ const instance = this._getInstance(symbol, context.strategyName);
14201
+ return instance.background(symbol, context);
14202
+ };
14203
+ /**
14204
+ * Stops the strategy from generating new signals.
14205
+ *
14206
+ * Sets internal flag to prevent strategy from opening new signals.
14207
+ * Current active signal (if any) will complete normally.
14208
+ * Live trading will stop at the next safe point (idle/closed state).
14209
+ *
14210
+ * @param symbol - Trading pair symbol
14211
+ * @param strategyName - Strategy name to stop
14212
+ * @returns Promise that resolves when stop flag is set
14213
+ *
14214
+ * @example
14215
+ * ```typescript
14216
+ * // Stop live trading gracefully
14217
+ * await Live.stop("BTCUSDT", "my-strategy");
14218
+ * ```
14219
+ */
14220
+ this.stop = async (symbol, strategyName) => {
14221
+ const instance = this._getInstance(symbol, strategyName);
14222
+ return await instance.stop(symbol, strategyName);
14223
+ };
14224
+ /**
14225
+ * Gets statistical data from all live trading events for a symbol-strategy pair.
14226
+ *
14227
+ * @param symbol - Trading pair symbol
14228
+ * @param strategyName - Strategy name to get data for
14229
+ * @returns Promise resolving to statistical data object
14230
+ *
14231
+ * @example
14232
+ * ```typescript
14233
+ * const stats = await Live.getData("BTCUSDT", "my-strategy");
14234
+ * console.log(stats.sharpeRatio, stats.winRate);
14235
+ * ```
14236
+ */
14237
+ this.getData = async (symbol, strategyName) => {
14238
+ const instance = this._getInstance(symbol, strategyName);
14239
+ return await instance.getData(symbol, strategyName);
14240
+ };
14241
+ /**
14242
+ * Generates markdown report with all events for a symbol-strategy pair.
14243
+ *
14244
+ * @param symbol - Trading pair symbol
14245
+ * @param strategyName - Strategy name to generate report for
14246
+ * @returns Promise resolving to markdown formatted report string
14247
+ *
14248
+ * @example
14249
+ * ```typescript
14250
+ * const markdown = await Live.getReport("BTCUSDT", "my-strategy");
14251
+ * console.log(markdown);
14252
+ * ```
14253
+ */
14254
+ this.getReport = async (symbol, strategyName) => {
14255
+ const instance = this._getInstance(symbol, strategyName);
14256
+ return await instance.getReport(symbol, strategyName);
14257
+ };
14258
+ /**
14259
+ * Saves strategy report to disk.
14260
+ *
14261
+ * @param symbol - Trading pair symbol
14262
+ * @param strategyName - Strategy name to save report for
14263
+ * @param path - Optional directory path to save report (default: "./dump/live")
14264
+ *
14265
+ * @example
14266
+ * ```typescript
14267
+ * // Save to default path: ./dump/live/my-strategy.md
14268
+ * await Live.dump("BTCUSDT", "my-strategy");
14269
+ *
14270
+ * // Save to custom path: ./custom/path/my-strategy.md
14271
+ * await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
14272
+ * ```
14273
+ */
14274
+ this.dump = async (symbol, strategyName, path) => {
14275
+ const instance = this._getInstance(symbol, strategyName);
14276
+ return await instance.dump(symbol, strategyName, path);
14277
+ };
14278
+ /**
14279
+ * Lists all active live trading instances with their current status.
14280
+ *
14281
+ * @returns Promise resolving to array of status objects for all instances
14282
+ *
14283
+ * @example
14284
+ * ```typescript
14285
+ * const statusList = await Live.list();
14286
+ * statusList.forEach(status => {
14287
+ * console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
14288
+ * });
14289
+ * ```
14290
+ */
14291
+ this.list = async () => {
14292
+ const instanceList = this._getInstance.values();
14293
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
14294
+ };
14295
+ }
14296
+ }
14297
+ /**
14298
+ * Singleton instance of LiveUtils for convenient live trading operations.
14299
+ *
14300
+ * @example
14301
+ * ```typescript
14302
+ * import { Live } from "./classes/Live";
14303
+ *
14304
+ * for await (const result of Live.run("BTCUSDT", {
14305
+ * strategyName: "my-strategy",
14306
+ * exchangeName: "my-exchange",
14307
+ * })) {
14308
+ * console.log("Result:", result.action);
14309
+ * }
14310
+ * ```
14311
+ */
14312
+ const Live = new LiveUtils();
13863
14313
 
13864
14314
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
13865
14315
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
@@ -14089,27 +14539,108 @@ const WALKER_METHOD_NAME_STOP = "WalkerUtils.stop";
14089
14539
  const WALKER_METHOD_NAME_GET_DATA = "WalkerUtils.getData";
14090
14540
  const WALKER_METHOD_NAME_GET_REPORT = "WalkerUtils.getReport";
14091
14541
  const WALKER_METHOD_NAME_DUMP = "WalkerUtils.dump";
14542
+ const WALKER_METHOD_NAME_TASK = "WalkerUtils.task";
14543
+ const WALKER_METHOD_NAME_GET_STATUS = "WalkerUtils.getStatus";
14092
14544
  /**
14093
- * Utility class for walker operations.
14545
+ * Internal task function that runs walker and handles completion.
14546
+ * Consumes walker results and updates instance state flags.
14094
14547
  *
14095
- * Provides simplified access to walkerCommandService.run() with logging.
14096
- * Automatically pulls exchangeName and frameName from walker schema.
14097
- * Exported as singleton instance for convenient usage.
14548
+ * @param symbol - Trading pair symbol
14549
+ * @param context - Execution context with walker name
14550
+ * @param self - WalkerInstance reference for state management
14551
+ * @returns Promise that resolves when walker completes
14552
+ *
14553
+ * @internal
14554
+ */
14555
+ const INSTANCE_TASK_FN = async (symbol, context, self) => {
14556
+ {
14557
+ self._isStopped = false;
14558
+ self._isDone = false;
14559
+ }
14560
+ for await (const _ of self.run(symbol, context)) {
14561
+ if (self._isStopped) {
14562
+ break;
14563
+ }
14564
+ }
14565
+ if (!self._isDone) {
14566
+ const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
14567
+ await doneWalkerSubject.next({
14568
+ exchangeName: walkerSchema.exchangeName,
14569
+ strategyName: context.walkerName,
14570
+ backtest: true,
14571
+ symbol,
14572
+ });
14573
+ }
14574
+ self._isDone = true;
14575
+ };
14576
+ /**
14577
+ * Instance class for walker operations on a specific symbol-walker pair.
14578
+ *
14579
+ * Provides isolated walker execution and reporting for a single symbol-walker combination.
14580
+ * Each instance maintains its own state and context.
14098
14581
  *
14099
14582
  * @example
14100
14583
  * ```typescript
14101
- * import { Walker } from "./classes/Walker";
14584
+ * const instance = new WalkerInstance("BTCUSDT", "my-walker");
14102
14585
  *
14103
- * for await (const result of Walker.run("BTCUSDT", {
14586
+ * for await (const result of instance.run("BTCUSDT", {
14104
14587
  * walkerName: "my-walker"
14105
14588
  * })) {
14106
14589
  * console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
14107
- * console.log("Best strategy:", result.bestStrategy, result.bestMetric);
14108
14590
  * }
14109
14591
  * ```
14110
14592
  */
14111
- class WalkerUtils {
14112
- constructor() {
14593
+ class WalkerInstance {
14594
+ /**
14595
+ * Creates a new WalkerInstance for a specific symbol-walker pair.
14596
+ *
14597
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14598
+ * @param walkerName - Walker name for this walker instance
14599
+ */
14600
+ constructor(symbol, walkerName) {
14601
+ this.symbol = symbol;
14602
+ this.walkerName = walkerName;
14603
+ /** Internal flag indicating if walker was stopped manually */
14604
+ this._isStopped = false;
14605
+ /** Internal flag indicating if walker task completed */
14606
+ this._isDone = false;
14607
+ /**
14608
+ * Internal singlerun task that executes the walker.
14609
+ * Ensures only one walker run per instance using singlerun wrapper.
14610
+ *
14611
+ * @param symbol - Trading pair symbol
14612
+ * @param context - Execution context with walker name
14613
+ * @returns Promise that resolves when walker completes
14614
+ *
14615
+ * @internal
14616
+ */
14617
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
14618
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_TASK, {
14619
+ symbol,
14620
+ context,
14621
+ });
14622
+ return await INSTANCE_TASK_FN(symbol, context, this);
14623
+ });
14624
+ /**
14625
+ * Gets the current status of this walker instance.
14626
+ *
14627
+ * @returns Promise resolving to status object with symbol, walkerName, and task status
14628
+ *
14629
+ * @example
14630
+ * ```typescript
14631
+ * const instance = new WalkerInstance("BTCUSDT", "my-walker");
14632
+ * const status = await instance.getStatus();
14633
+ * console.log(status.status); // "idle", "running", or "done"
14634
+ * ```
14635
+ */
14636
+ this.getStatus = async () => {
14637
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_STATUS);
14638
+ return {
14639
+ symbol: this.symbol,
14640
+ walkerName: this.walkerName,
14641
+ status: this.task.getStatus(),
14642
+ };
14643
+ };
14113
14644
  /**
14114
14645
  * Runs walker comparison for a symbol with context propagation.
14115
14646
  *
@@ -14162,11 +14693,10 @@ class WalkerUtils {
14162
14693
  *
14163
14694
  * @example
14164
14695
  * ```typescript
14165
- * // Run walker silently, only callbacks will fire
14166
- * await Walker.background("BTCUSDT", {
14696
+ * const instance = new WalkerInstance();
14697
+ * const cancel = instance.background("BTCUSDT", {
14167
14698
  * walkerName: "my-walker"
14168
14699
  * });
14169
- * console.log("Walker comparison completed");
14170
14700
  * ```
14171
14701
  */
14172
14702
  this.background = (symbol, context) => {
@@ -14175,31 +14705,13 @@ class WalkerUtils {
14175
14705
  context,
14176
14706
  });
14177
14707
  const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
14178
- let isStopped = false;
14179
- let isDone = false;
14180
- const task = async () => {
14181
- for await (const _ of this.run(symbol, context)) {
14182
- if (isStopped) {
14183
- break;
14184
- }
14185
- }
14186
- if (!isDone) {
14187
- await doneWalkerSubject.next({
14188
- exchangeName: walkerSchema.exchangeName,
14189
- strategyName: context.walkerName,
14190
- backtest: true,
14191
- symbol,
14192
- });
14193
- }
14194
- isDone = true;
14195
- };
14196
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14708
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14197
14709
  return () => {
14198
14710
  for (const strategyName of walkerSchema.strategies) {
14199
14711
  backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
14200
14712
  walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
14201
14713
  }
14202
- if (!isDone) {
14714
+ if (!this._isDone) {
14203
14715
  doneWalkerSubject.next({
14204
14716
  exchangeName: walkerSchema.exchangeName,
14205
14717
  strategyName: context.walkerName,
@@ -14207,8 +14719,8 @@ class WalkerUtils {
14207
14719
  symbol,
14208
14720
  });
14209
14721
  }
14210
- isDone = true;
14211
- isStopped = true;
14722
+ this._isDone = true;
14723
+ this._isStopped = true;
14212
14724
  };
14213
14725
  };
14214
14726
  /**
@@ -14230,8 +14742,8 @@ class WalkerUtils {
14230
14742
  *
14231
14743
  * @example
14232
14744
  * ```typescript
14233
- * // Stop walker and all its strategies
14234
- * await Walker.stop("BTCUSDT", "my-walker");
14745
+ * const instance = new WalkerInstance();
14746
+ * await instance.stop("BTCUSDT", "my-walker");
14235
14747
  * ```
14236
14748
  */
14237
14749
  this.stop = async (symbol, walkerName) => {
@@ -14254,7 +14766,8 @@ class WalkerUtils {
14254
14766
  *
14255
14767
  * @example
14256
14768
  * ```typescript
14257
- * const results = await Walker.getData("BTCUSDT", "my-walker");
14769
+ * const instance = new WalkerInstance();
14770
+ * const results = await instance.getData("BTCUSDT", "my-walker");
14258
14771
  * console.log(results.bestStrategy, results.bestMetric);
14259
14772
  * ```
14260
14773
  */
@@ -14278,7 +14791,8 @@ class WalkerUtils {
14278
14791
  *
14279
14792
  * @example
14280
14793
  * ```typescript
14281
- * const markdown = await Walker.getReport("BTCUSDT", "my-walker");
14794
+ * const instance = new WalkerInstance();
14795
+ * const markdown = await instance.getReport("BTCUSDT", "my-walker");
14282
14796
  * console.log(markdown);
14283
14797
  * ```
14284
14798
  */
@@ -14302,11 +14816,12 @@ class WalkerUtils {
14302
14816
  *
14303
14817
  * @example
14304
14818
  * ```typescript
14819
+ * const instance = new WalkerInstance();
14305
14820
  * // Save to default path: ./dump/walker/my-walker.md
14306
- * await Walker.dump("BTCUSDT", "my-walker");
14821
+ * await instance.dump("BTCUSDT", "my-walker");
14307
14822
  *
14308
14823
  * // Save to custom path: ./custom/path/my-walker.md
14309
- * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
14824
+ * await instance.dump("BTCUSDT", "my-walker", "./custom/path");
14310
14825
  * ```
14311
14826
  */
14312
14827
  this.dump = async (symbol, walkerName, path) => {
@@ -14323,6 +14838,166 @@ class WalkerUtils {
14323
14838
  };
14324
14839
  }
14325
14840
  }
14841
+ /**
14842
+ * Utility class for walker operations.
14843
+ *
14844
+ * Provides simplified access to walkerCommandService.run() with logging.
14845
+ * Automatically pulls exchangeName and frameName from walker schema.
14846
+ * Exported as singleton instance for convenient usage.
14847
+ *
14848
+ * @example
14849
+ * ```typescript
14850
+ * import { Walker } from "./classes/Walker";
14851
+ *
14852
+ * for await (const result of Walker.run("BTCUSDT", {
14853
+ * walkerName: "my-walker"
14854
+ * })) {
14855
+ * console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
14856
+ * console.log("Best strategy:", result.bestStrategy, result.bestMetric);
14857
+ * }
14858
+ * ```
14859
+ */
14860
+ class WalkerUtils {
14861
+ constructor() {
14862
+ /**
14863
+ * Memoized function to get or create WalkerInstance for a symbol-walker pair.
14864
+ * Each symbol-walker combination gets its own isolated instance.
14865
+ */
14866
+ this._getInstance = functoolsKit.memoize(([symbol, walkerName]) => `${symbol}:${walkerName}`, (symbol, walkerName) => new WalkerInstance(symbol, walkerName));
14867
+ /**
14868
+ * Runs walker comparison for a symbol with context propagation.
14869
+ *
14870
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14871
+ * @param context - Execution context with walker name
14872
+ * @returns Async generator yielding progress updates after each strategy
14873
+ */
14874
+ this.run = (symbol, context) => {
14875
+ const instance = this._getInstance(symbol, context.walkerName);
14876
+ return instance.run(symbol, context);
14877
+ };
14878
+ /**
14879
+ * Runs walker comparison in background without yielding results.
14880
+ *
14881
+ * Consumes all walker progress updates internally without exposing them.
14882
+ * Useful for running walker comparison for side effects only (callbacks, logging).
14883
+ *
14884
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14885
+ * @param context - Execution context with walker name
14886
+ * @returns Cancellation closure
14887
+ *
14888
+ * @example
14889
+ * ```typescript
14890
+ * // Run walker silently, only callbacks will fire
14891
+ * await Walker.background("BTCUSDT", {
14892
+ * walkerName: "my-walker"
14893
+ * });
14894
+ * console.log("Walker comparison completed");
14895
+ * ```
14896
+ */
14897
+ this.background = (symbol, context) => {
14898
+ const instance = this._getInstance(symbol, context.walkerName);
14899
+ return instance.background(symbol, context);
14900
+ };
14901
+ /**
14902
+ * Stops all strategies in the walker from generating new signals.
14903
+ *
14904
+ * Iterates through all strategies defined in walker schema and:
14905
+ * 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
14906
+ * 2. Sets internal stop flag for each strategy (prevents new signals)
14907
+ *
14908
+ * Current active signals (if any) will complete normally.
14909
+ * Walker will stop at the next safe point.
14910
+ *
14911
+ * Supports multiple walkers running on the same symbol simultaneously.
14912
+ * Stop signal is filtered by walkerName to prevent interference.
14913
+ *
14914
+ * @param symbol - Trading pair symbol
14915
+ * @param walkerName - Walker name to stop
14916
+ * @returns Promise that resolves when all stop flags are set
14917
+ *
14918
+ * @example
14919
+ * ```typescript
14920
+ * // Stop walker and all its strategies
14921
+ * await Walker.stop("BTCUSDT", "my-walker");
14922
+ * ```
14923
+ */
14924
+ this.stop = async (symbol, walkerName) => {
14925
+ const instance = this._getInstance(symbol, walkerName);
14926
+ return await instance.stop(symbol, walkerName);
14927
+ };
14928
+ /**
14929
+ * Gets walker results data from all strategy comparisons.
14930
+ *
14931
+ * @param symbol - Trading symbol
14932
+ * @param walkerName - Walker name to get data for
14933
+ * @returns Promise resolving to walker results data object
14934
+ *
14935
+ * @example
14936
+ * ```typescript
14937
+ * const results = await Walker.getData("BTCUSDT", "my-walker");
14938
+ * console.log(results.bestStrategy, results.bestMetric);
14939
+ * ```
14940
+ */
14941
+ this.getData = async (symbol, walkerName) => {
14942
+ const instance = this._getInstance(symbol, walkerName);
14943
+ return await instance.getData(symbol, walkerName);
14944
+ };
14945
+ /**
14946
+ * Generates markdown report with all strategy comparisons for a walker.
14947
+ *
14948
+ * @param symbol - Trading symbol
14949
+ * @param walkerName - Walker name to generate report for
14950
+ * @returns Promise resolving to markdown formatted report string
14951
+ *
14952
+ * @example
14953
+ * ```typescript
14954
+ * const markdown = await Walker.getReport("BTCUSDT", "my-walker");
14955
+ * console.log(markdown);
14956
+ * ```
14957
+ */
14958
+ this.getReport = async (symbol, walkerName) => {
14959
+ const instance = this._getInstance(symbol, walkerName);
14960
+ return await instance.getReport(symbol, walkerName);
14961
+ };
14962
+ /**
14963
+ * Saves walker report to disk.
14964
+ *
14965
+ * @param symbol - Trading symbol
14966
+ * @param walkerName - Walker name to save report for
14967
+ * @param path - Optional directory path to save report (default: "./dump/walker")
14968
+ *
14969
+ * @example
14970
+ * ```typescript
14971
+ * // Save to default path: ./dump/walker/my-walker.md
14972
+ * await Walker.dump("BTCUSDT", "my-walker");
14973
+ *
14974
+ * // Save to custom path: ./custom/path/my-walker.md
14975
+ * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
14976
+ * ```
14977
+ */
14978
+ this.dump = async (symbol, walkerName, path) => {
14979
+ const instance = this._getInstance(symbol, walkerName);
14980
+ return await instance.dump(symbol, walkerName, path);
14981
+ };
14982
+ /**
14983
+ * Lists all active walker instances with their current status.
14984
+ *
14985
+ * @returns Promise resolving to array of status objects for all instances
14986
+ *
14987
+ * @example
14988
+ * ```typescript
14989
+ * const statusList = await Walker.list();
14990
+ * statusList.forEach(status => {
14991
+ * console.log(`${status.symbol} - ${status.walkerName}: ${status.status}`);
14992
+ * });
14993
+ * ```
14994
+ */
14995
+ this.list = async () => {
14996
+ const instanceList = this._getInstance.values();
14997
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
14998
+ };
14999
+ }
15000
+ }
14326
15001
  /**
14327
15002
  * Singleton instance of WalkerUtils for convenient walker operations.
14328
15003
  *