backtest-kit 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -1430,6 +1430,11 @@ const walkerEmitter = new functoolsKit.Subject();
1430
1430
  * Emits when all strategies have been tested and final results are available.
1431
1431
  */
1432
1432
  const walkerCompleteSubject = new functoolsKit.Subject();
1433
+ /**
1434
+ * Walker stop emitter for walker cancellation events.
1435
+ * Emits when a walker comparison is stopped/cancelled.
1436
+ */
1437
+ const walkerStopSubject = new functoolsKit.Subject();
1433
1438
  /**
1434
1439
  * Validation emitter for risk validation errors.
1435
1440
  * Emits when risk validation functions throw errors during signal checking.
@@ -1449,7 +1454,8 @@ var emitters = /*#__PURE__*/Object.freeze({
1449
1454
  signalLiveEmitter: signalLiveEmitter,
1450
1455
  validationSubject: validationSubject,
1451
1456
  walkerCompleteSubject: walkerCompleteSubject,
1452
- walkerEmitter: walkerEmitter
1457
+ walkerEmitter: walkerEmitter,
1458
+ walkerStopSubject: walkerStopSubject
1453
1459
  });
1454
1460
 
1455
1461
  const INTERVAL_MINUTES$1 = {
@@ -2671,11 +2677,14 @@ class StrategyConnectionService {
2671
2677
  * Retrieves the currently active pending signal for the strategy.
2672
2678
  * If no active signal exists, returns null.
2673
2679
  * Used internally for monitoring TP/SL and time expiration.
2680
+ *
2681
+ * @param strategyName - Name of strategy to get pending signal for
2682
+ *
2674
2683
  * @returns Promise resolving to pending signal or null
2675
2684
  */
2676
- this.getPendingSignal = async () => {
2685
+ this.getPendingSignal = async (strategyName) => {
2677
2686
  this.loggerService.log("strategyConnectionService getPendingSignal");
2678
- const strategy = await this.getStrategy(this.methodContextService.context.strategyName);
2687
+ const strategy = await this.getStrategy(strategyName);
2679
2688
  return await strategy.getPendingSignal();
2680
2689
  };
2681
2690
  /**
@@ -3590,20 +3599,12 @@ class StrategyGlobalService {
3590
3599
  * @param backtest - Whether running in backtest mode
3591
3600
  * @returns Promise resolving to pending signal or null
3592
3601
  */
3593
- this.getPendingSignal = async (symbol, when, backtest) => {
3602
+ this.getPendingSignal = async (strategyName) => {
3594
3603
  this.loggerService.log("strategyGlobalService getPendingSignal", {
3595
- symbol,
3596
- when,
3597
- backtest,
3604
+ strategyName,
3598
3605
  });
3599
3606
  await this.validate(this.methodContextService.context.strategyName);
3600
- return await ExecutionContextService.runInContext(async () => {
3601
- return await this.strategyConnectionService.getPendingSignal();
3602
- }, {
3603
- symbol,
3604
- when,
3605
- backtest,
3606
- });
3607
+ return await this.strategyConnectionService.getPendingSignal(strategyName);
3607
3608
  };
3608
3609
  /**
3609
3610
  * Checks signal status at a specific timestamp.
@@ -4607,6 +4608,7 @@ class LiveLogicPrivateService {
4607
4608
  }
4608
4609
  }
4609
4610
 
4611
+ const CANCEL_SYMBOL = Symbol("CANCEL_SYMBOL");
4610
4612
  /**
4611
4613
  * Private service for walker orchestration (strategy comparison).
4612
4614
  *
@@ -4664,6 +4666,10 @@ class WalkerLogicPrivateService {
4664
4666
  let strategiesTested = 0;
4665
4667
  let bestMetric = null;
4666
4668
  let bestStrategy = null;
4669
+ const listenStop = walkerStopSubject
4670
+ .filter((walkerName) => walkerName === context.walkerName)
4671
+ .map(() => CANCEL_SYMBOL)
4672
+ .toPromise();
4667
4673
  // Run backtest for each strategy
4668
4674
  for (const strategyName of strategies) {
4669
4675
  // Call onStrategyStart callback if provided
@@ -4679,7 +4685,16 @@ class WalkerLogicPrivateService {
4679
4685
  exchangeName: context.exchangeName,
4680
4686
  frameName: context.frameName,
4681
4687
  });
4682
- await functoolsKit.resolveDocuments(iterator);
4688
+ const result = await Promise.race([
4689
+ await functoolsKit.resolveDocuments(iterator),
4690
+ listenStop,
4691
+ ]);
4692
+ if (result === CANCEL_SYMBOL) {
4693
+ this.loggerService.info("walkerLogicPrivateService received stop signal, cancelling walker", {
4694
+ context,
4695
+ });
4696
+ break;
4697
+ }
4683
4698
  this.loggerService.info("walkerLogicPrivateService backtest complete", {
4684
4699
  strategyName,
4685
4700
  symbol,
@@ -9343,22 +9358,42 @@ class BacktestUtils {
9343
9358
  context,
9344
9359
  });
9345
9360
  let isStopped = false;
9361
+ let isDone = false;
9346
9362
  const task = async () => {
9347
9363
  for await (const _ of this.run(symbol, context)) {
9348
9364
  if (isStopped) {
9349
9365
  break;
9350
9366
  }
9351
9367
  }
9352
- await doneBacktestSubject.next({
9353
- exchangeName: context.exchangeName,
9354
- strategyName: context.strategyName,
9355
- backtest: true,
9356
- symbol,
9357
- });
9368
+ if (!isDone) {
9369
+ await doneBacktestSubject.next({
9370
+ exchangeName: context.exchangeName,
9371
+ strategyName: context.strategyName,
9372
+ backtest: true,
9373
+ symbol,
9374
+ });
9375
+ }
9376
+ isDone = true;
9358
9377
  };
9359
9378
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
9360
9379
  return () => {
9361
9380
  backtest$1.strategyGlobalService.stop(context.strategyName);
9381
+ backtest$1.strategyGlobalService
9382
+ .getPendingSignal(context.strategyName)
9383
+ .then(async (pendingSignal) => {
9384
+ if (pendingSignal) {
9385
+ return;
9386
+ }
9387
+ if (!isDone) {
9388
+ await doneBacktestSubject.next({
9389
+ exchangeName: context.exchangeName,
9390
+ strategyName: context.strategyName,
9391
+ backtest: true,
9392
+ symbol,
9393
+ });
9394
+ }
9395
+ isDone = true;
9396
+ });
9362
9397
  isStopped = true;
9363
9398
  };
9364
9399
  };
@@ -9553,8 +9588,11 @@ class LiveUtils {
9553
9588
  return () => {
9554
9589
  backtest$1.strategyGlobalService.stop(context.strategyName);
9555
9590
  backtest$1.strategyGlobalService
9556
- .getPendingSignal(symbol, new Date(), false)
9557
- .then(async () => {
9591
+ .getPendingSignal(context.strategyName)
9592
+ .then(async (pendingSignal) => {
9593
+ if (pendingSignal) {
9594
+ return;
9595
+ }
9558
9596
  if (!isDone) {
9559
9597
  await doneLiveSubject.next({
9560
9598
  exchangeName: context.exchangeName,
@@ -9990,25 +10028,39 @@ class WalkerUtils {
9990
10028
  });
9991
10029
  const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
9992
10030
  let isStopped = false;
10031
+ let isDone = false;
9993
10032
  const task = async () => {
9994
10033
  for await (const _ of this.run(symbol, context)) {
9995
10034
  if (isStopped) {
9996
10035
  break;
9997
10036
  }
9998
10037
  }
9999
- await doneWalkerSubject.next({
10000
- exchangeName: walkerSchema.exchangeName,
10001
- strategyName: context.walkerName,
10002
- backtest: true,
10003
- symbol,
10004
- });
10038
+ if (!isDone) {
10039
+ await doneWalkerSubject.next({
10040
+ exchangeName: walkerSchema.exchangeName,
10041
+ strategyName: context.walkerName,
10042
+ backtest: true,
10043
+ symbol,
10044
+ });
10045
+ }
10046
+ isDone = true;
10005
10047
  };
10006
10048
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
10007
10049
  return () => {
10008
- isStopped = true;
10009
10050
  for (const strategyName of walkerSchema.strategies) {
10010
10051
  backtest$1.strategyGlobalService.stop(strategyName);
10011
10052
  }
10053
+ walkerStopSubject.next(context.walkerName);
10054
+ if (!isDone) {
10055
+ doneWalkerSubject.next({
10056
+ exchangeName: walkerSchema.exchangeName,
10057
+ strategyName: context.walkerName,
10058
+ backtest: true,
10059
+ symbol,
10060
+ });
10061
+ }
10062
+ isDone = true;
10063
+ isStopped = true;
10012
10064
  };
10013
10065
  };
10014
10066
  /**
package/build/index.mjs CHANGED
@@ -1428,6 +1428,11 @@ const walkerEmitter = new Subject();
1428
1428
  * Emits when all strategies have been tested and final results are available.
1429
1429
  */
1430
1430
  const walkerCompleteSubject = new Subject();
1431
+ /**
1432
+ * Walker stop emitter for walker cancellation events.
1433
+ * Emits when a walker comparison is stopped/cancelled.
1434
+ */
1435
+ const walkerStopSubject = new Subject();
1431
1436
  /**
1432
1437
  * Validation emitter for risk validation errors.
1433
1438
  * Emits when risk validation functions throw errors during signal checking.
@@ -1447,7 +1452,8 @@ var emitters = /*#__PURE__*/Object.freeze({
1447
1452
  signalLiveEmitter: signalLiveEmitter,
1448
1453
  validationSubject: validationSubject,
1449
1454
  walkerCompleteSubject: walkerCompleteSubject,
1450
- walkerEmitter: walkerEmitter
1455
+ walkerEmitter: walkerEmitter,
1456
+ walkerStopSubject: walkerStopSubject
1451
1457
  });
1452
1458
 
1453
1459
  const INTERVAL_MINUTES$1 = {
@@ -2669,11 +2675,14 @@ class StrategyConnectionService {
2669
2675
  * Retrieves the currently active pending signal for the strategy.
2670
2676
  * If no active signal exists, returns null.
2671
2677
  * Used internally for monitoring TP/SL and time expiration.
2678
+ *
2679
+ * @param strategyName - Name of strategy to get pending signal for
2680
+ *
2672
2681
  * @returns Promise resolving to pending signal or null
2673
2682
  */
2674
- this.getPendingSignal = async () => {
2683
+ this.getPendingSignal = async (strategyName) => {
2675
2684
  this.loggerService.log("strategyConnectionService getPendingSignal");
2676
- const strategy = await this.getStrategy(this.methodContextService.context.strategyName);
2685
+ const strategy = await this.getStrategy(strategyName);
2677
2686
  return await strategy.getPendingSignal();
2678
2687
  };
2679
2688
  /**
@@ -3588,20 +3597,12 @@ class StrategyGlobalService {
3588
3597
  * @param backtest - Whether running in backtest mode
3589
3598
  * @returns Promise resolving to pending signal or null
3590
3599
  */
3591
- this.getPendingSignal = async (symbol, when, backtest) => {
3600
+ this.getPendingSignal = async (strategyName) => {
3592
3601
  this.loggerService.log("strategyGlobalService getPendingSignal", {
3593
- symbol,
3594
- when,
3595
- backtest,
3602
+ strategyName,
3596
3603
  });
3597
3604
  await this.validate(this.methodContextService.context.strategyName);
3598
- return await ExecutionContextService.runInContext(async () => {
3599
- return await this.strategyConnectionService.getPendingSignal();
3600
- }, {
3601
- symbol,
3602
- when,
3603
- backtest,
3604
- });
3605
+ return await this.strategyConnectionService.getPendingSignal(strategyName);
3605
3606
  };
3606
3607
  /**
3607
3608
  * Checks signal status at a specific timestamp.
@@ -4605,6 +4606,7 @@ class LiveLogicPrivateService {
4605
4606
  }
4606
4607
  }
4607
4608
 
4609
+ const CANCEL_SYMBOL = Symbol("CANCEL_SYMBOL");
4608
4610
  /**
4609
4611
  * Private service for walker orchestration (strategy comparison).
4610
4612
  *
@@ -4662,6 +4664,10 @@ class WalkerLogicPrivateService {
4662
4664
  let strategiesTested = 0;
4663
4665
  let bestMetric = null;
4664
4666
  let bestStrategy = null;
4667
+ const listenStop = walkerStopSubject
4668
+ .filter((walkerName) => walkerName === context.walkerName)
4669
+ .map(() => CANCEL_SYMBOL)
4670
+ .toPromise();
4665
4671
  // Run backtest for each strategy
4666
4672
  for (const strategyName of strategies) {
4667
4673
  // Call onStrategyStart callback if provided
@@ -4677,7 +4683,16 @@ class WalkerLogicPrivateService {
4677
4683
  exchangeName: context.exchangeName,
4678
4684
  frameName: context.frameName,
4679
4685
  });
4680
- await resolveDocuments(iterator);
4686
+ const result = await Promise.race([
4687
+ await resolveDocuments(iterator),
4688
+ listenStop,
4689
+ ]);
4690
+ if (result === CANCEL_SYMBOL) {
4691
+ this.loggerService.info("walkerLogicPrivateService received stop signal, cancelling walker", {
4692
+ context,
4693
+ });
4694
+ break;
4695
+ }
4681
4696
  this.loggerService.info("walkerLogicPrivateService backtest complete", {
4682
4697
  strategyName,
4683
4698
  symbol,
@@ -9341,22 +9356,42 @@ class BacktestUtils {
9341
9356
  context,
9342
9357
  });
9343
9358
  let isStopped = false;
9359
+ let isDone = false;
9344
9360
  const task = async () => {
9345
9361
  for await (const _ of this.run(symbol, context)) {
9346
9362
  if (isStopped) {
9347
9363
  break;
9348
9364
  }
9349
9365
  }
9350
- await doneBacktestSubject.next({
9351
- exchangeName: context.exchangeName,
9352
- strategyName: context.strategyName,
9353
- backtest: true,
9354
- symbol,
9355
- });
9366
+ if (!isDone) {
9367
+ await doneBacktestSubject.next({
9368
+ exchangeName: context.exchangeName,
9369
+ strategyName: context.strategyName,
9370
+ backtest: true,
9371
+ symbol,
9372
+ });
9373
+ }
9374
+ isDone = true;
9356
9375
  };
9357
9376
  task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
9358
9377
  return () => {
9359
9378
  backtest$1.strategyGlobalService.stop(context.strategyName);
9379
+ backtest$1.strategyGlobalService
9380
+ .getPendingSignal(context.strategyName)
9381
+ .then(async (pendingSignal) => {
9382
+ if (pendingSignal) {
9383
+ return;
9384
+ }
9385
+ if (!isDone) {
9386
+ await doneBacktestSubject.next({
9387
+ exchangeName: context.exchangeName,
9388
+ strategyName: context.strategyName,
9389
+ backtest: true,
9390
+ symbol,
9391
+ });
9392
+ }
9393
+ isDone = true;
9394
+ });
9360
9395
  isStopped = true;
9361
9396
  };
9362
9397
  };
@@ -9551,8 +9586,11 @@ class LiveUtils {
9551
9586
  return () => {
9552
9587
  backtest$1.strategyGlobalService.stop(context.strategyName);
9553
9588
  backtest$1.strategyGlobalService
9554
- .getPendingSignal(symbol, new Date(), false)
9555
- .then(async () => {
9589
+ .getPendingSignal(context.strategyName)
9590
+ .then(async (pendingSignal) => {
9591
+ if (pendingSignal) {
9592
+ return;
9593
+ }
9556
9594
  if (!isDone) {
9557
9595
  await doneLiveSubject.next({
9558
9596
  exchangeName: context.exchangeName,
@@ -9988,25 +10026,39 @@ class WalkerUtils {
9988
10026
  });
9989
10027
  const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
9990
10028
  let isStopped = false;
10029
+ let isDone = false;
9991
10030
  const task = async () => {
9992
10031
  for await (const _ of this.run(symbol, context)) {
9993
10032
  if (isStopped) {
9994
10033
  break;
9995
10034
  }
9996
10035
  }
9997
- await doneWalkerSubject.next({
9998
- exchangeName: walkerSchema.exchangeName,
9999
- strategyName: context.walkerName,
10000
- backtest: true,
10001
- symbol,
10002
- });
10036
+ if (!isDone) {
10037
+ await doneWalkerSubject.next({
10038
+ exchangeName: walkerSchema.exchangeName,
10039
+ strategyName: context.walkerName,
10040
+ backtest: true,
10041
+ symbol,
10042
+ });
10043
+ }
10044
+ isDone = true;
10003
10045
  };
10004
10046
  task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
10005
10047
  return () => {
10006
- isStopped = true;
10007
10048
  for (const strategyName of walkerSchema.strategies) {
10008
10049
  backtest$1.strategyGlobalService.stop(strategyName);
10009
10050
  }
10051
+ walkerStopSubject.next(context.walkerName);
10052
+ if (!isDone) {
10053
+ doneWalkerSubject.next({
10054
+ exchangeName: walkerSchema.exchangeName,
10055
+ strategyName: context.walkerName,
10056
+ backtest: true,
10057
+ symbol,
10058
+ });
10059
+ }
10060
+ isDone = true;
10061
+ isStopped = true;
10010
10062
  };
10011
10063
  };
10012
10064
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -4446,6 +4446,11 @@ declare const walkerEmitter: Subject<WalkerContract>;
4446
4446
  * Emits when all strategies have been tested and final results are available.
4447
4447
  */
4448
4448
  declare const walkerCompleteSubject: Subject<IWalkerResults>;
4449
+ /**
4450
+ * Walker stop emitter for walker cancellation events.
4451
+ * Emits when a walker comparison is stopped/cancelled.
4452
+ */
4453
+ declare const walkerStopSubject: Subject<string>;
4449
4454
  /**
4450
4455
  * Validation emitter for risk validation errors.
4451
4456
  * Emits when risk validation functions throw errors during signal checking.
@@ -4464,8 +4469,9 @@ declare const emitters_signalLiveEmitter: typeof signalLiveEmitter;
4464
4469
  declare const emitters_validationSubject: typeof validationSubject;
4465
4470
  declare const emitters_walkerCompleteSubject: typeof walkerCompleteSubject;
4466
4471
  declare const emitters_walkerEmitter: typeof walkerEmitter;
4472
+ declare const emitters_walkerStopSubject: typeof walkerStopSubject;
4467
4473
  declare namespace emitters {
4468
- export { emitters_doneBacktestSubject as doneBacktestSubject, emitters_doneLiveSubject as doneLiveSubject, emitters_doneWalkerSubject as doneWalkerSubject, emitters_errorEmitter as errorEmitter, emitters_performanceEmitter as performanceEmitter, emitters_progressEmitter as progressEmitter, emitters_signalBacktestEmitter as signalBacktestEmitter, emitters_signalEmitter as signalEmitter, emitters_signalLiveEmitter as signalLiveEmitter, emitters_validationSubject as validationSubject, emitters_walkerCompleteSubject as walkerCompleteSubject, emitters_walkerEmitter as walkerEmitter };
4474
+ export { emitters_doneBacktestSubject as doneBacktestSubject, emitters_doneLiveSubject as doneLiveSubject, emitters_doneWalkerSubject as doneWalkerSubject, emitters_errorEmitter as errorEmitter, emitters_performanceEmitter as performanceEmitter, emitters_progressEmitter as progressEmitter, emitters_signalBacktestEmitter as signalBacktestEmitter, emitters_signalEmitter as signalEmitter, emitters_signalLiveEmitter as signalLiveEmitter, emitters_validationSubject as validationSubject, emitters_walkerCompleteSubject as walkerCompleteSubject, emitters_walkerEmitter as walkerEmitter, emitters_walkerStopSubject as walkerStopSubject };
4469
4475
  }
4470
4476
 
4471
4477
  /**
@@ -4731,9 +4737,12 @@ declare class StrategyConnectionService implements IStrategy {
4731
4737
  * Retrieves the currently active pending signal for the strategy.
4732
4738
  * If no active signal exists, returns null.
4733
4739
  * Used internally for monitoring TP/SL and time expiration.
4740
+ *
4741
+ * @param strategyName - Name of strategy to get pending signal for
4742
+ *
4734
4743
  * @returns Promise resolving to pending signal or null
4735
4744
  */
4736
- getPendingSignal: () => Promise<ISignalRow | null>;
4745
+ getPendingSignal: (strategyName: StrategyName) => Promise<ISignalRow | null>;
4737
4746
  /**
4738
4747
  * Executes live trading tick for current strategy.
4739
4748
  *
@@ -5191,7 +5200,7 @@ declare class StrategyGlobalService {
5191
5200
  * @param backtest - Whether running in backtest mode
5192
5201
  * @returns Promise resolving to pending signal or null
5193
5202
  */
5194
- getPendingSignal: (symbol: string, when: Date, backtest: boolean) => Promise<ISignalRow | null>;
5203
+ getPendingSignal: (strategyName: StrategyName) => Promise<ISignalRow | null>;
5195
5204
  /**
5196
5205
  * Checks signal status at a specific timestamp.
5197
5206
  *