backtest-kit 6.13.0 → 6.15.0

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.
Files changed (4) hide show
  1. package/build/index.cjs +1024 -132
  2. package/build/index.mjs +1020 -133
  3. package/package.json +2 -2
  4. package/types.d.ts +641 -10
package/build/index.cjs CHANGED
@@ -147,6 +147,9 @@ const reportServices$1 = {
147
147
  highestProfitReportService: Symbol('highestProfitReportService'),
148
148
  maxDrawdownReportService: Symbol('maxDrawdownReportService'),
149
149
  };
150
+ const helperServices$1 = {
151
+ notificationHelperService: Symbol('notificationHelperService'),
152
+ };
150
153
  const validationServices$1 = {
151
154
  exchangeValidationService: Symbol('exchangeValidationService'),
152
155
  strategyValidationService: Symbol('strategyValidationService'),
@@ -172,6 +175,7 @@ const TYPES = {
172
175
  ...markdownServices$1,
173
176
  ...reportServices$1,
174
177
  ...validationServices$1,
178
+ ...helperServices$1,
175
179
  };
176
180
 
177
181
  /**
@@ -764,6 +768,11 @@ const highestProfitSubject = new functoolsKit.Subject();
764
768
  * Allows users to track drawdown levels and implement custom risk management logic based on drawdown thresholds.
765
769
  */
766
770
  const maxDrawdownSubject = new functoolsKit.Subject();
771
+ /**
772
+ * Signal info emitter for user-defined informational notes on open positions.
773
+ * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
774
+ */
775
+ const signalNotifySubject = new functoolsKit.Subject();
767
776
 
768
777
  var emitters = /*#__PURE__*/Object.freeze({
769
778
  __proto__: null,
@@ -788,6 +797,7 @@ var emitters = /*#__PURE__*/Object.freeze({
788
797
  signalBacktestEmitter: signalBacktestEmitter,
789
798
  signalEmitter: signalEmitter,
790
799
  signalLiveEmitter: signalLiveEmitter,
800
+ signalNotifySubject: signalNotifySubject,
791
801
  strategyCommitSubject: strategyCommitSubject,
792
802
  syncSubject: syncSubject,
793
803
  validationSubject: validationSubject,
@@ -7973,6 +7983,40 @@ class ClientStrategy {
7973
7983
  }
7974
7984
  return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
7975
7985
  }
7986
+ /**
7987
+ * Returns the number of minutes the position has been active since it opened.
7988
+ *
7989
+ * Computed as elapsed minutes since `pendingAt` (the moment the signal was activated).
7990
+ * Returns null if no pending signal exists.
7991
+ *
7992
+ * @param symbol - Trading pair symbol
7993
+ * @param timestamp - Current Unix timestamp in milliseconds
7994
+ * @returns Promise resolving to active minutes (≥ 0) or null
7995
+ */
7996
+ async getPositionActiveMinutes(symbol, timestamp) {
7997
+ this.params.logger.debug("ClientStrategy getPositionActiveMinutes", { symbol });
7998
+ if (!this._pendingSignal) {
7999
+ return null;
8000
+ }
8001
+ return Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
8002
+ }
8003
+ /**
8004
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
8005
+ *
8006
+ * Computed as elapsed minutes since `scheduledAt` (the moment the scheduled signal was created).
8007
+ * Returns null if no scheduled signal exists.
8008
+ *
8009
+ * @param symbol - Trading pair symbol
8010
+ * @param timestamp - Current Unix timestamp in milliseconds
8011
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
8012
+ */
8013
+ async getPositionWaitingMinutes(symbol, timestamp) {
8014
+ this.params.logger.debug("ClientStrategy getPositionWaitingMinutes", { symbol });
8015
+ if (!this._scheduledSignal) {
8016
+ return null;
8017
+ }
8018
+ return Math.floor((timestamp - this._scheduledSignal.scheduledAt) / 60000);
8019
+ }
7976
8020
  /**
7977
8021
  * Returns the number of minutes elapsed since the highest profit price was recorded.
7978
8022
  *
@@ -10290,7 +10334,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10290
10334
  * @param backtest - Whether running in backtest mode
10291
10335
  * @returns Unique string key for memoization
10292
10336
  */
10293
- const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
10337
+ const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10294
10338
  const parts = [symbol, strategyName, exchangeName];
10295
10339
  if (frameName)
10296
10340
  parts.push(frameName);
@@ -10557,7 +10601,7 @@ class StrategyConnectionService {
10557
10601
  * @param backtest - Whether running in backtest mode
10558
10602
  * @returns Configured ClientStrategy instance
10559
10603
  */
10560
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10604
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10561
10605
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10562
10606
  return new ClientStrategy({
10563
10607
  symbol,
@@ -11074,6 +11118,46 @@ class StrategyConnectionService {
11074
11118
  const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11075
11119
  return await strategy.getPositionCountdownMinutes(symbol, timestamp);
11076
11120
  };
11121
+ /**
11122
+ * Returns the number of minutes the position has been active since it opened.
11123
+ *
11124
+ * Delegates to ClientStrategy.getPositionActiveMinutes().
11125
+ * Returns null if no pending signal exists.
11126
+ *
11127
+ * @param backtest - Whether running in backtest mode
11128
+ * @param symbol - Trading pair symbol
11129
+ * @param context - Execution context with strategyName, exchangeName, frameName
11130
+ * @returns Promise resolving to active minutes (≥ 0) or null
11131
+ */
11132
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
11133
+ this.loggerService.log("strategyConnectionService getPositionActiveMinutes", {
11134
+ symbol,
11135
+ context,
11136
+ });
11137
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11138
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11139
+ return await strategy.getPositionActiveMinutes(symbol, timestamp);
11140
+ };
11141
+ /**
11142
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
11143
+ *
11144
+ * Delegates to ClientStrategy.getPositionWaitingMinutes().
11145
+ * Returns null if no scheduled signal exists.
11146
+ *
11147
+ * @param backtest - Whether running in backtest mode
11148
+ * @param symbol - Trading pair symbol
11149
+ * @param context - Execution context with strategyName, exchangeName, frameName
11150
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
11151
+ */
11152
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
11153
+ this.loggerService.log("strategyConnectionService getPositionWaitingMinutes", {
11154
+ symbol,
11155
+ context,
11156
+ });
11157
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11158
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11159
+ return await strategy.getPositionWaitingMinutes(symbol, timestamp);
11160
+ };
11077
11161
  /**
11078
11162
  * Returns the best price reached in the profit direction during this position's life.
11079
11163
  *
@@ -11478,7 +11562,7 @@ class StrategyConnectionService {
11478
11562
  }
11479
11563
  return;
11480
11564
  }
11481
- const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11565
+ const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11482
11566
  if (!this.getStrategy.has(key)) {
11483
11567
  return;
11484
11568
  }
@@ -12647,7 +12731,7 @@ class ClientRisk {
12647
12731
  * @param backtest - Whether running in backtest mode
12648
12732
  * @returns Unique string key for memoization
12649
12733
  */
12650
- const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
12734
+ const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
12651
12735
  const parts = [riskName, exchangeName];
12652
12736
  if (frameName)
12653
12737
  parts.push(frameName);
@@ -12747,7 +12831,7 @@ class RiskConnectionService {
12747
12831
  * @param backtest - True if backtest mode, false if live mode
12748
12832
  * @returns Configured ClientRisk instance
12749
12833
  */
12750
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12834
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12751
12835
  const schema = this.riskSchemaService.get(riskName);
12752
12836
  return new ClientRisk({
12753
12837
  ...schema,
@@ -12816,7 +12900,7 @@ class RiskConnectionService {
12816
12900
  payload,
12817
12901
  });
12818
12902
  if (payload) {
12819
- const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12903
+ const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12820
12904
  this.getRisk.clear(key);
12821
12905
  }
12822
12906
  else {
@@ -13860,7 +13944,7 @@ class ClientAction {
13860
13944
  * @param backtest - Whether running in backtest mode
13861
13945
  * @returns Unique string key for memoization
13862
13946
  */
13863
- const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
13947
+ const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
13864
13948
  const parts = [actionName, strategyName, exchangeName];
13865
13949
  if (frameName)
13866
13950
  parts.push(frameName);
@@ -13912,7 +13996,7 @@ class ActionConnectionService {
13912
13996
  * @param backtest - True if backtest mode, false if live mode
13913
13997
  * @returns Configured ClientAction instance
13914
13998
  */
13915
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13999
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13916
14000
  const schema = this.actionSchemaService.get(actionName);
13917
14001
  return new ClientAction({
13918
14002
  ...schema,
@@ -14123,7 +14207,7 @@ class ActionConnectionService {
14123
14207
  await Promise.all(actions.map(async (action) => await action.dispose()));
14124
14208
  return;
14125
14209
  }
14126
- const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14210
+ const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14127
14211
  if (!this.getAction.has(key)) {
14128
14212
  return;
14129
14213
  }
@@ -14134,14 +14218,14 @@ class ActionConnectionService {
14134
14218
  }
14135
14219
  }
14136
14220
 
14137
- const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
14221
+ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14138
14222
  /**
14139
14223
  * Creates a unique key for memoizing validate calls.
14140
14224
  * Key format: "exchangeName"
14141
14225
  * @param exchangeName - Exchange name
14142
14226
  * @returns Unique string key for memoization
14143
14227
  */
14144
- const CREATE_KEY_FN$r = (exchangeName) => {
14228
+ const CREATE_KEY_FN$s = (exchangeName) => {
14145
14229
  return exchangeName;
14146
14230
  };
14147
14231
  /**
@@ -14165,11 +14249,11 @@ class ExchangeCoreService {
14165
14249
  * @param exchangeName - Name of the exchange to validate
14166
14250
  * @returns Promise that resolves when validation is complete
14167
14251
  */
14168
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
14169
- this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14252
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14253
+ this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14170
14254
  exchangeName,
14171
14255
  });
14172
- this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$2);
14256
+ this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$3);
14173
14257
  });
14174
14258
  /**
14175
14259
  * Fetches historical candles with execution context.
@@ -14410,14 +14494,14 @@ class ExchangeCoreService {
14410
14494
  }
14411
14495
  }
14412
14496
 
14413
- const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14497
+ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14414
14498
  /**
14415
14499
  * Creates a unique key for memoizing validate calls.
14416
14500
  * Key format: "strategyName:exchangeName:frameName"
14417
14501
  * @param context - Execution context with strategyName, exchangeName, frameName
14418
14502
  * @returns Unique string key for memoization
14419
14503
  */
14420
- const CREATE_KEY_FN$q = (context) => {
14504
+ const CREATE_KEY_FN$r = (context) => {
14421
14505
  const parts = [context.strategyName, context.exchangeName];
14422
14506
  if (context.frameName)
14423
14507
  parts.push(context.frameName);
@@ -14449,16 +14533,16 @@ class StrategyCoreService {
14449
14533
  * @param context - Execution context with strategyName, exchangeName, frameName
14450
14534
  * @returns Promise that resolves when validation is complete
14451
14535
  */
14452
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
14453
- this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14536
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14537
+ this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14454
14538
  context,
14455
14539
  });
14456
14540
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
14457
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
14458
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
14459
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
14460
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
14461
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
14541
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$2);
14542
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$2);
14543
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$2);
14544
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2);
14545
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2));
14462
14546
  });
14463
14547
  /**
14464
14548
  * Retrieves the currently active pending signal for the symbol.
@@ -15394,6 +15478,38 @@ class StrategyCoreService {
15394
15478
  await this.validate(context);
15395
15479
  return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
15396
15480
  };
15481
+ /**
15482
+ * Returns the number of minutes the position has been active since it opened.
15483
+ *
15484
+ * @param backtest - Whether running in backtest mode
15485
+ * @param symbol - Trading pair symbol
15486
+ * @param context - Execution context with strategyName, exchangeName, frameName
15487
+ * @returns Promise resolving to active minutes (≥ 0) or null
15488
+ */
15489
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
15490
+ this.loggerService.log("strategyCoreService getPositionActiveMinutes", {
15491
+ symbol,
15492
+ context,
15493
+ });
15494
+ await this.validate(context);
15495
+ return await this.strategyConnectionService.getPositionActiveMinutes(backtest, symbol, context);
15496
+ };
15497
+ /**
15498
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
15499
+ *
15500
+ * @param backtest - Whether running in backtest mode
15501
+ * @param symbol - Trading pair symbol
15502
+ * @param context - Execution context with strategyName, exchangeName, frameName
15503
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
15504
+ */
15505
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
15506
+ this.loggerService.log("strategyCoreService getPositionWaitingMinutes", {
15507
+ symbol,
15508
+ context,
15509
+ });
15510
+ await this.validate(context);
15511
+ return await this.strategyConnectionService.getPositionWaitingMinutes(backtest, symbol, context);
15512
+ };
15397
15513
  /**
15398
15514
  * Returns the best price reached in the profit direction during this position's life.
15399
15515
  *
@@ -15787,7 +15903,7 @@ class SizingGlobalService {
15787
15903
  * @param context - Context with riskName, exchangeName, frameName
15788
15904
  * @returns Unique string key for memoization
15789
15905
  */
15790
- const CREATE_KEY_FN$p = (context) => {
15906
+ const CREATE_KEY_FN$q = (context) => {
15791
15907
  const parts = [context.riskName, context.exchangeName];
15792
15908
  if (context.frameName)
15793
15909
  parts.push(context.frameName);
@@ -15813,7 +15929,7 @@ class RiskGlobalService {
15813
15929
  * @param payload - Payload with riskName, exchangeName and frameName
15814
15930
  * @returns Promise that resolves when validation is complete
15815
15931
  */
15816
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15932
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
15817
15933
  this.loggerService.log("riskGlobalService validate", {
15818
15934
  context,
15819
15935
  });
@@ -15884,14 +16000,14 @@ class RiskGlobalService {
15884
16000
  }
15885
16001
  }
15886
16002
 
15887
- const METHOD_NAME_VALIDATE = "actionCoreService validate";
16003
+ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
15888
16004
  /**
15889
16005
  * Creates a unique key for memoizing validate calls.
15890
16006
  * Key format: "strategyName:exchangeName:frameName"
15891
16007
  * @param context - Execution context with strategyName, exchangeName, frameName
15892
16008
  * @returns Unique string key for memoization
15893
16009
  */
15894
- const CREATE_KEY_FN$o = (context) => {
16010
+ const CREATE_KEY_FN$p = (context) => {
15895
16011
  const parts = [context.strategyName, context.exchangeName];
15896
16012
  if (context.frameName)
15897
16013
  parts.push(context.frameName);
@@ -15935,17 +16051,17 @@ class ActionCoreService {
15935
16051
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15936
16052
  * @returns Promise that resolves when all validations complete
15937
16053
  */
15938
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15939
- this.loggerService.log(METHOD_NAME_VALIDATE, {
16054
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
16055
+ this.loggerService.log(METHOD_NAME_VALIDATE$1, {
15940
16056
  context,
15941
16057
  });
15942
16058
  const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
15943
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
15944
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
15945
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
15946
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
15947
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
15948
- actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
16059
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
16060
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
16061
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
16062
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
16063
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
16064
+ actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$1));
15949
16065
  });
15950
16066
  /**
15951
16067
  * Initializes all ClientAction instances for the strategy.
@@ -20978,7 +21094,7 @@ const ReportWriter = new ReportWriterAdapter();
20978
21094
  * @param backtest - Whether running in backtest mode
20979
21095
  * @returns Unique string key for memoization
20980
21096
  */
20981
- const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21097
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
20982
21098
  const parts = [symbol, strategyName, exchangeName];
20983
21099
  if (frameName)
20984
21100
  parts.push(frameName);
@@ -21224,7 +21340,7 @@ class BacktestMarkdownService {
21224
21340
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21225
21341
  * Each combination gets its own isolated storage instance.
21226
21342
  */
21227
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21343
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21228
21344
  /**
21229
21345
  * Processes tick events and accumulates closed signals.
21230
21346
  * Should be called from IStrategyCallbacks.onTick.
@@ -21381,7 +21497,7 @@ class BacktestMarkdownService {
21381
21497
  payload,
21382
21498
  });
21383
21499
  if (payload) {
21384
- const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21500
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21385
21501
  this.getStorage.clear(key);
21386
21502
  }
21387
21503
  else {
@@ -21443,7 +21559,7 @@ class BacktestMarkdownService {
21443
21559
  * @param backtest - Whether running in backtest mode
21444
21560
  * @returns Unique string key for memoization
21445
21561
  */
21446
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
21562
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21447
21563
  const parts = [symbol, strategyName, exchangeName];
21448
21564
  if (frameName)
21449
21565
  parts.push(frameName);
@@ -21938,7 +22054,7 @@ class LiveMarkdownService {
21938
22054
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21939
22055
  * Each combination gets its own isolated storage instance.
21940
22056
  */
21941
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22057
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21942
22058
  /**
21943
22059
  * Subscribes to live signal emitter to receive tick events.
21944
22060
  * Protected against multiple subscriptions.
@@ -22156,7 +22272,7 @@ class LiveMarkdownService {
22156
22272
  payload,
22157
22273
  });
22158
22274
  if (payload) {
22159
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22275
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22160
22276
  this.getStorage.clear(key);
22161
22277
  }
22162
22278
  else {
@@ -22176,7 +22292,7 @@ class LiveMarkdownService {
22176
22292
  * @param backtest - Whether running in backtest mode
22177
22293
  * @returns Unique string key for memoization
22178
22294
  */
22179
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22295
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22180
22296
  const parts = [symbol, strategyName, exchangeName];
22181
22297
  if (frameName)
22182
22298
  parts.push(frameName);
@@ -22465,7 +22581,7 @@ class ScheduleMarkdownService {
22465
22581
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22466
22582
  * Each combination gets its own isolated storage instance.
22467
22583
  */
22468
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22584
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22469
22585
  /**
22470
22586
  * Subscribes to signal emitter to receive scheduled signal events.
22471
22587
  * Protected against multiple subscriptions.
@@ -22668,7 +22784,7 @@ class ScheduleMarkdownService {
22668
22784
  payload,
22669
22785
  });
22670
22786
  if (payload) {
22671
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22787
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22672
22788
  this.getStorage.clear(key);
22673
22789
  }
22674
22790
  else {
@@ -22688,7 +22804,7 @@ class ScheduleMarkdownService {
22688
22804
  * @param backtest - Whether running in backtest mode
22689
22805
  * @returns Unique string key for memoization
22690
22806
  */
22691
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22807
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22692
22808
  const parts = [symbol, strategyName, exchangeName];
22693
22809
  if (frameName)
22694
22810
  parts.push(frameName);
@@ -22933,7 +23049,7 @@ class PerformanceMarkdownService {
22933
23049
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22934
23050
  * Each combination gets its own isolated storage instance.
22935
23051
  */
22936
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23052
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22937
23053
  /**
22938
23054
  * Subscribes to performance emitter to receive performance events.
22939
23055
  * Protected against multiple subscriptions.
@@ -23100,7 +23216,7 @@ class PerformanceMarkdownService {
23100
23216
  payload,
23101
23217
  });
23102
23218
  if (payload) {
23103
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23219
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23104
23220
  this.getStorage.clear(key);
23105
23221
  }
23106
23222
  else {
@@ -23579,7 +23695,7 @@ class WalkerMarkdownService {
23579
23695
  * @param backtest - Whether running in backtest mode
23580
23696
  * @returns Unique string key for memoization
23581
23697
  */
23582
- const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
23698
+ const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
23583
23699
  const parts = [exchangeName];
23584
23700
  if (frameName)
23585
23701
  parts.push(frameName);
@@ -24026,7 +24142,7 @@ class HeatMarkdownService {
24026
24142
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
24027
24143
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
24028
24144
  */
24029
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24145
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24030
24146
  /**
24031
24147
  * Subscribes to signal emitter to receive tick events.
24032
24148
  * Protected against multiple subscriptions.
@@ -24244,7 +24360,7 @@ class HeatMarkdownService {
24244
24360
  payload,
24245
24361
  });
24246
24362
  if (payload) {
24247
- const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
24363
+ const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24248
24364
  this.getStorage.clear(key);
24249
24365
  }
24250
24366
  else {
@@ -25275,7 +25391,7 @@ class ClientPartial {
25275
25391
  * @param backtest - Whether running in backtest mode
25276
25392
  * @returns Unique string key for memoization
25277
25393
  */
25278
- const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25394
+ const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25279
25395
  /**
25280
25396
  * Creates a callback function for emitting profit events to partialProfitSubject.
25281
25397
  *
@@ -25397,7 +25513,7 @@ class PartialConnectionService {
25397
25513
  * Key format: "signalId:backtest" or "signalId:live"
25398
25514
  * Value: ClientPartial instance with logger and event emitters
25399
25515
  */
25400
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
25516
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25401
25517
  return new ClientPartial({
25402
25518
  signalId,
25403
25519
  logger: this.loggerService,
@@ -25487,7 +25603,7 @@ class PartialConnectionService {
25487
25603
  const partial = this.getPartial(data.id, backtest);
25488
25604
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25489
25605
  await partial.clear(symbol, data, priceClose, backtest);
25490
- const key = CREATE_KEY_FN$i(data.id, backtest);
25606
+ const key = CREATE_KEY_FN$j(data.id, backtest);
25491
25607
  this.getPartial.clear(key);
25492
25608
  };
25493
25609
  }
@@ -25503,7 +25619,7 @@ class PartialConnectionService {
25503
25619
  * @param backtest - Whether running in backtest mode
25504
25620
  * @returns Unique string key for memoization
25505
25621
  */
25506
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
25622
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
25507
25623
  const parts = [symbol, strategyName, exchangeName];
25508
25624
  if (frameName)
25509
25625
  parts.push(frameName);
@@ -25726,7 +25842,7 @@ class PartialMarkdownService {
25726
25842
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25727
25843
  * Each combination gets its own isolated storage instance.
25728
25844
  */
25729
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25845
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25730
25846
  /**
25731
25847
  * Subscribes to partial profit/loss signal emitters to receive events.
25732
25848
  * Protected against multiple subscriptions.
@@ -25936,7 +26052,7 @@ class PartialMarkdownService {
25936
26052
  payload,
25937
26053
  });
25938
26054
  if (payload) {
25939
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26055
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25940
26056
  this.getStorage.clear(key);
25941
26057
  }
25942
26058
  else {
@@ -25952,7 +26068,7 @@ class PartialMarkdownService {
25952
26068
  * @param context - Context with strategyName, exchangeName, frameName
25953
26069
  * @returns Unique string key for memoization
25954
26070
  */
25955
- const CREATE_KEY_FN$g = (context) => {
26071
+ const CREATE_KEY_FN$h = (context) => {
25956
26072
  const parts = [context.strategyName, context.exchangeName];
25957
26073
  if (context.frameName)
25958
26074
  parts.push(context.frameName);
@@ -26026,7 +26142,7 @@ class PartialGlobalService {
26026
26142
  * @param context - Context with strategyName, exchangeName and frameName
26027
26143
  * @param methodName - Name of the calling method for error tracking
26028
26144
  */
26029
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
26145
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
26030
26146
  this.loggerService.log("partialGlobalService validate", {
26031
26147
  context,
26032
26148
  methodName,
@@ -26481,7 +26597,7 @@ class ClientBreakeven {
26481
26597
  * @param backtest - Whether running in backtest mode
26482
26598
  * @returns Unique string key for memoization
26483
26599
  */
26484
- const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26600
+ const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26485
26601
  /**
26486
26602
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26487
26603
  *
@@ -26567,7 +26683,7 @@ class BreakevenConnectionService {
26567
26683
  * Key format: "signalId:backtest" or "signalId:live"
26568
26684
  * Value: ClientBreakeven instance with logger and event emitter
26569
26685
  */
26570
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
26686
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
26571
26687
  return new ClientBreakeven({
26572
26688
  signalId,
26573
26689
  logger: this.loggerService,
@@ -26628,7 +26744,7 @@ class BreakevenConnectionService {
26628
26744
  const breakeven = this.getBreakeven(data.id, backtest);
26629
26745
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26630
26746
  await breakeven.clear(symbol, data, priceClose, backtest);
26631
- const key = CREATE_KEY_FN$f(data.id, backtest);
26747
+ const key = CREATE_KEY_FN$g(data.id, backtest);
26632
26748
  this.getBreakeven.clear(key);
26633
26749
  };
26634
26750
  }
@@ -26644,7 +26760,7 @@ class BreakevenConnectionService {
26644
26760
  * @param backtest - Whether running in backtest mode
26645
26761
  * @returns Unique string key for memoization
26646
26762
  */
26647
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
26763
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
26648
26764
  const parts = [symbol, strategyName, exchangeName];
26649
26765
  if (frameName)
26650
26766
  parts.push(frameName);
@@ -26819,7 +26935,7 @@ class BreakevenMarkdownService {
26819
26935
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26820
26936
  * Each combination gets its own isolated storage instance.
26821
26937
  */
26822
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26938
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26823
26939
  /**
26824
26940
  * Subscribes to breakeven signal emitter to receive events.
26825
26941
  * Protected against multiple subscriptions.
@@ -27008,7 +27124,7 @@ class BreakevenMarkdownService {
27008
27124
  payload,
27009
27125
  });
27010
27126
  if (payload) {
27011
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27127
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27012
27128
  this.getStorage.clear(key);
27013
27129
  }
27014
27130
  else {
@@ -27024,7 +27140,7 @@ class BreakevenMarkdownService {
27024
27140
  * @param context - Context with strategyName, exchangeName, frameName
27025
27141
  * @returns Unique string key for memoization
27026
27142
  */
27027
- const CREATE_KEY_FN$d = (context) => {
27143
+ const CREATE_KEY_FN$e = (context) => {
27028
27144
  const parts = [context.strategyName, context.exchangeName];
27029
27145
  if (context.frameName)
27030
27146
  parts.push(context.frameName);
@@ -27098,7 +27214,7 @@ class BreakevenGlobalService {
27098
27214
  * @param context - Context with strategyName, exchangeName and frameName
27099
27215
  * @param methodName - Name of the calling method for error tracking
27100
27216
  */
27101
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
27217
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
27102
27218
  this.loggerService.log("breakevenGlobalService validate", {
27103
27219
  context,
27104
27220
  methodName,
@@ -27319,7 +27435,7 @@ class ConfigValidationService {
27319
27435
  * @param backtest - Whether running in backtest mode
27320
27436
  * @returns Unique string key for memoization
27321
27437
  */
27322
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
27438
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27323
27439
  const parts = [symbol, strategyName, exchangeName];
27324
27440
  if (frameName)
27325
27441
  parts.push(frameName);
@@ -27486,7 +27602,7 @@ class RiskMarkdownService {
27486
27602
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27487
27603
  * Each combination gets its own isolated storage instance.
27488
27604
  */
27489
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27605
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27490
27606
  /**
27491
27607
  * Subscribes to risk rejection emitter to receive rejection events.
27492
27608
  * Protected against multiple subscriptions.
@@ -27675,7 +27791,7 @@ class RiskMarkdownService {
27675
27791
  payload,
27676
27792
  });
27677
27793
  if (payload) {
27678
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27794
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27679
27795
  this.getStorage.clear(key);
27680
27796
  }
27681
27797
  else {
@@ -30057,7 +30173,7 @@ class HighestProfitReportService {
30057
30173
  * @returns Colon-separated key string for memoization
30058
30174
  * @internal
30059
30175
  */
30060
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30176
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
30061
30177
  const parts = [symbol, strategyName, exchangeName];
30062
30178
  if (frameName)
30063
30179
  parts.push(frameName);
@@ -30299,7 +30415,7 @@ class StrategyMarkdownService {
30299
30415
  *
30300
30416
  * @internal
30301
30417
  */
30302
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30418
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30303
30419
  /**
30304
30420
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30305
30421
  *
@@ -30873,7 +30989,7 @@ class StrategyMarkdownService {
30873
30989
  this.clear = async (payload) => {
30874
30990
  this.loggerService.log("strategyMarkdownService clear", { payload });
30875
30991
  if (payload) {
30876
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30992
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30877
30993
  this.getStorage.clear(key);
30878
30994
  }
30879
30995
  else {
@@ -30981,7 +31097,7 @@ class StrategyMarkdownService {
30981
31097
  * Creates a unique key for memoizing ReportStorage instances.
30982
31098
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30983
31099
  */
30984
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31100
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30985
31101
  const parts = [symbol, strategyName, exchangeName];
30986
31102
  if (frameName)
30987
31103
  parts.push(frameName);
@@ -31174,7 +31290,7 @@ let ReportStorage$2 = class ReportStorage {
31174
31290
  class SyncMarkdownService {
31175
31291
  constructor() {
31176
31292
  this.loggerService = inject(TYPES.loggerService);
31177
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31293
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31178
31294
  /**
31179
31295
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31180
31296
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31370,7 +31486,7 @@ class SyncMarkdownService {
31370
31486
  this.clear = async (payload) => {
31371
31487
  this.loggerService.log("syncMarkdownService clear", { payload });
31372
31488
  if (payload) {
31373
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31489
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31374
31490
  this.getStorage.clear(key);
31375
31491
  }
31376
31492
  else {
@@ -31383,7 +31499,7 @@ class SyncMarkdownService {
31383
31499
  /**
31384
31500
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31385
31501
  */
31386
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31502
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31387
31503
  const parts = [symbol, strategyName, exchangeName];
31388
31504
  if (frameName)
31389
31505
  parts.push(frameName);
@@ -31559,7 +31675,7 @@ let ReportStorage$1 = class ReportStorage {
31559
31675
  class HighestProfitMarkdownService {
31560
31676
  constructor() {
31561
31677
  this.loggerService = inject(TYPES.loggerService);
31562
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31678
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31563
31679
  /**
31564
31680
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31565
31681
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31725,7 +31841,7 @@ class HighestProfitMarkdownService {
31725
31841
  this.clear = async (payload) => {
31726
31842
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31727
31843
  if (payload) {
31728
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31844
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31729
31845
  this.getStorage.clear(key);
31730
31846
  }
31731
31847
  else {
@@ -31747,7 +31863,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31747
31863
  * @param backtest - Whether running in backtest mode
31748
31864
  * @returns Unique string key for memoization
31749
31865
  */
31750
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31866
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31751
31867
  const parts = [symbol, strategyName, exchangeName];
31752
31868
  if (frameName)
31753
31869
  parts.push(frameName);
@@ -31790,7 +31906,7 @@ class PriceMetaService {
31790
31906
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31791
31907
  * Instances are cached until clear() is called.
31792
31908
  */
31793
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31909
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31794
31910
  /**
31795
31911
  * Returns the current market price for the given symbol and context.
31796
31912
  *
@@ -31819,10 +31935,10 @@ class PriceMetaService {
31819
31935
  if (source.data) {
31820
31936
  return source.data;
31821
31937
  }
31822
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31938
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31823
31939
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31824
31940
  if (typeof currentPrice === "symbol") {
31825
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31941
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31826
31942
  }
31827
31943
  return currentPrice;
31828
31944
  };
@@ -31864,7 +31980,7 @@ class PriceMetaService {
31864
31980
  this.getSource.clear();
31865
31981
  return;
31866
31982
  }
31867
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31983
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31868
31984
  this.getSource.clear(key);
31869
31985
  };
31870
31986
  }
@@ -31882,7 +31998,7 @@ const LISTEN_TIMEOUT = 120000;
31882
31998
  * @param backtest - Whether running in backtest mode
31883
31999
  * @returns Unique string key for memoization
31884
32000
  */
31885
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32001
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31886
32002
  const parts = [symbol, strategyName, exchangeName];
31887
32003
  if (frameName)
31888
32004
  parts.push(frameName);
@@ -31925,7 +32041,7 @@ class TimeMetaService {
31925
32041
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31926
32042
  * Instances are cached until clear() is called.
31927
32043
  */
31928
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32044
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31929
32045
  /**
31930
32046
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31931
32047
  *
@@ -31953,10 +32069,10 @@ class TimeMetaService {
31953
32069
  if (source.data) {
31954
32070
  return source.data;
31955
32071
  }
31956
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32072
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31957
32073
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31958
32074
  if (typeof timestamp === "symbol") {
31959
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32075
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31960
32076
  }
31961
32077
  return timestamp;
31962
32078
  };
@@ -31998,7 +32114,7 @@ class TimeMetaService {
31998
32114
  this.getSource.clear();
31999
32115
  return;
32000
32116
  }
32001
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32117
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32002
32118
  this.getSource.clear(key);
32003
32119
  };
32004
32120
  }
@@ -32094,7 +32210,7 @@ class MaxDrawdownReportService {
32094
32210
  /**
32095
32211
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
32096
32212
  */
32097
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32213
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32098
32214
  const parts = [symbol, strategyName, exchangeName];
32099
32215
  if (frameName)
32100
32216
  parts.push(frameName);
@@ -32218,7 +32334,7 @@ class ReportStorage {
32218
32334
  class MaxDrawdownMarkdownService {
32219
32335
  constructor() {
32220
32336
  this.loggerService = inject(TYPES.loggerService);
32221
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32337
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32222
32338
  /**
32223
32339
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32224
32340
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32297,7 +32413,7 @@ class MaxDrawdownMarkdownService {
32297
32413
  this.clear = async (payload) => {
32298
32414
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32299
32415
  if (payload) {
32300
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32416
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32301
32417
  this.getStorage.clear(key);
32302
32418
  }
32303
32419
  else {
@@ -32307,6 +32423,125 @@ class MaxDrawdownMarkdownService {
32307
32423
  }
32308
32424
  }
32309
32425
 
32426
+ const METHOD_NAME_COMMIT_SIGNAL_NOTIFY = "notificationHelperService.commitSignalNotify";
32427
+ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32428
+ /**
32429
+ * Creates a unique key for memoizing validate calls.
32430
+ * Key format: "strategyName:exchangeName:frameName"
32431
+ * @param context - Execution context with strategyName, exchangeName, frameName
32432
+ * @returns Unique string key for memoization
32433
+ */
32434
+ const CREATE_KEY_FN$6 = (context) => {
32435
+ const parts = [context.strategyName, context.exchangeName];
32436
+ if (context.frameName)
32437
+ parts.push(context.frameName);
32438
+ return parts.join(":");
32439
+ };
32440
+ /**
32441
+ * Helper service for emitting signal info notifications.
32442
+ *
32443
+ * Handles validation (memoized per context) and emission of `signal.info` events
32444
+ * via `signalNotifySubject`. Used internally by the framework action pipeline —
32445
+ * end users interact with this via `commitSignalNotify()` in `onActivePing` callbacks.
32446
+ */
32447
+ class NotificationHelperService {
32448
+ constructor() {
32449
+ this.loggerService = inject(TYPES.loggerService);
32450
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
32451
+ this.riskValidationService = inject(TYPES.riskValidationService);
32452
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
32453
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
32454
+ this.frameValidationService = inject(TYPES.frameValidationService);
32455
+ this.actionValidationService = inject(TYPES.actionValidationService);
32456
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
32457
+ this.timeMetaService = inject(TYPES.timeMetaService);
32458
+ /**
32459
+ * Validates strategy, exchange, frame, risk, and action schemas for the given context.
32460
+ *
32461
+ * Memoized per unique `"strategyName:exchangeName[:frameName]"` key — subsequent calls
32462
+ * with the same context are no-ops, so validation runs at most once per context.
32463
+ *
32464
+ * @param context - Routing context: strategyName, exchangeName, frameName
32465
+ * @throws {Error} If any registered schema fails validation
32466
+ */
32467
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
32468
+ this.loggerService.log(METHOD_NAME_VALIDATE, {
32469
+ context,
32470
+ });
32471
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
32472
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
32473
+ const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
32474
+ context.frameName &&
32475
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
32476
+ riskName &&
32477
+ this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
32478
+ riskList &&
32479
+ riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
32480
+ actions &&
32481
+ actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
32482
+ });
32483
+ /**
32484
+ * Emits a `signal.info` notification for the currently active pending signal.
32485
+ *
32486
+ * Validates all schemas (via memoized `validate`), resolves the pending signal
32487
+ * for the given symbol, then emits a `SignalInfoContract` via `signalNotifySubject`,
32488
+ * which is routed to all registered `listenSignalNotify` callbacks and persisted
32489
+ * by `NotificationAdapter`.
32490
+ *
32491
+ * @param payload - Optional notification fields (notificationId, notificationNote)
32492
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
32493
+ * @param currentPrice - Market price at the time of the call
32494
+ * @param context - Routing context: strategyName, exchangeName, frameName
32495
+ * @param backtest - true when called during a backtest run
32496
+ *
32497
+ * @throws {Error} If no active pending signal is found for the given symbol
32498
+ *
32499
+ * @example
32500
+ * ```typescript
32501
+ * // Inside onActivePing callback:
32502
+ * await commitSignalNotify("BTCUSDT", {
32503
+ * notificationNote: "RSI crossed 70, consider closing",
32504
+ * notificationId: "msg-123",
32505
+ * });
32506
+ * ```
32507
+ */
32508
+ this.commitSignalNotify = async (payload, symbol, currentPrice, context, backtest) => {
32509
+ this.loggerService.info(METHOD_NAME_COMMIT_SIGNAL_NOTIFY, {
32510
+ symbol,
32511
+ context,
32512
+ backtest,
32513
+ currentPrice,
32514
+ });
32515
+ this.validate(context);
32516
+ const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32517
+ strategyName: context.strategyName,
32518
+ exchangeName: context.exchangeName,
32519
+ frameName: context.frameName,
32520
+ });
32521
+ if (!pendingSignal) {
32522
+ throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
32523
+ }
32524
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, {
32525
+ strategyName: context.strategyName,
32526
+ exchangeName: context.exchangeName,
32527
+ frameName: context.frameName,
32528
+ }, backtest);
32529
+ await signalNotifySubject.next({
32530
+ backtest,
32531
+ symbol,
32532
+ currentPrice,
32533
+ data: pendingSignal,
32534
+ exchangeName: context.exchangeName,
32535
+ strategyName: context.strategyName,
32536
+ frameName: context.frameName,
32537
+ note: payload.notificationNote || pendingSignal.note,
32538
+ notificationId: payload.notificationId,
32539
+ timestamp,
32540
+ });
32541
+ };
32542
+ }
32543
+ }
32544
+
32310
32545
  {
32311
32546
  provide(TYPES.loggerService, () => new LoggerService());
32312
32547
  }
@@ -32395,6 +32630,9 @@ class MaxDrawdownMarkdownService {
32395
32630
  provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
32396
32631
  provide(TYPES.maxDrawdownReportService, () => new MaxDrawdownReportService());
32397
32632
  }
32633
+ {
32634
+ provide(TYPES.notificationHelperService, () => new NotificationHelperService());
32635
+ }
32398
32636
  {
32399
32637
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
32400
32638
  provide(TYPES.strategyValidationService, () => new StrategyValidationService());
@@ -32495,6 +32733,9 @@ const reportServices = {
32495
32733
  highestProfitReportService: inject(TYPES.highestProfitReportService),
32496
32734
  maxDrawdownReportService: inject(TYPES.maxDrawdownReportService),
32497
32735
  };
32736
+ const helperServices = {
32737
+ notificationHelperService: inject(TYPES.notificationHelperService),
32738
+ };
32498
32739
  const validationServices = {
32499
32740
  exchangeValidationService: inject(TYPES.exchangeValidationService),
32500
32741
  strategyValidationService: inject(TYPES.strategyValidationService),
@@ -32520,6 +32761,7 @@ const backtest = {
32520
32761
  ...markdownServices,
32521
32762
  ...reportServices,
32522
32763
  ...validationServices,
32764
+ ...helperServices,
32523
32765
  };
32524
32766
  init();
32525
32767
 
@@ -35414,6 +35656,8 @@ const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
35414
35656
  const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
35415
35657
  const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
35416
35658
  const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
35659
+ const GET_POSITION_ACTIVE_MINUTES_METHOD_NAME = "strategy.getPositionActiveMinutes";
35660
+ const GET_POSITION_WAITING_MINUTES_METHOD_NAME = "strategy.getPositionWaitingMinutes";
35417
35661
  const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
35418
35662
  const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
35419
35663
  const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
@@ -35436,6 +35680,7 @@ const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap
35436
35680
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35437
35681
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
35438
35682
  const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35683
+ const COMMIT_SIGNAL_NOTIFY_METHOD_NAME = "strategy.commitSignalNotify";
35439
35684
  /**
35440
35685
  * Cancels the scheduled signal without stopping the strategy.
35441
35686
  *
@@ -36713,6 +36958,62 @@ async function getPositionCountdownMinutes(symbol) {
36713
36958
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36714
36959
  return await backtest.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36715
36960
  }
36961
+ /**
36962
+ * Returns the number of minutes the position has been active since it opened.
36963
+ *
36964
+ * Returns null if no pending signal exists.
36965
+ *
36966
+ * @param symbol - Trading pair symbol
36967
+ * @returns Promise resolving to active minutes (≥ 0) or null
36968
+ *
36969
+ * @example
36970
+ * ```typescript
36971
+ * import { getPositionActiveMinutes } from "backtest-kit";
36972
+ *
36973
+ * const minutes = await getPositionActiveMinutes("BTCUSDT");
36974
+ * // e.g. 120 (position has been open for 2 hours)
36975
+ * ```
36976
+ */
36977
+ async function getPositionActiveMinutes(symbol) {
36978
+ backtest.loggerService.info(GET_POSITION_ACTIVE_MINUTES_METHOD_NAME, { symbol });
36979
+ if (!ExecutionContextService.hasContext()) {
36980
+ throw new Error("getPositionActiveMinutes requires an execution context");
36981
+ }
36982
+ if (!MethodContextService.hasContext()) {
36983
+ throw new Error("getPositionActiveMinutes requires a method context");
36984
+ }
36985
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36986
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36987
+ return await backtest.strategyCoreService.getPositionActiveMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36988
+ }
36989
+ /**
36990
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
36991
+ *
36992
+ * Returns null if no scheduled signal exists.
36993
+ *
36994
+ * @param symbol - Trading pair symbol
36995
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
36996
+ *
36997
+ * @example
36998
+ * ```typescript
36999
+ * import { getPositionWaitingMinutes } from "backtest-kit";
37000
+ *
37001
+ * const minutes = await getPositionWaitingMinutes("BTCUSDT");
37002
+ * // e.g. 15 (scheduled signal has been waiting 15 minutes for activation)
37003
+ * ```
37004
+ */
37005
+ async function getPositionWaitingMinutes(symbol) {
37006
+ backtest.loggerService.info(GET_POSITION_WAITING_MINUTES_METHOD_NAME, { symbol });
37007
+ if (!ExecutionContextService.hasContext()) {
37008
+ throw new Error("getPositionWaitingMinutes requires an execution context");
37009
+ }
37010
+ if (!MethodContextService.hasContext()) {
37011
+ throw new Error("getPositionWaitingMinutes requires a method context");
37012
+ }
37013
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37014
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37015
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
37016
+ }
36716
37017
  /**
36717
37018
  * Returns the best price reached in the profit direction during this position's life.
36718
37019
  *
@@ -37400,6 +37701,50 @@ async function hasNoScheduledSignal(symbol) {
37400
37701
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37401
37702
  return await functoolsKit.not(backtest.strategyCoreService.hasScheduledSignal(isBacktest, symbol, { exchangeName, frameName, strategyName }));
37402
37703
  }
37704
+ /**
37705
+ * Emits a `signal.info` notification for the currently active pending signal.
37706
+ *
37707
+ * Broadcasts a user-defined informational note without affecting position state.
37708
+ * Useful for annotating strategy decisions, triggering external alerts, or logging
37709
+ * mid-position events (e.g. RSI crossing a threshold, volume spike detected).
37710
+ *
37711
+ * Automatically reads backtest/live mode from execution context.
37712
+ * Automatically reads strategyName, exchangeName, frameName from method context.
37713
+ * Automatically fetches current price via getAveragePrice.
37714
+ *
37715
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
37716
+ * @param payload - Optional notification fields
37717
+ * @param payload.notificationNote - Human-readable note. Falls back to signal.note if omitted.
37718
+ * @param payload.notificationId - Optional correlation ID for external systems (e.g. Telegram message ID).
37719
+ *
37720
+ * @throws {Error} If called outside an execution context
37721
+ * @throws {Error} If called outside a method context
37722
+ * @throws {Error} If no active pending signal exists for the given symbol
37723
+ *
37724
+ * @example
37725
+ * ```typescript
37726
+ * import { commitSignalNotify } from "backtest-kit";
37727
+ *
37728
+ * // Inside onActivePing callback:
37729
+ * await commitSignalNotify("BTCUSDT", {
37730
+ * notificationNote: "RSI crossed 70, consider closing",
37731
+ * notificationId: "msg-123",
37732
+ * });
37733
+ * ```
37734
+ */
37735
+ async function commitSignalNotify(symbol, payload = {}) {
37736
+ backtest.loggerService.info(COMMIT_SIGNAL_NOTIFY_METHOD_NAME, { symbol, payload });
37737
+ if (!ExecutionContextService.hasContext()) {
37738
+ throw new Error("commitSignalNotify requires an execution context");
37739
+ }
37740
+ if (!MethodContextService.hasContext()) {
37741
+ throw new Error("commitSignalNotify requires a method context");
37742
+ }
37743
+ const currentPrice = await getAveragePrice(symbol);
37744
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37745
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37746
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, { strategyName, exchangeName, frameName }, isBacktest);
37747
+ }
37403
37748
 
37404
37749
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
37405
37750
  /**
@@ -37483,6 +37828,8 @@ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
37483
37828
  const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
37484
37829
  const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
37485
37830
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
37831
+ const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
37832
+ const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
37486
37833
  /**
37487
37834
  * Subscribes to all signal events with queued async processing.
37488
37835
  *
@@ -38874,6 +39221,46 @@ function listenMaxDrawdownOnce(filterFn, fn) {
38874
39221
  };
38875
39222
  return disposeFn = listenMaxDrawdown(wrappedFn);
38876
39223
  }
39224
+ /**
39225
+ * Subscribes to signal info events with queued async processing.
39226
+ * Emits when a strategy calls commitSignalInfo() to broadcast a user-defined note for an open position.
39227
+ * Events are processed sequentially in order received, even if callback is async.
39228
+ * Uses queued wrapper to prevent concurrent execution of the callback.
39229
+ * @param fn - Callback function to handle signal info events
39230
+ * @return Unsubscribe function to stop listening to events
39231
+ */
39232
+ function listenSignalNotify(fn) {
39233
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_METHOD_NAME);
39234
+ const wrappedFn = async (event) => {
39235
+ if (await backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39236
+ strategyName: event.strategyName,
39237
+ exchangeName: event.exchangeName,
39238
+ frameName: event.frameName,
39239
+ })) {
39240
+ await fn(event);
39241
+ }
39242
+ };
39243
+ return signalNotifySubject.subscribe(functoolsKit.queued(wrappedFn));
39244
+ }
39245
+ /**
39246
+ * Subscribes to filtered signal info events with one-time execution.
39247
+ * Listens for events matching the filter predicate, then executes callback once
39248
+ * and automatically unsubscribes.
39249
+ * @param filterFn - Predicate to filter which events trigger the callback
39250
+ * @param fn - Callback function to handle the filtered event (called only once)
39251
+ * @return Unsubscribe function to cancel the listener before it fires
39252
+ */
39253
+ function listenSignalNotifyOnce(filterFn, fn) {
39254
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME);
39255
+ let disposeFn;
39256
+ const wrappedFn = async (event) => {
39257
+ if (filterFn(event)) {
39258
+ await fn(event);
39259
+ disposeFn && disposeFn();
39260
+ }
39261
+ };
39262
+ return disposeFn = listenSignalNotify(wrappedFn);
39263
+ }
38877
39264
 
38878
39265
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
38879
39266
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -38897,6 +39284,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPar
38897
39284
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
38898
39285
  const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
38899
39286
  const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
39287
+ const BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "BacktestUtils.getPositionActiveMinutes";
39288
+ const BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "BacktestUtils.getPositionWaitingMinutes";
38900
39289
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
38901
39290
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
38902
39291
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
@@ -38930,6 +39319,7 @@ const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingSto
38930
39319
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
38931
39320
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
38932
39321
  const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
39322
+ const BACKTEST_METHOD_NAME_SIGNAL_NOTIFY = "Backtest.commitSignalNotify";
38933
39323
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
38934
39324
  const BACKTEST_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "BacktestUtils.hasNoPendingSignal";
38935
39325
  const BACKTEST_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "BacktestUtils.hasNoScheduledSignal";
@@ -39851,6 +40241,60 @@ class BacktestUtils {
39851
40241
  }
39852
40242
  return await backtest.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
39853
40243
  };
40244
+ /**
40245
+ * Returns the number of minutes the position has been active since it opened.
40246
+ *
40247
+ * Returns null if no pending signal exists.
40248
+ *
40249
+ * @param symbol - Trading pair symbol
40250
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40251
+ * @returns Active minutes (≥ 0), or null if no active position
40252
+ */
40253
+ this.getPositionActiveMinutes = async (symbol, context) => {
40254
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
40255
+ symbol,
40256
+ context,
40257
+ });
40258
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40259
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40260
+ {
40261
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40262
+ riskName &&
40263
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40264
+ riskList &&
40265
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40266
+ actions &&
40267
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40268
+ }
40269
+ return await backtest.strategyCoreService.getPositionActiveMinutes(true, symbol, context);
40270
+ };
40271
+ /**
40272
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
40273
+ *
40274
+ * Returns null if no scheduled signal exists.
40275
+ *
40276
+ * @param symbol - Trading pair symbol
40277
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40278
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
40279
+ */
40280
+ this.getPositionWaitingMinutes = async (symbol, context) => {
40281
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
40282
+ symbol,
40283
+ context,
40284
+ });
40285
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40286
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40287
+ {
40288
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40289
+ riskName &&
40290
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40291
+ riskList &&
40292
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40293
+ actions &&
40294
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40295
+ }
40296
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(true, symbol, context);
40297
+ };
39854
40298
  /**
39855
40299
  * Returns the best price reached in the profit direction during this position's life.
39856
40300
  *
@@ -41285,6 +41729,33 @@ class BacktestUtils {
41285
41729
  });
41286
41730
  return await backtest.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
41287
41731
  };
41732
+ /**
41733
+ * Emits a `signal.info` notification for the currently active pending signal.
41734
+ *
41735
+ * @param symbol - Trading pair symbol
41736
+ * @param currentPrice - Market price at the time of the call
41737
+ * @param context - Execution context with strategyName, exchangeName, frameName
41738
+ * @param payload - Optional notification fields (notificationNote, notificationId)
41739
+ *
41740
+ * @throws {Error} If no active pending signal exists for the given symbol
41741
+ *
41742
+ * @example
41743
+ * ```typescript
41744
+ * await Backtest.commitSignalNotify("BTCUSDT", 42000, {
41745
+ * strategyName: "my-strategy",
41746
+ * exchangeName: "binance",
41747
+ * frameName: "1h"
41748
+ * }, { notificationNote: "RSI crossed 70" });
41749
+ * ```
41750
+ */
41751
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
41752
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_SIGNAL_NOTIFY, {
41753
+ symbol,
41754
+ currentPrice,
41755
+ context,
41756
+ });
41757
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, context, true);
41758
+ };
41288
41759
  /**
41289
41760
  * Gets statistical data from all closed signals for a symbol-strategy pair.
41290
41761
  *
@@ -41465,6 +41936,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
41465
41936
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
41466
41937
  const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
41467
41938
  const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
41939
+ const LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "LiveUtils.getPositionActiveMinutes";
41940
+ const LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "LiveUtils.getPositionWaitingMinutes";
41468
41941
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
41469
41942
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
41470
41943
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
@@ -41498,6 +41971,7 @@ const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
41498
41971
  const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
41499
41972
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
41500
41973
  const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
41974
+ const LIVE_METHOD_NAME_SIGNAL_NOTIFY = "Live.commitSignalNotify";
41501
41975
  const LIVE_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "LiveUtils.hasNoPendingSignal";
41502
41976
  const LIVE_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "LiveUtils.hasNoScheduledSignal";
41503
41977
  /**
@@ -42498,6 +42972,68 @@ class LiveUtils {
42498
42972
  frameName: "",
42499
42973
  });
42500
42974
  };
42975
+ /**
42976
+ * Returns the number of minutes the position has been active since it opened.
42977
+ *
42978
+ * Returns null if no pending signal exists.
42979
+ *
42980
+ * @param symbol - Trading pair symbol
42981
+ * @param context - Execution context with strategyName and exchangeName
42982
+ * @returns Active minutes (≥ 0), or null if no active position
42983
+ */
42984
+ this.getPositionActiveMinutes = async (symbol, context) => {
42985
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
42986
+ symbol,
42987
+ context,
42988
+ });
42989
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42990
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42991
+ {
42992
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42993
+ riskName &&
42994
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42995
+ riskList &&
42996
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
42997
+ actions &&
42998
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
42999
+ }
43000
+ return await backtest.strategyCoreService.getPositionActiveMinutes(false, symbol, {
43001
+ strategyName: context.strategyName,
43002
+ exchangeName: context.exchangeName,
43003
+ frameName: "",
43004
+ });
43005
+ };
43006
+ /**
43007
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
43008
+ *
43009
+ * Returns null if no scheduled signal exists.
43010
+ *
43011
+ * @param symbol - Trading pair symbol
43012
+ * @param context - Execution context with strategyName and exchangeName
43013
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
43014
+ */
43015
+ this.getPositionWaitingMinutes = async (symbol, context) => {
43016
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
43017
+ symbol,
43018
+ context,
43019
+ });
43020
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43021
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43022
+ {
43023
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43024
+ riskName &&
43025
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43026
+ riskList &&
43027
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43028
+ actions &&
43029
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43030
+ }
43031
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(false, symbol, {
43032
+ strategyName: context.strategyName,
43033
+ exchangeName: context.exchangeName,
43034
+ frameName: "",
43035
+ });
43036
+ };
42501
43037
  /**
42502
43038
  * Returns the best price reached in the profit direction during this position's life.
42503
43039
  *
@@ -44194,6 +44730,36 @@ class LiveUtils {
44194
44730
  frameName: "",
44195
44731
  }, cost);
44196
44732
  };
44733
+ /**
44734
+ * Emits a `signal.info` notification for the currently active pending signal.
44735
+ *
44736
+ * @param symbol - Trading pair symbol
44737
+ * @param currentPrice - Market price at the time of the call
44738
+ * @param context - Execution context with strategyName and exchangeName
44739
+ * @param payload - Optional notification fields (notificationNote, notificationId)
44740
+ *
44741
+ * @throws {Error} If no active pending signal exists for the given symbol
44742
+ *
44743
+ * @example
44744
+ * ```typescript
44745
+ * await Live.commitSignalNotify("BTCUSDT", 42000, {
44746
+ * strategyName: "my-strategy",
44747
+ * exchangeName: "binance",
44748
+ * }, { notificationNote: "RSI crossed 70" });
44749
+ * ```
44750
+ */
44751
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
44752
+ backtest.loggerService.info(LIVE_METHOD_NAME_SIGNAL_NOTIFY, {
44753
+ symbol,
44754
+ currentPrice,
44755
+ context,
44756
+ });
44757
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, {
44758
+ strategyName: context.strategyName,
44759
+ exchangeName: context.exchangeName,
44760
+ frameName: "",
44761
+ }, false);
44762
+ };
44197
44763
  /**
44198
44764
  * Gets statistical data from all live trading events for a symbol-strategy pair.
44199
44765
  *
@@ -47921,7 +48487,7 @@ const LOGGER_SERVICE$2 = new LoggerService();
47921
48487
  * Default configuration that enables all report services.
47922
48488
  * Used when no specific configuration is provided to enable().
47923
48489
  */
47924
- const WILDCARD_TARGET$1 = {
48490
+ const WILDCARD_TARGET$2 = {
47925
48491
  backtest: true,
47926
48492
  strategy: true,
47927
48493
  breakeven: true,
@@ -47972,7 +48538,7 @@ class ReportUtils {
47972
48538
  *
47973
48539
  * @returns Cleanup function that unsubscribes from all enabled services
47974
48540
  */
47975
- this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48541
+ this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
47976
48542
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
47977
48543
  backtest: bt,
47978
48544
  breakeven,
@@ -48064,7 +48630,7 @@ class ReportUtils {
48064
48630
  * Report.disable();
48065
48631
  * ```
48066
48632
  */
48067
- this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48633
+ this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
48068
48634
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
48069
48635
  backtest: bt,
48070
48636
  breakeven,
@@ -48188,7 +48754,7 @@ const LOGGER_SERVICE$1 = new LoggerService();
48188
48754
  * Default configuration that enables all markdown services.
48189
48755
  * Used when no specific configuration is provided to `enable()`.
48190
48756
  */
48191
- const WILDCARD_TARGET = {
48757
+ const WILDCARD_TARGET$1 = {
48192
48758
  backtest: true,
48193
48759
  breakeven: true,
48194
48760
  heat: true,
@@ -48239,7 +48805,7 @@ class MarkdownUtils {
48239
48805
  *
48240
48806
  * @returns Cleanup function that unsubscribes from all enabled services
48241
48807
  */
48242
- this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET) => {
48808
+ this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48243
48809
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_ENABLE, {
48244
48810
  backtest: bt,
48245
48811
  breakeven,
@@ -48333,7 +48899,7 @@ class MarkdownUtils {
48333
48899
  * Markdown.disable();
48334
48900
  * ```
48335
48901
  */
48336
- this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET) => {
48902
+ this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48337
48903
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_DISABLE, {
48338
48904
  backtest: bt,
48339
48905
  breakeven,
@@ -48388,6 +48954,87 @@ class MarkdownUtils {
48388
48954
  backtest.maxDrawdownMarkdownService.unsubscribe();
48389
48955
  }
48390
48956
  };
48957
+ /**
48958
+ * Clears markdown report data selectively.
48959
+ *
48960
+ * Clears accumulated data for specified markdown services without unsubscribing.
48961
+ * Use this method to reset report data for specific services while keeping them active.
48962
+ *
48963
+ * Each cleared service will:
48964
+ * - Clear accumulated data for all reports
48965
+ * - Start fresh with new data for future events
48966
+ * - Not affect event subscriptions or report generation status
48967
+ *
48968
+ * @param config - Service configuration object specifying which services to clear. Defaults to clearing all services.
48969
+ * @param config.backtest - Clear backtest result report data
48970
+ * @param config.breakeven - Clear breakeven event tracking data
48971
+ * @param config.partial - Clear partial profit/loss event tracking data
48972
+ * @param config.heat - Clear portfolio heatmap analysis data
48973
+ * @param config.walker - Clear walker strategy comparison report data
48974
+ * @param config.performance - Clear performance bottleneck analysis data
48975
+ * @param config.risk - Clear risk rejection tracking data
48976
+ * @param config.schedule - Clear scheduled signal tracking data
48977
+ * @param config.live - Clear live trading event report data
48978
+ * @param config.strategy - Clear strategy report data
48979
+ * @param config.sync - Clear sync report data
48980
+ * @param config.highest_profit - Clear highest profit report data
48981
+ * @param config.max_drawdown - Clear max drawdown report data
48982
+ */
48983
+ this.clear = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48984
+ LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48985
+ backtest: bt,
48986
+ breakeven,
48987
+ heat,
48988
+ live,
48989
+ partial,
48990
+ performance,
48991
+ risk,
48992
+ strategy,
48993
+ schedule,
48994
+ walker,
48995
+ sync,
48996
+ highest_profit,
48997
+ });
48998
+ if (bt) {
48999
+ backtest.backtestMarkdownService.clear();
49000
+ }
49001
+ if (breakeven) {
49002
+ backtest.breakevenMarkdownService.clear();
49003
+ }
49004
+ if (heat) {
49005
+ backtest.heatMarkdownService.clear();
49006
+ }
49007
+ if (live) {
49008
+ backtest.liveMarkdownService.clear();
49009
+ }
49010
+ if (partial) {
49011
+ backtest.partialMarkdownService.clear();
49012
+ }
49013
+ if (performance) {
49014
+ backtest.performanceMarkdownService.clear();
49015
+ }
49016
+ if (risk) {
49017
+ backtest.riskMarkdownService.clear();
49018
+ }
49019
+ if (strategy) {
49020
+ backtest.strategyMarkdownService.clear();
49021
+ }
49022
+ if (schedule) {
49023
+ backtest.scheduleMarkdownService.clear();
49024
+ }
49025
+ if (walker) {
49026
+ backtest.walkerMarkdownService.clear();
49027
+ }
49028
+ if (sync) {
49029
+ backtest.syncMarkdownService.clear();
49030
+ }
49031
+ if (highest_profit) {
49032
+ backtest.highestProfitMarkdownService.clear();
49033
+ }
49034
+ if (max_drawdown) {
49035
+ backtest.maxDrawdownMarkdownService.clear();
49036
+ }
49037
+ };
48391
49038
  }
48392
49039
  }
48393
49040
  /**
@@ -48430,15 +49077,6 @@ class MarkdownAdapter extends MarkdownUtils {
48430
49077
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
48431
49078
  MarkdownWriter.useJsonl();
48432
49079
  }
48433
- /**
48434
- * Clears the memoized storage cache.
48435
- * Call this when process.cwd() changes between strategy iterations
48436
- * so new storage instances are created with the updated base path.
48437
- */
48438
- clear() {
48439
- LOGGER_SERVICE$1.log(MARKDOWN_METHOD_NAME_CLEAR);
48440
- MarkdownWriter.clear();
48441
- }
48442
49080
  /**
48443
49081
  * Switches to a dummy markdown adapter that discards all writes.
48444
49082
  * All future markdown writes will be no-ops.
@@ -49135,6 +49773,7 @@ const SUBJECT_ISOLATION_LIST = [
49135
49773
  strategyCommitSubject,
49136
49774
  syncSubject,
49137
49775
  validationSubject,
49776
+ signalNotifySubject,
49138
49777
  ];
49139
49778
  /**
49140
49779
  * Creates a snapshot function for a given subject by clearing its internal
@@ -50753,6 +51392,8 @@ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.
50753
51392
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
50754
51393
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
50755
51394
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
51395
+ const REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "ReflectUtils.getPositionActiveMinutes";
51396
+ const REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "ReflectUtils.getPositionWaitingMinutes";
50756
51397
  const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
50757
51398
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
50758
51399
  const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
@@ -51027,6 +51668,52 @@ class ReflectUtils {
51027
51668
  }
51028
51669
  return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
51029
51670
  };
51671
+ /**
51672
+ * Returns the number of minutes the position has been active since it opened.
51673
+ *
51674
+ * Returns null if no pending signal exists.
51675
+ *
51676
+ * @param symbol - Trading pair symbol
51677
+ * @param context - Execution context with strategyName, exchangeName and frameName
51678
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51679
+ * @returns Promise resolving to active minutes (≥ 0) or null
51680
+ */
51681
+ this.getPositionActiveMinutes = async (symbol, context, backtest$1 = false) => {
51682
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, { symbol, context });
51683
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51684
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51685
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51686
+ {
51687
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51688
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51689
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
51690
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
51691
+ }
51692
+ return await backtest.strategyCoreService.getPositionActiveMinutes(backtest$1, symbol, context);
51693
+ };
51694
+ /**
51695
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
51696
+ *
51697
+ * Returns null if no scheduled signal exists.
51698
+ *
51699
+ * @param symbol - Trading pair symbol
51700
+ * @param context - Execution context with strategyName, exchangeName and frameName
51701
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51702
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
51703
+ */
51704
+ this.getPositionWaitingMinutes = async (symbol, context, backtest$1 = false) => {
51705
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES, { symbol, context });
51706
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51707
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51708
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51709
+ {
51710
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51711
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51712
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
51713
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
51714
+ }
51715
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(backtest$1, symbol, context);
51716
+ };
51030
51717
  /**
51031
51718
  * Returns the number of minutes elapsed since the highest profit price was recorded.
51032
51719
  *
@@ -53303,6 +53990,23 @@ const StorageLive = new StorageLiveAdapter();
53303
53990
  */
53304
53991
  const StorageBacktest = new StorageBacktestAdapter();
53305
53992
 
53993
+ /**
53994
+ * Default configuration that enables all notification types.
53995
+ * Used when no specific configuration is provided to enable().
53996
+ */
53997
+ const WILDCARD_TARGET = {
53998
+ signal: true,
53999
+ partial_profit: true,
54000
+ partial_loss: true,
54001
+ breakeven: true,
54002
+ strategy_commit: true,
54003
+ signal_sync: true,
54004
+ risk: true,
54005
+ info: true,
54006
+ common_error: true,
54007
+ critical_error: true,
54008
+ validation_error: true,
54009
+ };
53306
54010
  /**
53307
54011
  * Generates a unique key for notification identification.
53308
54012
  * @returns Random string identifier
@@ -53980,7 +54684,44 @@ const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
53980
54684
  message: functoolsKit.getErrorMessage(error),
53981
54685
  backtest: false,
53982
54686
  });
54687
+ /**
54688
+ * Creates a notification model for signal info events.
54689
+ * @param data - The signal info contract data
54690
+ * @returns NotificationModel for signal info event
54691
+ */
54692
+ const CREATE_SIGNAL_INFO_NOTIFICATION_FN = (data) => ({
54693
+ type: "signal.info",
54694
+ id: CREATE_KEY_FN$2(),
54695
+ timestamp: data.timestamp,
54696
+ backtest: data.backtest,
54697
+ symbol: data.symbol,
54698
+ strategyName: data.strategyName,
54699
+ exchangeName: data.exchangeName,
54700
+ signalId: data.data.id,
54701
+ currentPrice: data.currentPrice,
54702
+ position: data.data.position,
54703
+ priceOpen: data.data.priceOpen,
54704
+ priceTakeProfit: data.data.priceTakeProfit,
54705
+ priceStopLoss: data.data.priceStopLoss,
54706
+ originalPriceTakeProfit: data.data.originalPriceTakeProfit,
54707
+ originalPriceStopLoss: data.data.originalPriceStopLoss,
54708
+ originalPriceOpen: data.data.originalPriceOpen,
54709
+ totalEntries: data.data.totalEntries,
54710
+ totalPartials: data.data.totalPartials,
54711
+ pnl: data.data.pnl,
54712
+ pnlPercentage: data.data.pnl.pnlPercentage,
54713
+ pnlPriceOpen: data.data.pnl.priceOpen,
54714
+ pnlPriceClose: data.data.pnl.priceClose,
54715
+ pnlCost: data.data.pnl.pnlCost,
54716
+ pnlEntries: data.data.pnl.pnlEntries,
54717
+ note: data.note,
54718
+ notificationId: data.notificationId,
54719
+ scheduledAt: data.data.scheduledAt,
54720
+ pendingAt: data.data.pendingAt,
54721
+ createdAt: data.timestamp,
54722
+ });
53983
54723
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryBacktestUtils.handleSignal";
54724
+ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryBacktestUtils.handleSignalNotify";
53984
54725
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryBacktestUtils.handlePartialProfit";
53985
54726
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryBacktestUtils.handlePartialLoss";
53986
54727
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryBacktestUtils.handleBreakeven";
@@ -53993,6 +54734,7 @@ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "Notifi
53993
54734
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA = "NotificationMemoryBacktestUtils.getData";
53994
54735
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_DISPOSE = "NotificationMemoryBacktestUtils.dispose";
53995
54736
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryLiveUtils.handleSignal";
54737
+ const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryLiveUtils.handleSignalNotify";
53996
54738
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryLiveUtils.handlePartialProfit";
53997
54739
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryLiveUtils.handlePartialLoss";
53998
54740
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryLiveUtils.handleBreakeven";
@@ -54021,6 +54763,7 @@ const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_CLEAR = "NotificationLiveAdapter.cle
54021
54763
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistBacktestUtils.waitForInit";
54022
54764
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistBacktestUtils._updateNotifications";
54023
54765
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistBacktestUtils.handleSignal";
54766
+ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistBacktestUtils.handleSignalNotify";
54024
54767
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistBacktestUtils.handlePartialProfit";
54025
54768
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistBacktestUtils.handlePartialLoss";
54026
54769
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistBacktestUtils.handleBreakeven";
@@ -54035,6 +54778,7 @@ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_DISPOSE = "NotificationPersistBa
54035
54778
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistLiveUtils.waitForInit";
54036
54779
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistLiveUtils._updateNotifications";
54037
54780
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistLiveUtils.handleSignal";
54781
+ const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistLiveUtils.handleSignalNotify";
54038
54782
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistLiveUtils.handlePartialProfit";
54039
54783
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistLiveUtils.handlePartialLoss";
54040
54784
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistLiveUtils.handleBreakeven";
@@ -54077,6 +54821,12 @@ class NotificationMemoryBacktestUtils {
54077
54821
  this._addNotification(notification);
54078
54822
  }
54079
54823
  };
54824
+ this.handleSignalNotify = async (data) => {
54825
+ backtest.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54826
+ signalId: data.data.id,
54827
+ });
54828
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54829
+ };
54080
54830
  /**
54081
54831
  * Handles partial profit availability event.
54082
54832
  * @param data - The partial profit contract data
@@ -54221,6 +54971,8 @@ class NotificationDummyBacktestUtils {
54221
54971
  */
54222
54972
  this.handleSignal = async () => {
54223
54973
  };
54974
+ this.handleSignalNotify = async () => {
54975
+ };
54224
54976
  /**
54225
54977
  * No-op handler for partial profit event.
54226
54978
  */
@@ -54328,6 +55080,14 @@ class NotificationPersistBacktestUtils {
54328
55080
  await this._updateNotifications();
54329
55081
  }
54330
55082
  };
55083
+ this.handleSignalNotify = async (data) => {
55084
+ backtest.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55085
+ signalId: data.data.id,
55086
+ });
55087
+ await this.waitForInit();
55088
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55089
+ await this._updateNotifications();
55090
+ };
54331
55091
  /**
54332
55092
  * Handles partial profit availability event.
54333
55093
  * @param data - The partial profit contract data
@@ -54529,6 +55289,12 @@ class NotificationMemoryLiveUtils {
54529
55289
  this._addNotification(notification);
54530
55290
  }
54531
55291
  };
55292
+ this.handleSignalNotify = async (data) => {
55293
+ backtest.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55294
+ signalId: data.data.id,
55295
+ });
55296
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55297
+ };
54532
55298
  /**
54533
55299
  * Handles partial profit availability event.
54534
55300
  * @param data - The partial profit contract data
@@ -54673,6 +55439,8 @@ class NotificationDummyLiveUtils {
54673
55439
  */
54674
55440
  this.handleSignal = async () => {
54675
55441
  };
55442
+ this.handleSignalNotify = async () => {
55443
+ };
54676
55444
  /**
54677
55445
  * No-op handler for partial profit event.
54678
55446
  */
@@ -54781,6 +55549,14 @@ class NotificationPersistLiveUtils {
54781
55549
  await this._updateNotifications();
54782
55550
  }
54783
55551
  };
55552
+ this.handleSignalNotify = async (data) => {
55553
+ backtest.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55554
+ signalId: data.data.id,
55555
+ });
55556
+ await this.waitForInit();
55557
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55558
+ await this._updateNotifications();
55559
+ };
54784
55560
  /**
54785
55561
  * Handles partial profit availability event.
54786
55562
  * @param data - The partial profit contract data
@@ -54974,6 +55750,9 @@ class NotificationBacktestAdapter {
54974
55750
  this.handleSignal = async (data) => {
54975
55751
  return await this._notificationBacktestUtils.handleSignal(data);
54976
55752
  };
55753
+ this.handleSignalNotify = async (data) => {
55754
+ return await this._notificationBacktestUtils.handleSignalNotify(data);
55755
+ };
54977
55756
  /**
54978
55757
  * Handles partial profit availability event.
54979
55758
  * Proxies call to the underlying notification adapter.
@@ -55129,6 +55908,9 @@ class NotificationLiveAdapter {
55129
55908
  this.handleSignal = async (data) => {
55130
55909
  return await this._notificationLiveUtils.handleSignal(data);
55131
55910
  };
55911
+ this.handleSignalNotify = async (data) => {
55912
+ return await this._notificationLiveUtils.handleSignalNotify(data);
55913
+ };
55132
55914
  /**
55133
55915
  * Handles partial profit availability event.
55134
55916
  * Proxies call to the underlying notification adapter.
@@ -55280,59 +56062,153 @@ class NotificationAdapter {
55280
56062
  *
55281
56063
  * @returns Cleanup function that unsubscribes from all emitters
55282
56064
  */
55283
- this.enable = functoolsKit.singleshot(() => {
56065
+ this.enable = functoolsKit.singleshot(({ signal = false, info = false, partial_profit = false, partial_loss = false, breakeven = false, strategy_commit = false, signal_sync = false, risk = false, common_error = false, critical_error = false, validation_error = false, } = WILDCARD_TARGET) => {
55284
56066
  backtest.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE);
55285
56067
  let unLive;
55286
56068
  let unBacktest;
55287
56069
  {
55288
- const unBacktestSignal = signalBacktestEmitter.subscribe((data) => NotificationBacktest.handleSignal(data));
56070
+ const unBacktestSignal = signalBacktestEmitter.subscribe(async (data) => {
56071
+ if (signal) {
56072
+ await NotificationBacktest.handleSignal(data);
56073
+ }
56074
+ });
55289
56075
  const unBacktestPartialProfit = partialProfitSubject
55290
56076
  .filter(({ backtest }) => backtest)
55291
- .connect((data) => NotificationBacktest.handlePartialProfit(data));
56077
+ .connect(async (data) => {
56078
+ if (partial_profit) {
56079
+ await NotificationBacktest.handlePartialProfit(data);
56080
+ }
56081
+ });
55292
56082
  const unBacktestPartialLoss = partialLossSubject
55293
56083
  .filter(({ backtest }) => backtest)
55294
- .connect((data) => NotificationBacktest.handlePartialLoss(data));
56084
+ .connect(async (data) => {
56085
+ if (partial_loss) {
56086
+ await NotificationBacktest.handlePartialLoss(data);
56087
+ }
56088
+ });
55295
56089
  const unBacktestBreakeven = breakevenSubject
55296
56090
  .filter(({ backtest }) => backtest)
55297
- .connect((data) => NotificationBacktest.handleBreakeven(data));
56091
+ .connect(async (data) => {
56092
+ if (breakeven) {
56093
+ await NotificationBacktest.handleBreakeven(data);
56094
+ }
56095
+ });
55298
56096
  const unBacktestStrategyCommit = strategyCommitSubject
55299
56097
  .filter(({ backtest }) => backtest)
55300
- .connect((data) => NotificationBacktest.handleStrategyCommit(data));
56098
+ .connect(async (data) => {
56099
+ if (strategy_commit) {
56100
+ await NotificationBacktest.handleStrategyCommit(data);
56101
+ }
56102
+ });
55301
56103
  const unBacktestSync = syncSubject
55302
56104
  .filter(({ backtest }) => backtest)
55303
- .connect((data) => NotificationBacktest.handleSync(data));
56105
+ .connect(async (data) => {
56106
+ if (signal_sync) {
56107
+ await NotificationBacktest.handleSync(data);
56108
+ }
56109
+ });
55304
56110
  const unBacktestRisk = riskSubject
55305
56111
  .filter(({ backtest }) => backtest)
55306
- .connect((data) => NotificationBacktest.handleRisk(data));
55307
- const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
55308
- const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
55309
- const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
55310
- unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation());
56112
+ .connect(async (data) => {
56113
+ if (risk) {
56114
+ await NotificationBacktest.handleRisk(data);
56115
+ }
56116
+ });
56117
+ const unBacktestError = errorEmitter.subscribe(async (error) => {
56118
+ if (common_error) {
56119
+ await NotificationBacktest.handleError(error);
56120
+ }
56121
+ });
56122
+ const unBacktestExit = exitEmitter.subscribe(async (error) => {
56123
+ if (critical_error) {
56124
+ await NotificationBacktest.handleCriticalError(error);
56125
+ }
56126
+ });
56127
+ const unBacktestValidation = validationSubject.subscribe(async (error) => {
56128
+ if (validation_error) {
56129
+ await NotificationBacktest.handleValidationError(error);
56130
+ }
56131
+ });
56132
+ const unBacktestSignalNotify = signalNotifySubject
56133
+ .filter(({ backtest }) => backtest)
56134
+ .connect(async (data) => {
56135
+ if (info) {
56136
+ await NotificationBacktest.handleSignalNotify(data);
56137
+ }
56138
+ });
56139
+ unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
55311
56140
  }
55312
56141
  {
55313
- const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
56142
+ const unLiveSignal = signalLiveEmitter.subscribe(async (data) => {
56143
+ if (signal) {
56144
+ await NotificationLive.handleSignal(data);
56145
+ }
56146
+ });
55314
56147
  const unLivePartialProfit = partialProfitSubject
55315
56148
  .filter(({ backtest }) => !backtest)
55316
- .connect((data) => NotificationLive.handlePartialProfit(data));
56149
+ .connect(async (data) => {
56150
+ if (partial_profit) {
56151
+ await NotificationLive.handlePartialProfit(data);
56152
+ }
56153
+ });
55317
56154
  const unLivePartialLoss = partialLossSubject
55318
56155
  .filter(({ backtest }) => !backtest)
55319
- .connect((data) => NotificationLive.handlePartialLoss(data));
56156
+ .connect(async (data) => {
56157
+ if (partial_loss) {
56158
+ await NotificationLive.handlePartialLoss(data);
56159
+ }
56160
+ });
55320
56161
  const unLiveBreakeven = breakevenSubject
55321
56162
  .filter(({ backtest }) => !backtest)
55322
- .connect((data) => NotificationLive.handleBreakeven(data));
56163
+ .connect(async (data) => {
56164
+ if (breakeven) {
56165
+ await NotificationLive.handleBreakeven(data);
56166
+ }
56167
+ });
55323
56168
  const unLiveStrategyCommit = strategyCommitSubject
55324
56169
  .filter(({ backtest }) => !backtest)
55325
- .connect((data) => NotificationLive.handleStrategyCommit(data));
56170
+ .connect(async (data) => {
56171
+ if (strategy_commit) {
56172
+ await NotificationLive.handleStrategyCommit(data);
56173
+ }
56174
+ });
55326
56175
  const unLiveSync = syncSubject
55327
56176
  .filter(({ backtest }) => !backtest)
55328
- .connect((data) => NotificationLive.handleSync(data));
56177
+ .connect(async (data) => {
56178
+ if (signal_sync) {
56179
+ await NotificationLive.handleSync(data);
56180
+ }
56181
+ });
55329
56182
  const unLiveRisk = riskSubject
55330
56183
  .filter(({ backtest }) => !backtest)
55331
- .connect((data) => NotificationLive.handleRisk(data));
55332
- const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
55333
- const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
55334
- const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
55335
- unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation());
56184
+ .connect(async (data) => {
56185
+ if (risk) {
56186
+ await NotificationLive.handleRisk(data);
56187
+ }
56188
+ });
56189
+ const unLiveError = errorEmitter.subscribe(async (error) => {
56190
+ if (common_error) {
56191
+ await NotificationLive.handleError(error);
56192
+ }
56193
+ });
56194
+ const unLiveExit = exitEmitter.subscribe(async (error) => {
56195
+ if (critical_error) {
56196
+ await NotificationLive.handleCriticalError(error);
56197
+ }
56198
+ });
56199
+ const unLiveValidation = validationSubject.subscribe(async (error) => {
56200
+ if (validation_error) {
56201
+ await NotificationLive.handleValidationError(error);
56202
+ }
56203
+ });
56204
+ const unLiveSignalNotify = signalNotifySubject
56205
+ .filter(({ backtest }) => !backtest)
56206
+ .connect(async (data) => {
56207
+ if (info) {
56208
+ await NotificationLive.handleSignalNotify(data);
56209
+ }
56210
+ });
56211
+ unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
55336
56212
  }
55337
56213
  return () => {
55338
56214
  unLive();
@@ -55570,11 +56446,17 @@ class CacheFnInstance {
55570
56446
  return cached;
55571
56447
  }
55572
56448
  }
56449
+ const value = this.fn(...args);
55573
56450
  const newCache = {
55574
56451
  when: currentWhen,
55575
- value: this.fn(...args),
56452
+ value,
55576
56453
  };
55577
56454
  this._cacheMap.set(key, newCache);
56455
+ if (value && value instanceof Promise) {
56456
+ value.catch(() => {
56457
+ this._cacheMap.delete(key);
56458
+ });
56459
+ }
55578
56460
  return newCache;
55579
56461
  };
55580
56462
  /**
@@ -56155,7 +57037,7 @@ class IntervalFnInstance {
56155
57037
  * within the same interval or when `fn` itself returned `null`
56156
57038
  * @throws Error if method context, execution context, or interval is missing
56157
57039
  */
56158
- this.run = async (...args) => {
57040
+ this.run = (...args) => {
56159
57041
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
56160
57042
  const step = INTERVAL_MINUTES[this.interval];
56161
57043
  {
@@ -56177,10 +57059,15 @@ class IntervalFnInstance {
56177
57059
  if (this._stateMap.get(stateKey) === currentAligned) {
56178
57060
  return null;
56179
57061
  }
56180
- const result = await this.fn.apply(null, args);
57062
+ const result = this.fn.apply(null, args);
56181
57063
  if (result !== null) {
56182
57064
  this._stateMap.set(stateKey, currentAligned);
56183
57065
  }
57066
+ if (result && result instanceof Promise) {
57067
+ result.catch(() => {
57068
+ this._stateMap.delete(stateKey);
57069
+ });
57070
+ }
56184
57071
  return result;
56185
57072
  };
56186
57073
  /**
@@ -57788,6 +58675,7 @@ exports.commitPartialLoss = commitPartialLoss;
57788
58675
  exports.commitPartialLossCost = commitPartialLossCost;
57789
58676
  exports.commitPartialProfit = commitPartialProfit;
57790
58677
  exports.commitPartialProfitCost = commitPartialProfitCost;
58678
+ exports.commitSignalNotify = commitSignalNotify;
57791
58679
  exports.commitTrailingStop = commitTrailingStop;
57792
58680
  exports.commitTrailingStopCost = commitTrailingStopCost;
57793
58681
  exports.commitTrailingTake = commitTrailingTake;
@@ -57824,6 +58712,7 @@ exports.getMode = getMode;
57824
58712
  exports.getNextCandles = getNextCandles;
57825
58713
  exports.getOrderBook = getOrderBook;
57826
58714
  exports.getPendingSignal = getPendingSignal;
58715
+ exports.getPositionActiveMinutes = getPositionActiveMinutes;
57827
58716
  exports.getPositionCountdownMinutes = getPositionCountdownMinutes;
57828
58717
  exports.getPositionDrawdownMinutes = getPositionDrawdownMinutes;
57829
58718
  exports.getPositionEffectivePrice = getPositionEffectivePrice;
@@ -57852,6 +58741,7 @@ exports.getPositionPartialOverlap = getPositionPartialOverlap;
57852
58741
  exports.getPositionPartials = getPositionPartials;
57853
58742
  exports.getPositionPnlCost = getPositionPnlCost;
57854
58743
  exports.getPositionPnlPercent = getPositionPnlPercent;
58744
+ exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
57855
58745
  exports.getRawCandles = getRawCandles;
57856
58746
  exports.getRiskSchema = getRiskSchema;
57857
58747
  exports.getScheduledSignal = getScheduledSignal;
@@ -57906,6 +58796,8 @@ exports.listenSignalBacktest = listenSignalBacktest;
57906
58796
  exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
57907
58797
  exports.listenSignalLive = listenSignalLive;
57908
58798
  exports.listenSignalLiveOnce = listenSignalLiveOnce;
58799
+ exports.listenSignalNotify = listenSignalNotify;
58800
+ exports.listenSignalNotifyOnce = listenSignalNotifyOnce;
57909
58801
  exports.listenSignalOnce = listenSignalOnce;
57910
58802
  exports.listenStrategyCommit = listenStrategyCommit;
57911
58803
  exports.listenStrategyCommitOnce = listenStrategyCommitOnce;