backtest-kit 1.5.2 → 1.5.4

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
@@ -1675,6 +1675,8 @@ const walkerCompleteSubject = new functoolsKit.Subject();
1675
1675
  /**
1676
1676
  * Walker stop emitter for walker cancellation events.
1677
1677
  * Emits when a walker comparison is stopped/cancelled.
1678
+ *
1679
+ * Includes walkerName to support multiple walkers running on the same symbol.
1678
1680
  */
1679
1681
  const walkerStopSubject = new functoolsKit.Subject();
1680
1682
  /**
@@ -2909,6 +2911,23 @@ class ClientStrategy {
2909
2911
  });
2910
2912
  return this._pendingSignal;
2911
2913
  }
2914
+ /**
2915
+ * Returns the stopped state of the strategy.
2916
+ *
2917
+ * Indicates whether the strategy has been explicitly stopped and should
2918
+ * not continue processing new ticks or signals.
2919
+ *
2920
+ * @param symbol - Trading pair symbol
2921
+ * @param strategyName - Name of the strategy
2922
+ * @returns Promise resolving to true if strategy is stopped, false otherwise
2923
+ */
2924
+ async getStopped(symbol, strategyName) {
2925
+ this.params.logger.debug("ClientStrategy getStopped", {
2926
+ symbol,
2927
+ strategyName,
2928
+ });
2929
+ return this._isStopped;
2930
+ }
2912
2931
  /**
2913
2932
  * Performs a single tick of strategy execution.
2914
2933
  *
@@ -3151,18 +3170,24 @@ class ClientStrategy {
3151
3170
  * // Existing signal will continue until natural close
3152
3171
  * ```
3153
3172
  */
3154
- async stop(symbol, strategyName) {
3173
+ async stop(symbol, strategyName, backtest) {
3155
3174
  this.params.logger.debug("ClientStrategy stop", {
3156
3175
  symbol,
3157
3176
  strategyName,
3158
3177
  hasPendingSignal: this._pendingSignal !== null,
3159
3178
  hasScheduledSignal: this._scheduledSignal !== null,
3179
+ backtest,
3160
3180
  });
3161
3181
  this._isStopped = true;
3162
3182
  // Clear scheduled signal if exists
3163
- if (this._scheduledSignal) {
3164
- await this.setScheduledSignal(null);
3183
+ if (!this._scheduledSignal) {
3184
+ return;
3185
+ }
3186
+ this._scheduledSignal = null;
3187
+ if (backtest) {
3188
+ return;
3165
3189
  }
3190
+ await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, this.params.execution.context.symbol, this.params.strategyName);
3166
3191
  }
3167
3192
  }
3168
3193
 
@@ -3245,6 +3270,24 @@ class StrategyConnectionService {
3245
3270
  const strategy = this.getStrategy(symbol, strategyName);
3246
3271
  return await strategy.getPendingSignal(symbol, strategyName);
3247
3272
  };
3273
+ /**
3274
+ * Retrieves the stopped state of the strategy.
3275
+ *
3276
+ * Delegates to the underlying strategy instance to check if it has been
3277
+ * marked as stopped and should cease operation.
3278
+ *
3279
+ * @param symbol - Trading pair symbol
3280
+ * @param strategyName - Name of the strategy
3281
+ * @returns Promise resolving to true if strategy is stopped, false otherwise
3282
+ */
3283
+ this.getStopped = async (symbol, strategyName) => {
3284
+ this.loggerService.log("strategyConnectionService getStopped", {
3285
+ symbol,
3286
+ strategyName,
3287
+ });
3288
+ const strategy = this.getStrategy(symbol, strategyName);
3289
+ return await strategy.getStopped(symbol, strategyName);
3290
+ };
3248
3291
  /**
3249
3292
  * Executes live trading tick for current strategy.
3250
3293
  *
@@ -3312,12 +3355,12 @@ class StrategyConnectionService {
3312
3355
  * @param strategyName - Name of strategy to stop
3313
3356
  * @returns Promise that resolves when stop flag is set
3314
3357
  */
3315
- this.stop = async (ctx) => {
3358
+ this.stop = async (ctx, backtest) => {
3316
3359
  this.loggerService.log("strategyConnectionService stop", {
3317
3360
  ctx
3318
3361
  });
3319
3362
  const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
3320
- await strategy.stop(ctx.symbol, ctx.strategyName);
3363
+ await strategy.stop(ctx.symbol, ctx.strategyName, backtest);
3321
3364
  };
3322
3365
  /**
3323
3366
  * Clears the memoized ClientStrategy instance from cache.
@@ -4182,6 +4225,24 @@ class StrategyGlobalService {
4182
4225
  await this.validate(symbol, strategyName);
4183
4226
  return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
4184
4227
  };
4228
+ /**
4229
+ * Checks if the strategy has been stopped.
4230
+ *
4231
+ * Validates strategy existence and delegates to connection service
4232
+ * to retrieve the stopped state from the strategy instance.
4233
+ *
4234
+ * @param symbol - Trading pair symbol
4235
+ * @param strategyName - Name of the strategy
4236
+ * @returns Promise resolving to true if strategy is stopped, false otherwise
4237
+ */
4238
+ this.getStopped = async (symbol, strategyName) => {
4239
+ this.loggerService.log("strategyGlobalService getStopped", {
4240
+ symbol,
4241
+ strategyName,
4242
+ });
4243
+ await this.validate(symbol, strategyName);
4244
+ return await this.strategyConnectionService.getStopped(symbol, strategyName);
4245
+ };
4185
4246
  /**
4186
4247
  * Checks signal status at a specific timestamp.
4187
4248
  *
@@ -4248,12 +4309,13 @@ class StrategyGlobalService {
4248
4309
  * @param strategyName - Name of strategy to stop
4249
4310
  * @returns Promise that resolves when stop flag is set
4250
4311
  */
4251
- this.stop = async (ctx) => {
4312
+ this.stop = async (ctx, backtest) => {
4252
4313
  this.loggerService.log("strategyGlobalService stop", {
4253
4314
  ctx,
4315
+ backtest,
4254
4316
  });
4255
4317
  await this.validate(ctx.symbol, ctx.strategyName);
4256
- return await this.strategyConnectionService.stop(ctx);
4318
+ return await this.strategyConnectionService.stop(ctx, backtest);
4257
4319
  };
4258
4320
  /**
4259
4321
  * Clears the memoized ClientStrategy instance from cache.
@@ -4945,6 +5007,16 @@ class BacktestLogicPrivateService {
4945
5007
  progress: totalFrames > 0 ? i / totalFrames : 0,
4946
5008
  });
4947
5009
  }
5010
+ // Check if strategy should stop before processing next frame
5011
+ if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5012
+ this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5013
+ symbol,
5014
+ when: when.toISOString(),
5015
+ processedFrames: i,
5016
+ totalFrames,
5017
+ });
5018
+ break;
5019
+ }
4948
5020
  let result;
4949
5021
  try {
4950
5022
  result = await this.strategyGlobalService.tick(symbol, when, true);
@@ -4960,6 +5032,16 @@ class BacktestLogicPrivateService {
4960
5032
  i++;
4961
5033
  continue;
4962
5034
  }
5035
+ // Check if strategy should stop when idle (no active signal)
5036
+ if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5037
+ this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5038
+ symbol,
5039
+ when: when.toISOString(),
5040
+ processedFrames: i,
5041
+ totalFrames,
5042
+ });
5043
+ break;
5044
+ }
4963
5045
  // Если scheduled signal создан - обрабатываем через backtest()
4964
5046
  if (result.action === "scheduled") {
4965
5047
  const signalStartTime = performance.now();
@@ -5052,6 +5134,16 @@ class BacktestLogicPrivateService {
5052
5134
  i++;
5053
5135
  }
5054
5136
  yield backtestResult;
5137
+ // Check if strategy should stop after signal is closed
5138
+ if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5139
+ this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5140
+ symbol,
5141
+ signalId: backtestResult.signal.id,
5142
+ processedFrames: i,
5143
+ totalFrames,
5144
+ });
5145
+ break;
5146
+ }
5055
5147
  }
5056
5148
  // Если обычный сигнал открыт, вызываем backtest
5057
5149
  if (result.action === "opened") {
@@ -5135,6 +5227,16 @@ class BacktestLogicPrivateService {
5135
5227
  i++;
5136
5228
  }
5137
5229
  yield backtestResult;
5230
+ // Check if strategy should stop after signal is closed
5231
+ if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5232
+ this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5233
+ symbol,
5234
+ signalId: backtestResult.signal.id,
5235
+ processedFrames: i,
5236
+ totalFrames,
5237
+ });
5238
+ break;
5239
+ }
5138
5240
  }
5139
5241
  // Track timeframe processing duration
5140
5242
  const timeframeEndTime = performance.now();
@@ -5242,7 +5344,8 @@ class LiveLogicPrivateService {
5242
5344
  this.loggerService.warn("liveLogicPrivateService tick failed, retrying after sleep", {
5243
5345
  symbol,
5244
5346
  when: when.toISOString(),
5245
- error: functoolsKit.errorData(error), message: functoolsKit.getErrorMessage(error),
5347
+ error: functoolsKit.errorData(error),
5348
+ message: functoolsKit.getErrorMessage(error),
5246
5349
  });
5247
5350
  await errorEmitter.next(error);
5248
5351
  await functoolsKit.sleep(TICK_TTL);
@@ -5266,11 +5369,19 @@ class LiveLogicPrivateService {
5266
5369
  backtest: false,
5267
5370
  });
5268
5371
  previousEventTimestamp = currentTimestamp;
5269
- if (result.action === "active") {
5372
+ // Check if strategy should stop when idle (no active signal)
5373
+ if (result.action === "idle") {
5374
+ if (await functoolsKit.and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5375
+ this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5376
+ symbol,
5377
+ when: when.toISOString(),
5378
+ });
5379
+ break;
5380
+ }
5270
5381
  await functoolsKit.sleep(TICK_TTL);
5271
5382
  continue;
5272
5383
  }
5273
- if (result.action === "idle") {
5384
+ if (result.action === "active") {
5274
5385
  await functoolsKit.sleep(TICK_TTL);
5275
5386
  continue;
5276
5387
  }
@@ -5280,12 +5391,21 @@ class LiveLogicPrivateService {
5280
5391
  }
5281
5392
  // Yield opened, closed results
5282
5393
  yield result;
5394
+ // Check if strategy should stop after signal is closed
5395
+ if (result.action === "closed") {
5396
+ if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5397
+ this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5398
+ symbol,
5399
+ signalId: result.signal.id,
5400
+ });
5401
+ break;
5402
+ }
5403
+ }
5283
5404
  await functoolsKit.sleep(TICK_TTL);
5284
5405
  }
5285
5406
  }
5286
5407
  }
5287
5408
 
5288
- const CANCEL_SYMBOL = Symbol("CANCEL_SYMBOL");
5289
5409
  /**
5290
5410
  * Private service for walker orchestration (strategy comparison).
5291
5411
  *
@@ -5343,112 +5463,121 @@ class WalkerLogicPrivateService {
5343
5463
  let strategiesTested = 0;
5344
5464
  let bestMetric = null;
5345
5465
  let bestStrategy = null;
5346
- let pendingStrategy;
5347
- const listenStop = walkerStopSubject
5348
- .filter((data) => {
5349
- let isOk = true;
5350
- isOk = isOk && data.symbol === symbol;
5351
- isOk = isOk && data.strategyName === pendingStrategy;
5352
- return isOk;
5353
- })
5354
- .map(() => CANCEL_SYMBOL)
5355
- .toPromise();
5356
- // Run backtest for each strategy
5357
- for (const strategyName of strategies) {
5358
- // Call onStrategyStart callback if provided
5359
- if (walkerSchema.callbacks?.onStrategyStart) {
5360
- walkerSchema.callbacks.onStrategyStart(strategyName, symbol);
5361
- }
5362
- this.loggerService.info("walkerLogicPrivateService testing strategy", {
5363
- strategyName,
5466
+ // Track stopped strategies in Set for efficient lookup
5467
+ const stoppedStrategies = new Set();
5468
+ // Subscribe to stop signals and collect them in Set
5469
+ // Filter by both symbol AND walkerName to support multiple walkers on same symbol
5470
+ // connect() returns destructor function
5471
+ const unsubscribe = walkerStopSubject
5472
+ .filter((data) => data.symbol === symbol && data.walkerName === context.walkerName)
5473
+ .connect((data) => {
5474
+ stoppedStrategies.add(data.strategyName);
5475
+ this.loggerService.info("walkerLogicPrivateService received stop signal for strategy", {
5364
5476
  symbol,
5477
+ walkerName: context.walkerName,
5478
+ strategyName: data.strategyName,
5479
+ stoppedCount: stoppedStrategies.size,
5365
5480
  });
5366
- const iterator = this.backtestLogicPublicService.run(symbol, {
5367
- strategyName,
5368
- exchangeName: context.exchangeName,
5369
- frameName: context.frameName,
5370
- });
5371
- pendingStrategy = strategyName;
5372
- let result;
5373
- try {
5374
- result = await Promise.race([
5375
- await functoolsKit.resolveDocuments(iterator),
5376
- listenStop,
5377
- ]);
5378
- }
5379
- catch (error) {
5380
- console.warn(`walkerLogicPrivateService backtest failed symbol=${symbol} strategyName=${strategyName} exchangeName=${context.exchangeName}`);
5381
- this.loggerService.warn("walkerLogicPrivateService backtest failed for strategy, skipping", {
5481
+ });
5482
+ try {
5483
+ // Run backtest for each strategy
5484
+ for (const strategyName of strategies) {
5485
+ // Check if this strategy should be stopped before starting
5486
+ if (stoppedStrategies.has(strategyName)) {
5487
+ this.loggerService.info("walkerLogicPrivateService skipping stopped strategy", {
5488
+ symbol,
5489
+ strategyName,
5490
+ });
5491
+ break;
5492
+ }
5493
+ // Call onStrategyStart callback if provided
5494
+ if (walkerSchema.callbacks?.onStrategyStart) {
5495
+ walkerSchema.callbacks.onStrategyStart(strategyName, symbol);
5496
+ }
5497
+ this.loggerService.info("walkerLogicPrivateService testing strategy", {
5382
5498
  strategyName,
5383
5499
  symbol,
5384
- error: functoolsKit.errorData(error), message: functoolsKit.getErrorMessage(error),
5385
5500
  });
5386
- await errorEmitter.next(error);
5387
- // Call onStrategyError callback if provided
5388
- if (walkerSchema.callbacks?.onStrategyError) {
5389
- walkerSchema.callbacks.onStrategyError(strategyName, symbol, error);
5501
+ const iterator = this.backtestLogicPublicService.run(symbol, {
5502
+ strategyName,
5503
+ exchangeName: context.exchangeName,
5504
+ frameName: context.frameName,
5505
+ });
5506
+ try {
5507
+ await functoolsKit.resolveDocuments(iterator);
5390
5508
  }
5391
- continue;
5392
- }
5393
- if (result === CANCEL_SYMBOL) {
5394
- this.loggerService.info("walkerLogicPrivateService received stop signal, cancelling walker", {
5395
- context,
5509
+ catch (error) {
5510
+ console.warn(`walkerLogicPrivateService backtest failed symbol=${symbol} strategyName=${strategyName} exchangeName=${context.exchangeName}`);
5511
+ this.loggerService.warn("walkerLogicPrivateService backtest failed for strategy, skipping", {
5512
+ strategyName,
5513
+ symbol,
5514
+ error: functoolsKit.errorData(error), message: functoolsKit.getErrorMessage(error),
5515
+ });
5516
+ await errorEmitter.next(error);
5517
+ // Call onStrategyError callback if provided
5518
+ if (walkerSchema.callbacks?.onStrategyError) {
5519
+ walkerSchema.callbacks.onStrategyError(strategyName, symbol, error);
5520
+ }
5521
+ continue;
5522
+ }
5523
+ this.loggerService.info("walkerLogicPrivateService backtest complete", {
5524
+ strategyName,
5525
+ symbol,
5396
5526
  });
5397
- break;
5398
- }
5399
- this.loggerService.info("walkerLogicPrivateService backtest complete", {
5400
- strategyName,
5401
- symbol,
5402
- });
5403
- // Get statistics from BacktestMarkdownService
5404
- const stats = await this.backtestMarkdownService.getData(symbol, strategyName);
5405
- // Extract metric value
5406
- const value = stats[metric];
5407
- const metricValue = value !== null &&
5408
- value !== undefined &&
5409
- typeof value === "number" &&
5410
- !isNaN(value) &&
5411
- isFinite(value)
5412
- ? value
5413
- : null;
5414
- // Update best strategy if needed
5415
- const isBetter = bestMetric === null ||
5416
- (metricValue !== null && metricValue > bestMetric);
5417
- if (isBetter && metricValue !== null) {
5418
- bestMetric = metricValue;
5419
- bestStrategy = strategyName;
5420
- }
5421
- strategiesTested++;
5422
- const walkerContract = {
5423
- walkerName: context.walkerName,
5424
- exchangeName: context.exchangeName,
5425
- frameName: context.frameName,
5426
- symbol,
5427
- strategyName,
5428
- stats,
5429
- metricValue,
5430
- metric,
5431
- bestMetric,
5432
- bestStrategy,
5433
- strategiesTested,
5434
- totalStrategies: strategies.length,
5435
- };
5436
- // Emit progress event
5437
- await progressWalkerEmitter.next({
5438
- walkerName: context.walkerName,
5439
- exchangeName: context.exchangeName,
5440
- frameName: context.frameName,
5441
- symbol,
5442
- totalStrategies: strategies.length,
5443
- processedStrategies: strategiesTested,
5444
- progress: strategies.length > 0 ? strategiesTested / strategies.length : 0,
5445
- });
5446
- // Call onStrategyComplete callback if provided
5447
- if (walkerSchema.callbacks?.onStrategyComplete) {
5448
- walkerSchema.callbacks.onStrategyComplete(strategyName, symbol, stats, metricValue);
5527
+ // Get statistics from BacktestMarkdownService
5528
+ const stats = await this.backtestMarkdownService.getData(symbol, strategyName);
5529
+ // Extract metric value
5530
+ const value = stats[metric];
5531
+ const metricValue = value !== null &&
5532
+ value !== undefined &&
5533
+ typeof value === "number" &&
5534
+ !isNaN(value) &&
5535
+ isFinite(value)
5536
+ ? value
5537
+ : null;
5538
+ // Update best strategy if needed
5539
+ const isBetter = bestMetric === null ||
5540
+ (metricValue !== null && metricValue > bestMetric);
5541
+ if (isBetter && metricValue !== null) {
5542
+ bestMetric = metricValue;
5543
+ bestStrategy = strategyName;
5544
+ }
5545
+ strategiesTested++;
5546
+ const walkerContract = {
5547
+ walkerName: context.walkerName,
5548
+ exchangeName: context.exchangeName,
5549
+ frameName: context.frameName,
5550
+ symbol,
5551
+ strategyName,
5552
+ stats,
5553
+ metricValue,
5554
+ metric,
5555
+ bestMetric,
5556
+ bestStrategy,
5557
+ strategiesTested,
5558
+ totalStrategies: strategies.length,
5559
+ };
5560
+ // Emit progress event
5561
+ await progressWalkerEmitter.next({
5562
+ walkerName: context.walkerName,
5563
+ exchangeName: context.exchangeName,
5564
+ frameName: context.frameName,
5565
+ symbol,
5566
+ totalStrategies: strategies.length,
5567
+ processedStrategies: strategiesTested,
5568
+ progress: strategies.length > 0 ? strategiesTested / strategies.length : 0,
5569
+ });
5570
+ // Call onStrategyComplete callback if provided
5571
+ if (walkerSchema.callbacks?.onStrategyComplete) {
5572
+ await walkerSchema.callbacks.onStrategyComplete(strategyName, symbol, stats, metricValue);
5573
+ }
5574
+ await walkerEmitter.next(walkerContract);
5575
+ yield walkerContract;
5449
5576
  }
5450
- await walkerEmitter.next(walkerContract);
5451
- yield walkerContract;
5577
+ }
5578
+ finally {
5579
+ // Unsubscribe from stop signals by calling destructor
5580
+ unsubscribe();
5452
5581
  }
5453
5582
  const finalResults = {
5454
5583
  walkerName: context.walkerName,
@@ -13270,19 +13399,53 @@ async function dumpSignal(signalId, history, signal, outputDir = "./dump/strateg
13270
13399
 
13271
13400
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
13272
13401
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
13402
+ const BACKTEST_METHOD_NAME_STOP = "BacktestUtils.stop";
13273
13403
  const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
13274
13404
  const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
13405
+ const BACKTEST_METHOD_NAME_TASK = "BacktestUtils.task";
13406
+ const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
13275
13407
  /**
13276
- * Utility class for backtest operations.
13408
+ * Internal task function that runs backtest and handles completion.
13409
+ * Consumes backtest results and updates instance state flags.
13277
13410
  *
13278
- * Provides simplified access to backtestCommandService.run() with logging.
13279
- * Exported as singleton instance for convenient usage.
13411
+ * @param symbol - Trading pair symbol
13412
+ * @param context - Execution context with strategy, exchange, and frame names
13413
+ * @param self - BacktestInstance reference for state management
13414
+ * @returns Promise that resolves when backtest completes
13415
+ *
13416
+ * @internal
13417
+ */
13418
+ const INSTANCE_TASK_FN$2 = async (symbol, context, self) => {
13419
+ {
13420
+ self._isStopped = false;
13421
+ self._isDone = false;
13422
+ }
13423
+ for await (const _ of self.run(symbol, context)) {
13424
+ if (self._isStopped) {
13425
+ break;
13426
+ }
13427
+ }
13428
+ if (!self._isDone) {
13429
+ await doneBacktestSubject.next({
13430
+ exchangeName: context.exchangeName,
13431
+ strategyName: context.strategyName,
13432
+ backtest: true,
13433
+ symbol,
13434
+ });
13435
+ }
13436
+ self._isDone = true;
13437
+ };
13438
+ /**
13439
+ * Instance class for backtest operations on a specific symbol-strategy pair.
13440
+ *
13441
+ * Provides isolated backtest execution and reporting for a single symbol-strategy combination.
13442
+ * Each instance maintains its own state and context.
13280
13443
  *
13281
13444
  * @example
13282
13445
  * ```typescript
13283
- * import { Backtest } from "./classes/Backtest";
13446
+ * const instance = new BacktestInstance("BTCUSDT", "my-strategy");
13284
13447
  *
13285
- * for await (const result of Backtest.run("BTCUSDT", {
13448
+ * for await (const result of instance.run("BTCUSDT", {
13286
13449
  * strategyName: "my-strategy",
13287
13450
  * exchangeName: "my-exchange",
13288
13451
  * frameName: "1d-backtest"
@@ -13291,8 +13454,57 @@ const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
13291
13454
  * }
13292
13455
  * ```
13293
13456
  */
13294
- class BacktestUtils {
13295
- constructor() {
13457
+ class BacktestInstance {
13458
+ /**
13459
+ * Creates a new BacktestInstance for a specific symbol-strategy pair.
13460
+ *
13461
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13462
+ * @param strategyName - Strategy name for this backtest instance
13463
+ */
13464
+ constructor(symbol, strategyName) {
13465
+ this.symbol = symbol;
13466
+ this.strategyName = strategyName;
13467
+ /** Internal flag indicating if backtest was stopped manually */
13468
+ this._isStopped = false;
13469
+ /** Internal flag indicating if backtest task completed */
13470
+ this._isDone = false;
13471
+ /**
13472
+ * Internal singlerun task that executes the backtest.
13473
+ * Ensures only one backtest run per instance using singlerun wrapper.
13474
+ *
13475
+ * @param symbol - Trading pair symbol
13476
+ * @param context - Execution context with strategy, exchange, and frame names
13477
+ * @returns Promise that resolves when backtest completes
13478
+ *
13479
+ * @internal
13480
+ */
13481
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
13482
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_TASK, {
13483
+ symbol,
13484
+ context,
13485
+ });
13486
+ return await INSTANCE_TASK_FN$2(symbol, context, this);
13487
+ });
13488
+ /**
13489
+ * Gets the current status of this backtest instance.
13490
+ *
13491
+ * @returns Promise resolving to status object with symbol, strategyName, and task status
13492
+ *
13493
+ * @example
13494
+ * ```typescript
13495
+ * const instance = new BacktestInstance("BTCUSDT", "my-strategy");
13496
+ * const status = await instance.getStatus();
13497
+ * console.log(status.status); // "idle", "running", or "done"
13498
+ * ```
13499
+ */
13500
+ this.getStatus = async () => {
13501
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_STATUS);
13502
+ return {
13503
+ symbol: this.symbol,
13504
+ strategyName: this.strategyName,
13505
+ status: this.task.getStatus(),
13506
+ };
13507
+ };
13296
13508
  /**
13297
13509
  * Runs backtest for a symbol with context propagation.
13298
13510
  *
@@ -13330,13 +13542,12 @@ class BacktestUtils {
13330
13542
  *
13331
13543
  * @example
13332
13544
  * ```typescript
13333
- * // Run backtest silently, only callbacks will fire
13334
- * await Backtest.background("BTCUSDT", {
13545
+ * const instance = new BacktestInstance();
13546
+ * const cancel = instance.background("BTCUSDT", {
13335
13547
  * strategyName: "my-strategy",
13336
13548
  * exchangeName: "my-exchange",
13337
13549
  * frameName: "1d-backtest"
13338
13550
  * });
13339
- * console.log("Backtest completed");
13340
13551
  * ```
13341
13552
  */
13342
13553
  this.background = (symbol, context) => {
@@ -13344,34 +13555,16 @@ class BacktestUtils {
13344
13555
  symbol,
13345
13556
  context,
13346
13557
  });
13347
- let isStopped = false;
13348
- let isDone = false;
13349
- const task = async () => {
13350
- for await (const _ of this.run(symbol, context)) {
13351
- if (isStopped) {
13352
- break;
13353
- }
13354
- }
13355
- if (!isDone) {
13356
- await doneBacktestSubject.next({
13357
- exchangeName: context.exchangeName,
13358
- strategyName: context.strategyName,
13359
- backtest: true,
13360
- symbol,
13361
- });
13362
- }
13363
- isDone = true;
13364
- };
13365
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13558
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13366
13559
  return () => {
13367
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
13560
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13368
13561
  backtest$1.strategyGlobalService
13369
13562
  .getPendingSignal(symbol, context.strategyName)
13370
13563
  .then(async (pendingSignal) => {
13371
13564
  if (pendingSignal) {
13372
13565
  return;
13373
13566
  }
13374
- if (!isDone) {
13567
+ if (!this._isDone) {
13375
13568
  await doneBacktestSubject.next({
13376
13569
  exchangeName: context.exchangeName,
13377
13570
  strategyName: context.strategyName,
@@ -13379,11 +13572,35 @@ class BacktestUtils {
13379
13572
  symbol,
13380
13573
  });
13381
13574
  }
13382
- isDone = true;
13575
+ this._isDone = true;
13383
13576
  });
13384
- isStopped = true;
13577
+ this._isStopped = true;
13385
13578
  };
13386
13579
  };
13580
+ /**
13581
+ * Stops the strategy from generating new signals.
13582
+ *
13583
+ * Sets internal flag to prevent strategy from opening new signals.
13584
+ * Current active signal (if any) will complete normally.
13585
+ * Backtest will stop at the next safe point (idle state or after signal closes).
13586
+ *
13587
+ * @param symbol - Trading pair symbol
13588
+ * @param strategyName - Strategy name to stop
13589
+ * @returns Promise that resolves when stop flag is set
13590
+ *
13591
+ * @example
13592
+ * ```typescript
13593
+ * const instance = new BacktestInstance();
13594
+ * await instance.stop("BTCUSDT", "my-strategy");
13595
+ * ```
13596
+ */
13597
+ this.stop = async (symbol, strategyName) => {
13598
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_STOP, {
13599
+ symbol,
13600
+ strategyName,
13601
+ });
13602
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13603
+ };
13387
13604
  /**
13388
13605
  * Gets statistical data from all closed signals for a symbol-strategy pair.
13389
13606
  *
@@ -13393,7 +13610,8 @@ class BacktestUtils {
13393
13610
  *
13394
13611
  * @example
13395
13612
  * ```typescript
13396
- * const stats = await Backtest.getData("BTCUSDT", "my-strategy");
13613
+ * const instance = new BacktestInstance();
13614
+ * const stats = await instance.getData("BTCUSDT", "my-strategy");
13397
13615
  * console.log(stats.sharpeRatio, stats.winRate);
13398
13616
  * ```
13399
13617
  */
@@ -13413,7 +13631,8 @@ class BacktestUtils {
13413
13631
  *
13414
13632
  * @example
13415
13633
  * ```typescript
13416
- * const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
13634
+ * const instance = new BacktestInstance();
13635
+ * const markdown = await instance.getReport("BTCUSDT", "my-strategy");
13417
13636
  * console.log(markdown);
13418
13637
  * ```
13419
13638
  */
@@ -13433,11 +13652,12 @@ class BacktestUtils {
13433
13652
  *
13434
13653
  * @example
13435
13654
  * ```typescript
13655
+ * const instance = new BacktestInstance();
13436
13656
  * // Save to default path: ./dump/backtest/my-strategy.md
13437
- * await Backtest.dump("BTCUSDT", "my-strategy");
13657
+ * await instance.dump("BTCUSDT", "my-strategy");
13438
13658
  *
13439
13659
  * // Save to custom path: ./custom/path/my-strategy.md
13440
- * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
13660
+ * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
13441
13661
  * ```
13442
13662
  */
13443
13663
  this.dump = async (symbol, strategyName, path) => {
@@ -13451,7 +13671,10 @@ class BacktestUtils {
13451
13671
  }
13452
13672
  }
13453
13673
  /**
13454
- * Singleton instance of BacktestUtils for convenient backtest operations.
13674
+ * Utility class for backtest operations.
13675
+ *
13676
+ * Provides simplified access to backtestCommandService.run() with logging.
13677
+ * Exported as singleton instance for convenient usage.
13455
13678
  *
13456
13679
  * @example
13457
13680
  * ```typescript
@@ -13462,49 +13685,276 @@ class BacktestUtils {
13462
13685
  * exchangeName: "my-exchange",
13463
13686
  * frameName: "1d-backtest"
13464
13687
  * })) {
13465
- * if (result.action === "closed") {
13466
- * console.log("PNL:", result.pnl.pnlPercentage);
13467
- * }
13688
+ * console.log("Closed signal PNL:", result.pnl.pnlPercentage);
13468
13689
  * }
13469
13690
  * ```
13470
13691
  */
13471
- const Backtest = new BacktestUtils();
13472
-
13473
- const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
13474
- const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
13475
- const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
13476
- const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
13477
- /**
13478
- * Utility class for live trading operations.
13479
- *
13480
- * Provides simplified access to liveCommandService.run() with logging.
13481
- * Exported as singleton instance for convenient usage.
13482
- *
13483
- * Features:
13484
- * - Infinite async generator (never completes)
13485
- * - Crash recovery via persisted state
13486
- * - Real-time progression with Date.now()
13487
- *
13488
- * @example
13489
- * ```typescript
13490
- * import { Live } from "./classes/Live";
13491
- *
13492
- * // Infinite loop - use Ctrl+C to stop
13493
- * for await (const result of Live.run("BTCUSDT", {
13494
- * strategyName: "my-strategy",
13495
- * exchangeName: "my-exchange",
13496
- * frameName: ""
13497
- * })) {
13498
- * if (result.action === "opened") {
13499
- * console.log("Signal opened:", result.signal);
13500
- * } else if (result.action === "closed") {
13501
- * console.log("PNL:", result.pnl.pnlPercentage);
13502
- * }
13503
- * }
13504
- * ```
13505
- */
13506
- class LiveUtils {
13692
+ class BacktestUtils {
13507
13693
  constructor() {
13694
+ /**
13695
+ * Memoized function to get or create BacktestInstance for a symbol-strategy pair.
13696
+ * Each symbol-strategy combination gets its own isolated instance.
13697
+ */
13698
+ this._getInstance = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new BacktestInstance(symbol, strategyName));
13699
+ /**
13700
+ * Runs backtest for a symbol with context propagation.
13701
+ *
13702
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13703
+ * @param context - Execution context with strategy, exchange, and frame names
13704
+ * @returns Async generator yielding closed signals with PNL
13705
+ */
13706
+ this.run = (symbol, context) => {
13707
+ const instance = this._getInstance(symbol, context.strategyName);
13708
+ return instance.run(symbol, context);
13709
+ };
13710
+ /**
13711
+ * Runs backtest in background without yielding results.
13712
+ *
13713
+ * Consumes all backtest results internally without exposing them.
13714
+ * Useful for running backtests for side effects only (callbacks, logging).
13715
+ *
13716
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13717
+ * @param context - Execution context with strategy, exchange, and frame names
13718
+ * @returns Cancellation closure
13719
+ *
13720
+ * @example
13721
+ * ```typescript
13722
+ * // Run backtest silently, only callbacks will fire
13723
+ * await Backtest.background("BTCUSDT", {
13724
+ * strategyName: "my-strategy",
13725
+ * exchangeName: "my-exchange",
13726
+ * frameName: "1d-backtest"
13727
+ * });
13728
+ * console.log("Backtest completed");
13729
+ * ```
13730
+ */
13731
+ this.background = (symbol, context) => {
13732
+ const instance = this._getInstance(symbol, context.strategyName);
13733
+ return instance.background(symbol, context);
13734
+ };
13735
+ /**
13736
+ * Stops the strategy from generating new signals.
13737
+ *
13738
+ * Sets internal flag to prevent strategy from opening new signals.
13739
+ * Current active signal (if any) will complete normally.
13740
+ * Backtest will stop at the next safe point (idle state or after signal closes).
13741
+ *
13742
+ * @param symbol - Trading pair symbol
13743
+ * @param strategyName - Strategy name to stop
13744
+ * @returns Promise that resolves when stop flag is set
13745
+ *
13746
+ * @example
13747
+ * ```typescript
13748
+ * // Stop strategy after some condition
13749
+ * await Backtest.stop("BTCUSDT", "my-strategy");
13750
+ * ```
13751
+ */
13752
+ this.stop = async (symbol, strategyName) => {
13753
+ const instance = this._getInstance(symbol, strategyName);
13754
+ return await instance.stop(symbol, strategyName);
13755
+ };
13756
+ /**
13757
+ * Gets statistical data from all closed signals for a symbol-strategy pair.
13758
+ *
13759
+ * @param symbol - Trading pair symbol
13760
+ * @param strategyName - Strategy name to get data for
13761
+ * @returns Promise resolving to statistical data object
13762
+ *
13763
+ * @example
13764
+ * ```typescript
13765
+ * const stats = await Backtest.getData("BTCUSDT", "my-strategy");
13766
+ * console.log(stats.sharpeRatio, stats.winRate);
13767
+ * ```
13768
+ */
13769
+ this.getData = async (symbol, strategyName) => {
13770
+ const instance = this._getInstance(symbol, strategyName);
13771
+ return await instance.getData(symbol, strategyName);
13772
+ };
13773
+ /**
13774
+ * Generates markdown report with all closed signals for a symbol-strategy pair.
13775
+ *
13776
+ * @param symbol - Trading pair symbol
13777
+ * @param strategyName - Strategy name to generate report for
13778
+ * @returns Promise resolving to markdown formatted report string
13779
+ *
13780
+ * @example
13781
+ * ```typescript
13782
+ * const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
13783
+ * console.log(markdown);
13784
+ * ```
13785
+ */
13786
+ this.getReport = async (symbol, strategyName) => {
13787
+ const instance = this._getInstance(symbol, strategyName);
13788
+ return await instance.getReport(symbol, strategyName);
13789
+ };
13790
+ /**
13791
+ * Saves strategy report to disk.
13792
+ *
13793
+ * @param symbol - Trading pair symbol
13794
+ * @param strategyName - Strategy name to save report for
13795
+ * @param path - Optional directory path to save report (default: "./dump/backtest")
13796
+ *
13797
+ * @example
13798
+ * ```typescript
13799
+ * // Save to default path: ./dump/backtest/my-strategy.md
13800
+ * await Backtest.dump("BTCUSDT", "my-strategy");
13801
+ *
13802
+ * // Save to custom path: ./custom/path/my-strategy.md
13803
+ * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
13804
+ * ```
13805
+ */
13806
+ this.dump = async (symbol, strategyName, path) => {
13807
+ const instance = this._getInstance(symbol, strategyName);
13808
+ return await instance.dump(symbol, strategyName, path);
13809
+ };
13810
+ /**
13811
+ * Lists all active backtest instances with their current status.
13812
+ *
13813
+ * @returns Promise resolving to array of status objects for all instances
13814
+ *
13815
+ * @example
13816
+ * ```typescript
13817
+ * const statusList = await Backtest.list();
13818
+ * statusList.forEach(status => {
13819
+ * console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
13820
+ * });
13821
+ * ```
13822
+ */
13823
+ this.list = async () => {
13824
+ const instanceList = this._getInstance.values();
13825
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
13826
+ };
13827
+ }
13828
+ }
13829
+ /**
13830
+ * Singleton instance of BacktestUtils for convenient backtest operations.
13831
+ *
13832
+ * @example
13833
+ * ```typescript
13834
+ * import { Backtest } from "./classes/Backtest";
13835
+ *
13836
+ * for await (const result of Backtest.run("BTCUSDT", {
13837
+ * strategyName: "my-strategy",
13838
+ * exchangeName: "my-exchange",
13839
+ * frameName: "1d-backtest"
13840
+ * })) {
13841
+ * if (result.action === "closed") {
13842
+ * console.log("PNL:", result.pnl.pnlPercentage);
13843
+ * }
13844
+ * }
13845
+ * ```
13846
+ */
13847
+ const Backtest = new BacktestUtils();
13848
+
13849
+ const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
13850
+ const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
13851
+ const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
13852
+ const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
13853
+ const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
13854
+ const LIVE_METHOD_NAME_TASK = "LiveUtils.task";
13855
+ const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
13856
+ /**
13857
+ * Internal task function that runs live trading and handles completion.
13858
+ * Consumes live trading results and updates instance state flags.
13859
+ *
13860
+ * @param symbol - Trading pair symbol
13861
+ * @param context - Execution context with strategy and exchange names
13862
+ * @param self - LiveInstance reference for state management
13863
+ * @returns Promise that resolves when live trading completes
13864
+ *
13865
+ * @internal
13866
+ */
13867
+ const INSTANCE_TASK_FN$1 = async (symbol, context, self) => {
13868
+ {
13869
+ self._isStopped = false;
13870
+ self._isDone = false;
13871
+ }
13872
+ for await (const signal of self.run(symbol, context)) {
13873
+ if (signal?.action === "closed" && self._isStopped) {
13874
+ break;
13875
+ }
13876
+ }
13877
+ if (!self._isDone) {
13878
+ await doneLiveSubject.next({
13879
+ exchangeName: context.exchangeName,
13880
+ strategyName: context.strategyName,
13881
+ backtest: false,
13882
+ symbol,
13883
+ });
13884
+ }
13885
+ self._isDone = true;
13886
+ };
13887
+ /**
13888
+ * Instance class for live trading operations on a specific symbol-strategy pair.
13889
+ *
13890
+ * Provides isolated live trading execution and reporting for a single symbol-strategy combination.
13891
+ * Each instance maintains its own state and context.
13892
+ *
13893
+ * @example
13894
+ * ```typescript
13895
+ * const instance = new LiveInstance("BTCUSDT", "my-strategy");
13896
+ *
13897
+ * for await (const result of instance.run("BTCUSDT", {
13898
+ * strategyName: "my-strategy",
13899
+ * exchangeName: "my-exchange"
13900
+ * })) {
13901
+ * if (result.action === "closed") {
13902
+ * console.log("Signal closed, PNL:", result.pnl.pnlPercentage);
13903
+ * }
13904
+ * }
13905
+ * ```
13906
+ */
13907
+ class LiveInstance {
13908
+ /**
13909
+ * Creates a new LiveInstance for a specific symbol-strategy pair.
13910
+ *
13911
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
13912
+ * @param strategyName - Strategy name for this live trading instance
13913
+ */
13914
+ constructor(symbol, strategyName) {
13915
+ this.symbol = symbol;
13916
+ this.strategyName = strategyName;
13917
+ /** Internal flag indicating if live trading was stopped manually */
13918
+ this._isStopped = false;
13919
+ /** Internal flag indicating if live trading task completed */
13920
+ this._isDone = false;
13921
+ /**
13922
+ * Internal singlerun task that executes the live trading.
13923
+ * Ensures only one live trading run per instance using singlerun wrapper.
13924
+ *
13925
+ * @param symbol - Trading pair symbol
13926
+ * @param context - Execution context with strategy and exchange names
13927
+ * @returns Promise that resolves when live trading completes
13928
+ *
13929
+ * @internal
13930
+ */
13931
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
13932
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_TASK, {
13933
+ symbol,
13934
+ context,
13935
+ });
13936
+ return await INSTANCE_TASK_FN$1(symbol, context, this);
13937
+ });
13938
+ /**
13939
+ * Gets the current status of this live trading instance.
13940
+ *
13941
+ * @returns Promise resolving to status object with symbol, strategyName, and task status
13942
+ *
13943
+ * @example
13944
+ * ```typescript
13945
+ * const instance = new LiveInstance("BTCUSDT", "my-strategy");
13946
+ * const status = await instance.getStatus();
13947
+ * console.log(status.status); // "idle", "running", or "done"
13948
+ * ```
13949
+ */
13950
+ this.getStatus = async () => {
13951
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_STATUS);
13952
+ return {
13953
+ symbol: this.symbol,
13954
+ strategyName: this.strategyName,
13955
+ status: this.task.getStatus(),
13956
+ };
13957
+ };
13508
13958
  /**
13509
13959
  * Runs live trading for a symbol with context propagation.
13510
13960
  *
@@ -13546,9 +13996,8 @@ class LiveUtils {
13546
13996
  *
13547
13997
  * @example
13548
13998
  * ```typescript
13549
- * // Run live trading silently in background, only callbacks will fire
13550
- * // This will run forever until Ctrl+C
13551
- * await Live.background("BTCUSDT", {
13999
+ * const instance = new LiveInstance();
14000
+ * const cancel = instance.background("BTCUSDT", {
13552
14001
  * strategyName: "my-strategy",
13553
14002
  * exchangeName: "my-exchange"
13554
14003
  * });
@@ -13559,34 +14008,16 @@ class LiveUtils {
13559
14008
  symbol,
13560
14009
  context,
13561
14010
  });
13562
- let isStopped = false;
13563
- let isDone = false;
13564
- const task = async () => {
13565
- for await (const signal of this.run(symbol, context)) {
13566
- if (signal?.action === "closed" && isStopped) {
13567
- break;
13568
- }
13569
- }
13570
- if (!isDone) {
13571
- await doneLiveSubject.next({
13572
- exchangeName: context.exchangeName,
13573
- strategyName: context.strategyName,
13574
- backtest: false,
13575
- symbol,
13576
- });
13577
- }
13578
- isDone = true;
13579
- };
13580
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14011
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13581
14012
  return () => {
13582
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
14013
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
13583
14014
  backtest$1.strategyGlobalService
13584
14015
  .getPendingSignal(symbol, context.strategyName)
13585
14016
  .then(async (pendingSignal) => {
13586
14017
  if (pendingSignal) {
13587
14018
  return;
13588
14019
  }
13589
- if (!isDone) {
14020
+ if (!this._isDone) {
13590
14021
  await doneLiveSubject.next({
13591
14022
  exchangeName: context.exchangeName,
13592
14023
  strategyName: context.strategyName,
@@ -13594,11 +14025,200 @@ class LiveUtils {
13594
14025
  symbol,
13595
14026
  });
13596
14027
  }
13597
- isDone = true;
14028
+ this._isDone = true;
13598
14029
  });
13599
- isStopped = true;
14030
+ this._isStopped = true;
13600
14031
  };
13601
14032
  };
14033
+ /**
14034
+ * Stops the strategy from generating new signals.
14035
+ *
14036
+ * Sets internal flag to prevent strategy from opening new signals.
14037
+ * Current active signal (if any) will complete normally.
14038
+ * Live trading will stop at the next safe point (idle/closed state).
14039
+ *
14040
+ * @param symbol - Trading pair symbol
14041
+ * @param strategyName - Strategy name to stop
14042
+ * @returns Promise that resolves when stop flag is set
14043
+ *
14044
+ * @example
14045
+ * ```typescript
14046
+ * const instance = new LiveInstance();
14047
+ * await instance.stop("BTCUSDT", "my-strategy");
14048
+ * ```
14049
+ */
14050
+ this.stop = async (symbol, strategyName) => {
14051
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
14052
+ symbol,
14053
+ strategyName,
14054
+ });
14055
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14056
+ };
14057
+ /**
14058
+ * Gets statistical data from all live trading events for a symbol-strategy pair.
14059
+ *
14060
+ * @param symbol - Trading pair symbol
14061
+ * @param strategyName - Strategy name to get data for
14062
+ * @returns Promise resolving to statistical data object
14063
+ *
14064
+ * @example
14065
+ * ```typescript
14066
+ * const instance = new LiveInstance();
14067
+ * const stats = await instance.getData("BTCUSDT", "my-strategy");
14068
+ * console.log(stats.sharpeRatio, stats.winRate);
14069
+ * ```
14070
+ */
14071
+ this.getData = async (symbol, strategyName) => {
14072
+ backtest$1.loggerService.info("LiveUtils.getData", {
14073
+ symbol,
14074
+ strategyName,
14075
+ });
14076
+ return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
14077
+ };
14078
+ /**
14079
+ * Generates markdown report with all events for a symbol-strategy pair.
14080
+ *
14081
+ * @param symbol - Trading pair symbol
14082
+ * @param strategyName - Strategy name to generate report for
14083
+ * @returns Promise resolving to markdown formatted report string
14084
+ *
14085
+ * @example
14086
+ * ```typescript
14087
+ * const instance = new LiveInstance();
14088
+ * const markdown = await instance.getReport("BTCUSDT", "my-strategy");
14089
+ * console.log(markdown);
14090
+ * ```
14091
+ */
14092
+ this.getReport = async (symbol, strategyName) => {
14093
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
14094
+ symbol,
14095
+ strategyName,
14096
+ });
14097
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
14098
+ };
14099
+ /**
14100
+ * Saves strategy report to disk.
14101
+ *
14102
+ * @param symbol - Trading pair symbol
14103
+ * @param strategyName - Strategy name to save report for
14104
+ * @param path - Optional directory path to save report (default: "./dump/live")
14105
+ *
14106
+ * @example
14107
+ * ```typescript
14108
+ * const instance = new LiveInstance();
14109
+ * // Save to default path: ./dump/live/my-strategy.md
14110
+ * await instance.dump("BTCUSDT", "my-strategy");
14111
+ *
14112
+ * // Save to custom path: ./custom/path/my-strategy.md
14113
+ * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
14114
+ * ```
14115
+ */
14116
+ this.dump = async (symbol, strategyName, path) => {
14117
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
14118
+ symbol,
14119
+ strategyName,
14120
+ path,
14121
+ });
14122
+ await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
14123
+ };
14124
+ }
14125
+ }
14126
+ /**
14127
+ * Utility class for live trading operations.
14128
+ *
14129
+ * Provides simplified access to liveCommandService.run() with logging.
14130
+ * Exported as singleton instance for convenient usage.
14131
+ *
14132
+ * Features:
14133
+ * - Infinite async generator (never completes)
14134
+ * - Crash recovery via persisted state
14135
+ * - Real-time progression with Date.now()
14136
+ *
14137
+ * @example
14138
+ * ```typescript
14139
+ * import { Live } from "./classes/Live";
14140
+ *
14141
+ * // Infinite loop - use Ctrl+C to stop
14142
+ * for await (const result of Live.run("BTCUSDT", {
14143
+ * strategyName: "my-strategy",
14144
+ * exchangeName: "my-exchange",
14145
+ * frameName: ""
14146
+ * })) {
14147
+ * if (result.action === "opened") {
14148
+ * console.log("Signal opened:", result.signal);
14149
+ * } else if (result.action === "closed") {
14150
+ * console.log("PNL:", result.pnl.pnlPercentage);
14151
+ * }
14152
+ * }
14153
+ * ```
14154
+ */
14155
+ class LiveUtils {
14156
+ constructor() {
14157
+ /**
14158
+ * Memoized function to get or create LiveInstance for a symbol-strategy pair.
14159
+ * Each symbol-strategy combination gets its own isolated instance.
14160
+ */
14161
+ this._getInstance = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new LiveInstance(symbol, strategyName));
14162
+ /**
14163
+ * Runs live trading for a symbol with context propagation.
14164
+ *
14165
+ * Infinite async generator with crash recovery support.
14166
+ * Process can crash and restart - state will be recovered from disk.
14167
+ *
14168
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14169
+ * @param context - Execution context with strategy and exchange names
14170
+ * @returns Infinite async generator yielding opened and closed signals
14171
+ */
14172
+ this.run = (symbol, context) => {
14173
+ const instance = this._getInstance(symbol, context.strategyName);
14174
+ return instance.run(symbol, context);
14175
+ };
14176
+ /**
14177
+ * Runs live trading in background without yielding results.
14178
+ *
14179
+ * Consumes all live trading results internally without exposing them.
14180
+ * Infinite loop - will run until process is stopped or crashes.
14181
+ * Useful for running live trading for side effects only (callbacks, persistence).
14182
+ *
14183
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14184
+ * @param context - Execution context with strategy and exchange names
14185
+ * @returns Cancellation closure
14186
+ *
14187
+ * @example
14188
+ * ```typescript
14189
+ * // Run live trading silently in background, only callbacks will fire
14190
+ * // This will run forever until Ctrl+C
14191
+ * await Live.background("BTCUSDT", {
14192
+ * strategyName: "my-strategy",
14193
+ * exchangeName: "my-exchange"
14194
+ * });
14195
+ * ```
14196
+ */
14197
+ this.background = (symbol, context) => {
14198
+ const instance = this._getInstance(symbol, context.strategyName);
14199
+ return instance.background(symbol, context);
14200
+ };
14201
+ /**
14202
+ * Stops the strategy from generating new signals.
14203
+ *
14204
+ * Sets internal flag to prevent strategy from opening new signals.
14205
+ * Current active signal (if any) will complete normally.
14206
+ * Live trading will stop at the next safe point (idle/closed state).
14207
+ *
14208
+ * @param symbol - Trading pair symbol
14209
+ * @param strategyName - Strategy name to stop
14210
+ * @returns Promise that resolves when stop flag is set
14211
+ *
14212
+ * @example
14213
+ * ```typescript
14214
+ * // Stop live trading gracefully
14215
+ * await Live.stop("BTCUSDT", "my-strategy");
14216
+ * ```
14217
+ */
14218
+ this.stop = async (symbol, strategyName) => {
14219
+ const instance = this._getInstance(symbol, strategyName);
14220
+ return await instance.stop(symbol, strategyName);
14221
+ };
13602
14222
  /**
13603
14223
  * Gets statistical data from all live trading events for a symbol-strategy pair.
13604
14224
  *
@@ -13613,11 +14233,8 @@ class LiveUtils {
13613
14233
  * ```
13614
14234
  */
13615
14235
  this.getData = async (symbol, strategyName) => {
13616
- backtest$1.loggerService.info("LiveUtils.getData", {
13617
- symbol,
13618
- strategyName,
13619
- });
13620
- return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
14236
+ const instance = this._getInstance(symbol, strategyName);
14237
+ return await instance.getData(symbol, strategyName);
13621
14238
  };
13622
14239
  /**
13623
14240
  * Generates markdown report with all events for a symbol-strategy pair.
@@ -13633,11 +14250,8 @@ class LiveUtils {
13633
14250
  * ```
13634
14251
  */
13635
14252
  this.getReport = async (symbol, strategyName) => {
13636
- backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
13637
- symbol,
13638
- strategyName,
13639
- });
13640
- return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
14253
+ const instance = this._getInstance(symbol, strategyName);
14254
+ return await instance.getReport(symbol, strategyName);
13641
14255
  };
13642
14256
  /**
13643
14257
  * Saves strategy report to disk.
@@ -13656,12 +14270,25 @@ class LiveUtils {
13656
14270
  * ```
13657
14271
  */
13658
14272
  this.dump = async (symbol, strategyName, path) => {
13659
- backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
13660
- symbol,
13661
- strategyName,
13662
- path,
13663
- });
13664
- await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
14273
+ const instance = this._getInstance(symbol, strategyName);
14274
+ return await instance.dump(symbol, strategyName, path);
14275
+ };
14276
+ /**
14277
+ * Lists all active live trading instances with their current status.
14278
+ *
14279
+ * @returns Promise resolving to array of status objects for all instances
14280
+ *
14281
+ * @example
14282
+ * ```typescript
14283
+ * const statusList = await Live.list();
14284
+ * statusList.forEach(status => {
14285
+ * console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
14286
+ * });
14287
+ * ```
14288
+ */
14289
+ this.list = async () => {
14290
+ const instanceList = this._getInstance.values();
14291
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
13665
14292
  };
13666
14293
  }
13667
14294
  }
@@ -13906,30 +14533,112 @@ class Performance {
13906
14533
 
13907
14534
  const WALKER_METHOD_NAME_RUN = "WalkerUtils.run";
13908
14535
  const WALKER_METHOD_NAME_BACKGROUND = "WalkerUtils.background";
14536
+ const WALKER_METHOD_NAME_STOP = "WalkerUtils.stop";
13909
14537
  const WALKER_METHOD_NAME_GET_DATA = "WalkerUtils.getData";
13910
14538
  const WALKER_METHOD_NAME_GET_REPORT = "WalkerUtils.getReport";
13911
14539
  const WALKER_METHOD_NAME_DUMP = "WalkerUtils.dump";
14540
+ const WALKER_METHOD_NAME_TASK = "WalkerUtils.task";
14541
+ const WALKER_METHOD_NAME_GET_STATUS = "WalkerUtils.getStatus";
13912
14542
  /**
13913
- * Utility class for walker operations.
14543
+ * Internal task function that runs walker and handles completion.
14544
+ * Consumes walker results and updates instance state flags.
13914
14545
  *
13915
- * Provides simplified access to walkerCommandService.run() with logging.
13916
- * Automatically pulls exchangeName and frameName from walker schema.
13917
- * Exported as singleton instance for convenient usage.
14546
+ * @param symbol - Trading pair symbol
14547
+ * @param context - Execution context with walker name
14548
+ * @param self - WalkerInstance reference for state management
14549
+ * @returns Promise that resolves when walker completes
14550
+ *
14551
+ * @internal
14552
+ */
14553
+ const INSTANCE_TASK_FN = async (symbol, context, self) => {
14554
+ {
14555
+ self._isStopped = false;
14556
+ self._isDone = false;
14557
+ }
14558
+ for await (const _ of self.run(symbol, context)) {
14559
+ if (self._isStopped) {
14560
+ break;
14561
+ }
14562
+ }
14563
+ if (!self._isDone) {
14564
+ const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
14565
+ await doneWalkerSubject.next({
14566
+ exchangeName: walkerSchema.exchangeName,
14567
+ strategyName: context.walkerName,
14568
+ backtest: true,
14569
+ symbol,
14570
+ });
14571
+ }
14572
+ self._isDone = true;
14573
+ };
14574
+ /**
14575
+ * Instance class for walker operations on a specific symbol-walker pair.
14576
+ *
14577
+ * Provides isolated walker execution and reporting for a single symbol-walker combination.
14578
+ * Each instance maintains its own state and context.
13918
14579
  *
13919
14580
  * @example
13920
14581
  * ```typescript
13921
- * import { Walker } from "./classes/Walker";
14582
+ * const instance = new WalkerInstance("BTCUSDT", "my-walker");
13922
14583
  *
13923
- * for await (const result of Walker.run("BTCUSDT", {
14584
+ * for await (const result of instance.run("BTCUSDT", {
13924
14585
  * walkerName: "my-walker"
13925
14586
  * })) {
13926
14587
  * console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
13927
- * console.log("Best strategy:", result.bestStrategy, result.bestMetric);
13928
14588
  * }
13929
14589
  * ```
13930
14590
  */
13931
- class WalkerUtils {
13932
- constructor() {
14591
+ class WalkerInstance {
14592
+ /**
14593
+ * Creates a new WalkerInstance for a specific symbol-walker pair.
14594
+ *
14595
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14596
+ * @param walkerName - Walker name for this walker instance
14597
+ */
14598
+ constructor(symbol, walkerName) {
14599
+ this.symbol = symbol;
14600
+ this.walkerName = walkerName;
14601
+ /** Internal flag indicating if walker was stopped manually */
14602
+ this._isStopped = false;
14603
+ /** Internal flag indicating if walker task completed */
14604
+ this._isDone = false;
14605
+ /**
14606
+ * Internal singlerun task that executes the walker.
14607
+ * Ensures only one walker run per instance using singlerun wrapper.
14608
+ *
14609
+ * @param symbol - Trading pair symbol
14610
+ * @param context - Execution context with walker name
14611
+ * @returns Promise that resolves when walker completes
14612
+ *
14613
+ * @internal
14614
+ */
14615
+ this.task = functoolsKit.singlerun(async (symbol, context) => {
14616
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_TASK, {
14617
+ symbol,
14618
+ context,
14619
+ });
14620
+ return await INSTANCE_TASK_FN(symbol, context, this);
14621
+ });
14622
+ /**
14623
+ * Gets the current status of this walker instance.
14624
+ *
14625
+ * @returns Promise resolving to status object with symbol, walkerName, and task status
14626
+ *
14627
+ * @example
14628
+ * ```typescript
14629
+ * const instance = new WalkerInstance("BTCUSDT", "my-walker");
14630
+ * const status = await instance.getStatus();
14631
+ * console.log(status.status); // "idle", "running", or "done"
14632
+ * ```
14633
+ */
14634
+ this.getStatus = async () => {
14635
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_STATUS);
14636
+ return {
14637
+ symbol: this.symbol,
14638
+ walkerName: this.walkerName,
14639
+ status: this.task.getStatus(),
14640
+ };
14641
+ };
13933
14642
  /**
13934
14643
  * Runs walker comparison for a symbol with context propagation.
13935
14644
  *
@@ -13982,11 +14691,10 @@ class WalkerUtils {
13982
14691
  *
13983
14692
  * @example
13984
14693
  * ```typescript
13985
- * // Run walker silently, only callbacks will fire
13986
- * await Walker.background("BTCUSDT", {
14694
+ * const instance = new WalkerInstance();
14695
+ * const cancel = instance.background("BTCUSDT", {
13987
14696
  * walkerName: "my-walker"
13988
14697
  * });
13989
- * console.log("Walker comparison completed");
13990
14698
  * ```
13991
14699
  */
13992
14700
  this.background = (symbol, context) => {
@@ -13995,31 +14703,13 @@ class WalkerUtils {
13995
14703
  context,
13996
14704
  });
13997
14705
  const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
13998
- let isStopped = false;
13999
- let isDone = false;
14000
- const task = async () => {
14001
- for await (const _ of this.run(symbol, context)) {
14002
- if (isStopped) {
14003
- break;
14004
- }
14005
- }
14006
- if (!isDone) {
14007
- await doneWalkerSubject.next({
14008
- exchangeName: walkerSchema.exchangeName,
14009
- strategyName: context.walkerName,
14010
- backtest: true,
14011
- symbol,
14012
- });
14013
- }
14014
- isDone = true;
14015
- };
14016
- task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14706
+ this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14017
14707
  return () => {
14018
14708
  for (const strategyName of walkerSchema.strategies) {
14019
- backtest$1.strategyGlobalService.stop({ symbol, strategyName });
14020
- walkerStopSubject.next({ symbol, strategyName });
14709
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
14710
+ walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
14021
14711
  }
14022
- if (!isDone) {
14712
+ if (!this._isDone) {
14023
14713
  doneWalkerSubject.next({
14024
14714
  exchangeName: walkerSchema.exchangeName,
14025
14715
  strategyName: context.walkerName,
@@ -14027,10 +14717,44 @@ class WalkerUtils {
14027
14717
  symbol,
14028
14718
  });
14029
14719
  }
14030
- isDone = true;
14031
- isStopped = true;
14720
+ this._isDone = true;
14721
+ this._isStopped = true;
14032
14722
  };
14033
14723
  };
14724
+ /**
14725
+ * Stops all strategies in the walker from generating new signals.
14726
+ *
14727
+ * Iterates through all strategies defined in walker schema and:
14728
+ * 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
14729
+ * 2. Sets internal stop flag for each strategy (prevents new signals)
14730
+ *
14731
+ * Current active signals (if any) will complete normally.
14732
+ * Walker will stop at the next safe point.
14733
+ *
14734
+ * Supports multiple walkers running on the same symbol simultaneously.
14735
+ * Stop signal is filtered by walkerName to prevent interference.
14736
+ *
14737
+ * @param symbol - Trading pair symbol
14738
+ * @param walkerName - Walker name to stop
14739
+ * @returns Promise that resolves when all stop flags are set
14740
+ *
14741
+ * @example
14742
+ * ```typescript
14743
+ * const instance = new WalkerInstance();
14744
+ * await instance.stop("BTCUSDT", "my-walker");
14745
+ * ```
14746
+ */
14747
+ this.stop = async (symbol, walkerName) => {
14748
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_STOP, {
14749
+ symbol,
14750
+ walkerName,
14751
+ });
14752
+ const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
14753
+ for (const strategyName of walkerSchema.strategies) {
14754
+ await walkerStopSubject.next({ symbol, strategyName, walkerName });
14755
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
14756
+ }
14757
+ };
14034
14758
  /**
14035
14759
  * Gets walker results data from all strategy comparisons.
14036
14760
  *
@@ -14040,7 +14764,8 @@ class WalkerUtils {
14040
14764
  *
14041
14765
  * @example
14042
14766
  * ```typescript
14043
- * const results = await Walker.getData("BTCUSDT", "my-walker");
14767
+ * const instance = new WalkerInstance();
14768
+ * const results = await instance.getData("BTCUSDT", "my-walker");
14044
14769
  * console.log(results.bestStrategy, results.bestMetric);
14045
14770
  * ```
14046
14771
  */
@@ -14064,7 +14789,8 @@ class WalkerUtils {
14064
14789
  *
14065
14790
  * @example
14066
14791
  * ```typescript
14067
- * const markdown = await Walker.getReport("BTCUSDT", "my-walker");
14792
+ * const instance = new WalkerInstance();
14793
+ * const markdown = await instance.getReport("BTCUSDT", "my-walker");
14068
14794
  * console.log(markdown);
14069
14795
  * ```
14070
14796
  */
@@ -14088,11 +14814,12 @@ class WalkerUtils {
14088
14814
  *
14089
14815
  * @example
14090
14816
  * ```typescript
14817
+ * const instance = new WalkerInstance();
14091
14818
  * // Save to default path: ./dump/walker/my-walker.md
14092
- * await Walker.dump("BTCUSDT", "my-walker");
14819
+ * await instance.dump("BTCUSDT", "my-walker");
14093
14820
  *
14094
14821
  * // Save to custom path: ./custom/path/my-walker.md
14095
- * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
14822
+ * await instance.dump("BTCUSDT", "my-walker", "./custom/path");
14096
14823
  * ```
14097
14824
  */
14098
14825
  this.dump = async (symbol, walkerName, path) => {
@@ -14109,6 +14836,166 @@ class WalkerUtils {
14109
14836
  };
14110
14837
  }
14111
14838
  }
14839
+ /**
14840
+ * Utility class for walker operations.
14841
+ *
14842
+ * Provides simplified access to walkerCommandService.run() with logging.
14843
+ * Automatically pulls exchangeName and frameName from walker schema.
14844
+ * Exported as singleton instance for convenient usage.
14845
+ *
14846
+ * @example
14847
+ * ```typescript
14848
+ * import { Walker } from "./classes/Walker";
14849
+ *
14850
+ * for await (const result of Walker.run("BTCUSDT", {
14851
+ * walkerName: "my-walker"
14852
+ * })) {
14853
+ * console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
14854
+ * console.log("Best strategy:", result.bestStrategy, result.bestMetric);
14855
+ * }
14856
+ * ```
14857
+ */
14858
+ class WalkerUtils {
14859
+ constructor() {
14860
+ /**
14861
+ * Memoized function to get or create WalkerInstance for a symbol-walker pair.
14862
+ * Each symbol-walker combination gets its own isolated instance.
14863
+ */
14864
+ this._getInstance = functoolsKit.memoize(([symbol, walkerName]) => `${symbol}:${walkerName}`, (symbol, walkerName) => new WalkerInstance(symbol, walkerName));
14865
+ /**
14866
+ * Runs walker comparison for a symbol with context propagation.
14867
+ *
14868
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14869
+ * @param context - Execution context with walker name
14870
+ * @returns Async generator yielding progress updates after each strategy
14871
+ */
14872
+ this.run = (symbol, context) => {
14873
+ const instance = this._getInstance(symbol, context.walkerName);
14874
+ return instance.run(symbol, context);
14875
+ };
14876
+ /**
14877
+ * Runs walker comparison in background without yielding results.
14878
+ *
14879
+ * Consumes all walker progress updates internally without exposing them.
14880
+ * Useful for running walker comparison for side effects only (callbacks, logging).
14881
+ *
14882
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
14883
+ * @param context - Execution context with walker name
14884
+ * @returns Cancellation closure
14885
+ *
14886
+ * @example
14887
+ * ```typescript
14888
+ * // Run walker silently, only callbacks will fire
14889
+ * await Walker.background("BTCUSDT", {
14890
+ * walkerName: "my-walker"
14891
+ * });
14892
+ * console.log("Walker comparison completed");
14893
+ * ```
14894
+ */
14895
+ this.background = (symbol, context) => {
14896
+ const instance = this._getInstance(symbol, context.walkerName);
14897
+ return instance.background(symbol, context);
14898
+ };
14899
+ /**
14900
+ * Stops all strategies in the walker from generating new signals.
14901
+ *
14902
+ * Iterates through all strategies defined in walker schema and:
14903
+ * 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
14904
+ * 2. Sets internal stop flag for each strategy (prevents new signals)
14905
+ *
14906
+ * Current active signals (if any) will complete normally.
14907
+ * Walker will stop at the next safe point.
14908
+ *
14909
+ * Supports multiple walkers running on the same symbol simultaneously.
14910
+ * Stop signal is filtered by walkerName to prevent interference.
14911
+ *
14912
+ * @param symbol - Trading pair symbol
14913
+ * @param walkerName - Walker name to stop
14914
+ * @returns Promise that resolves when all stop flags are set
14915
+ *
14916
+ * @example
14917
+ * ```typescript
14918
+ * // Stop walker and all its strategies
14919
+ * await Walker.stop("BTCUSDT", "my-walker");
14920
+ * ```
14921
+ */
14922
+ this.stop = async (symbol, walkerName) => {
14923
+ const instance = this._getInstance(symbol, walkerName);
14924
+ return await instance.stop(symbol, walkerName);
14925
+ };
14926
+ /**
14927
+ * Gets walker results data from all strategy comparisons.
14928
+ *
14929
+ * @param symbol - Trading symbol
14930
+ * @param walkerName - Walker name to get data for
14931
+ * @returns Promise resolving to walker results data object
14932
+ *
14933
+ * @example
14934
+ * ```typescript
14935
+ * const results = await Walker.getData("BTCUSDT", "my-walker");
14936
+ * console.log(results.bestStrategy, results.bestMetric);
14937
+ * ```
14938
+ */
14939
+ this.getData = async (symbol, walkerName) => {
14940
+ const instance = this._getInstance(symbol, walkerName);
14941
+ return await instance.getData(symbol, walkerName);
14942
+ };
14943
+ /**
14944
+ * Generates markdown report with all strategy comparisons for a walker.
14945
+ *
14946
+ * @param symbol - Trading symbol
14947
+ * @param walkerName - Walker name to generate report for
14948
+ * @returns Promise resolving to markdown formatted report string
14949
+ *
14950
+ * @example
14951
+ * ```typescript
14952
+ * const markdown = await Walker.getReport("BTCUSDT", "my-walker");
14953
+ * console.log(markdown);
14954
+ * ```
14955
+ */
14956
+ this.getReport = async (symbol, walkerName) => {
14957
+ const instance = this._getInstance(symbol, walkerName);
14958
+ return await instance.getReport(symbol, walkerName);
14959
+ };
14960
+ /**
14961
+ * Saves walker report to disk.
14962
+ *
14963
+ * @param symbol - Trading symbol
14964
+ * @param walkerName - Walker name to save report for
14965
+ * @param path - Optional directory path to save report (default: "./dump/walker")
14966
+ *
14967
+ * @example
14968
+ * ```typescript
14969
+ * // Save to default path: ./dump/walker/my-walker.md
14970
+ * await Walker.dump("BTCUSDT", "my-walker");
14971
+ *
14972
+ * // Save to custom path: ./custom/path/my-walker.md
14973
+ * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
14974
+ * ```
14975
+ */
14976
+ this.dump = async (symbol, walkerName, path) => {
14977
+ const instance = this._getInstance(symbol, walkerName);
14978
+ return await instance.dump(symbol, walkerName, path);
14979
+ };
14980
+ /**
14981
+ * Lists all active walker instances with their current status.
14982
+ *
14983
+ * @returns Promise resolving to array of status objects for all instances
14984
+ *
14985
+ * @example
14986
+ * ```typescript
14987
+ * const statusList = await Walker.list();
14988
+ * statusList.forEach(status => {
14989
+ * console.log(`${status.symbol} - ${status.walkerName}: ${status.status}`);
14990
+ * });
14991
+ * ```
14992
+ */
14993
+ this.list = async () => {
14994
+ const instanceList = this._getInstance.values();
14995
+ return await Promise.all(instanceList.map((instance) => instance.getStatus()));
14996
+ };
14997
+ }
14998
+ }
14112
14999
  /**
14113
15000
  * Singleton instance of WalkerUtils for convenient walker operations.
14114
15001
  *