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