backtest-kit 1.5.2 → 1.5.3

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 } 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;
3163
3183
  }
3184
+ this._scheduledSignal = null;
3185
+ if (backtest) {
3186
+ return;
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,6 +13397,7 @@ 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";
13273
13403
  /**
@@ -13362,7 +13492,7 @@ class BacktestUtils {
13362
13492
  };
13363
13493
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
13364
13494
  return () => {
13365
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
13495
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13366
13496
  backtest$1.strategyGlobalService
13367
13497
  .getPendingSignal(symbol, context.strategyName)
13368
13498
  .then(async (pendingSignal) => {
@@ -13382,6 +13512,30 @@ class BacktestUtils {
13382
13512
  isStopped = true;
13383
13513
  };
13384
13514
  };
13515
+ /**
13516
+ * Stops the strategy from generating new signals.
13517
+ *
13518
+ * Sets internal flag to prevent strategy from opening new signals.
13519
+ * Current active signal (if any) will complete normally.
13520
+ * Backtest will stop at the next safe point (idle state or after signal closes).
13521
+ *
13522
+ * @param symbol - Trading pair symbol
13523
+ * @param strategyName - Strategy name to stop
13524
+ * @returns Promise that resolves when stop flag is set
13525
+ *
13526
+ * @example
13527
+ * ```typescript
13528
+ * // Stop strategy after some condition
13529
+ * await Backtest.stop("BTCUSDT", "my-strategy");
13530
+ * ```
13531
+ */
13532
+ this.stop = async (symbol, strategyName) => {
13533
+ backtest$1.loggerService.info(BACKTEST_METHOD_NAME_STOP, {
13534
+ symbol,
13535
+ strategyName,
13536
+ });
13537
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13538
+ };
13385
13539
  /**
13386
13540
  * Gets statistical data from all closed signals for a symbol-strategy pair.
13387
13541
  *
@@ -13470,6 +13624,7 @@ const Backtest = new BacktestUtils();
13470
13624
 
13471
13625
  const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
13472
13626
  const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
13627
+ const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
13473
13628
  const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
13474
13629
  const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
13475
13630
  /**
@@ -13577,7 +13732,7 @@ class LiveUtils {
13577
13732
  };
13578
13733
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
13579
13734
  return () => {
13580
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
13735
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
13581
13736
  backtest$1.strategyGlobalService
13582
13737
  .getPendingSignal(symbol, context.strategyName)
13583
13738
  .then(async (pendingSignal) => {
@@ -13597,6 +13752,30 @@ class LiveUtils {
13597
13752
  isStopped = true;
13598
13753
  };
13599
13754
  };
13755
+ /**
13756
+ * Stops the strategy from generating new signals.
13757
+ *
13758
+ * Sets internal flag to prevent strategy from opening new signals.
13759
+ * Current active signal (if any) will complete normally.
13760
+ * Live trading will stop at the next safe point (idle/closed state).
13761
+ *
13762
+ * @param symbol - Trading pair symbol
13763
+ * @param strategyName - Strategy name to stop
13764
+ * @returns Promise that resolves when stop flag is set
13765
+ *
13766
+ * @example
13767
+ * ```typescript
13768
+ * // Stop live trading gracefully
13769
+ * await Live.stop("BTCUSDT", "my-strategy");
13770
+ * ```
13771
+ */
13772
+ this.stop = async (symbol, strategyName) => {
13773
+ backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
13774
+ symbol,
13775
+ strategyName,
13776
+ });
13777
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
13778
+ };
13600
13779
  /**
13601
13780
  * Gets statistical data from all live trading events for a symbol-strategy pair.
13602
13781
  *
@@ -13904,6 +14083,7 @@ class Performance {
13904
14083
 
13905
14084
  const WALKER_METHOD_NAME_RUN = "WalkerUtils.run";
13906
14085
  const WALKER_METHOD_NAME_BACKGROUND = "WalkerUtils.background";
14086
+ const WALKER_METHOD_NAME_STOP = "WalkerUtils.stop";
13907
14087
  const WALKER_METHOD_NAME_GET_DATA = "WalkerUtils.getData";
13908
14088
  const WALKER_METHOD_NAME_GET_REPORT = "WalkerUtils.getReport";
13909
14089
  const WALKER_METHOD_NAME_DUMP = "WalkerUtils.dump";
@@ -14014,8 +14194,8 @@ class WalkerUtils {
14014
14194
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
14015
14195
  return () => {
14016
14196
  for (const strategyName of walkerSchema.strategies) {
14017
- backtest$1.strategyGlobalService.stop({ symbol, strategyName });
14018
- walkerStopSubject.next({ symbol, strategyName });
14197
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
14198
+ walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
14019
14199
  }
14020
14200
  if (!isDone) {
14021
14201
  doneWalkerSubject.next({
@@ -14029,6 +14209,40 @@ class WalkerUtils {
14029
14209
  isStopped = true;
14030
14210
  };
14031
14211
  };
14212
+ /**
14213
+ * Stops all strategies in the walker from generating new signals.
14214
+ *
14215
+ * Iterates through all strategies defined in walker schema and:
14216
+ * 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
14217
+ * 2. Sets internal stop flag for each strategy (prevents new signals)
14218
+ *
14219
+ * Current active signals (if any) will complete normally.
14220
+ * Walker will stop at the next safe point.
14221
+ *
14222
+ * Supports multiple walkers running on the same symbol simultaneously.
14223
+ * Stop signal is filtered by walkerName to prevent interference.
14224
+ *
14225
+ * @param symbol - Trading pair symbol
14226
+ * @param walkerName - Walker name to stop
14227
+ * @returns Promise that resolves when all stop flags are set
14228
+ *
14229
+ * @example
14230
+ * ```typescript
14231
+ * // Stop walker and all its strategies
14232
+ * await Walker.stop("BTCUSDT", "my-walker");
14233
+ * ```
14234
+ */
14235
+ this.stop = async (symbol, walkerName) => {
14236
+ backtest$1.loggerService.info(WALKER_METHOD_NAME_STOP, {
14237
+ symbol,
14238
+ walkerName,
14239
+ });
14240
+ const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
14241
+ for (const strategyName of walkerSchema.strategies) {
14242
+ await walkerStopSubject.next({ symbol, strategyName, walkerName });
14243
+ await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
14244
+ }
14245
+ };
14032
14246
  /**
14033
14247
  * Gets walker results data from all strategy comparisons.
14034
14248
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",