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.mjs CHANGED
@@ -127,6 +127,9 @@ const reportServices$1 = {
127
127
  highestProfitReportService: Symbol('highestProfitReportService'),
128
128
  maxDrawdownReportService: Symbol('maxDrawdownReportService'),
129
129
  };
130
+ const helperServices$1 = {
131
+ notificationHelperService: Symbol('notificationHelperService'),
132
+ };
130
133
  const validationServices$1 = {
131
134
  exchangeValidationService: Symbol('exchangeValidationService'),
132
135
  strategyValidationService: Symbol('strategyValidationService'),
@@ -152,6 +155,7 @@ const TYPES = {
152
155
  ...markdownServices$1,
153
156
  ...reportServices$1,
154
157
  ...validationServices$1,
158
+ ...helperServices$1,
155
159
  };
156
160
 
157
161
  /**
@@ -744,6 +748,11 @@ const highestProfitSubject = new Subject();
744
748
  * Allows users to track drawdown levels and implement custom risk management logic based on drawdown thresholds.
745
749
  */
746
750
  const maxDrawdownSubject = new Subject();
751
+ /**
752
+ * Signal info emitter for user-defined informational notes on open positions.
753
+ * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
754
+ */
755
+ const signalNotifySubject = new Subject();
747
756
 
748
757
  var emitters = /*#__PURE__*/Object.freeze({
749
758
  __proto__: null,
@@ -768,6 +777,7 @@ var emitters = /*#__PURE__*/Object.freeze({
768
777
  signalBacktestEmitter: signalBacktestEmitter,
769
778
  signalEmitter: signalEmitter,
770
779
  signalLiveEmitter: signalLiveEmitter,
780
+ signalNotifySubject: signalNotifySubject,
771
781
  strategyCommitSubject: strategyCommitSubject,
772
782
  syncSubject: syncSubject,
773
783
  validationSubject: validationSubject,
@@ -7953,6 +7963,40 @@ class ClientStrategy {
7953
7963
  }
7954
7964
  return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
7955
7965
  }
7966
+ /**
7967
+ * Returns the number of minutes the position has been active since it opened.
7968
+ *
7969
+ * Computed as elapsed minutes since `pendingAt` (the moment the signal was activated).
7970
+ * Returns null if no pending signal exists.
7971
+ *
7972
+ * @param symbol - Trading pair symbol
7973
+ * @param timestamp - Current Unix timestamp in milliseconds
7974
+ * @returns Promise resolving to active minutes (≥ 0) or null
7975
+ */
7976
+ async getPositionActiveMinutes(symbol, timestamp) {
7977
+ this.params.logger.debug("ClientStrategy getPositionActiveMinutes", { symbol });
7978
+ if (!this._pendingSignal) {
7979
+ return null;
7980
+ }
7981
+ return Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
7982
+ }
7983
+ /**
7984
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
7985
+ *
7986
+ * Computed as elapsed minutes since `scheduledAt` (the moment the scheduled signal was created).
7987
+ * Returns null if no scheduled signal exists.
7988
+ *
7989
+ * @param symbol - Trading pair symbol
7990
+ * @param timestamp - Current Unix timestamp in milliseconds
7991
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
7992
+ */
7993
+ async getPositionWaitingMinutes(symbol, timestamp) {
7994
+ this.params.logger.debug("ClientStrategy getPositionWaitingMinutes", { symbol });
7995
+ if (!this._scheduledSignal) {
7996
+ return null;
7997
+ }
7998
+ return Math.floor((timestamp - this._scheduledSignal.scheduledAt) / 60000);
7999
+ }
7956
8000
  /**
7957
8001
  * Returns the number of minutes elapsed since the highest profit price was recorded.
7958
8002
  *
@@ -10270,7 +10314,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10270
10314
  * @param backtest - Whether running in backtest mode
10271
10315
  * @returns Unique string key for memoization
10272
10316
  */
10273
- const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
10317
+ const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10274
10318
  const parts = [symbol, strategyName, exchangeName];
10275
10319
  if (frameName)
10276
10320
  parts.push(frameName);
@@ -10537,7 +10581,7 @@ class StrategyConnectionService {
10537
10581
  * @param backtest - Whether running in backtest mode
10538
10582
  * @returns Configured ClientStrategy instance
10539
10583
  */
10540
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10584
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10541
10585
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10542
10586
  return new ClientStrategy({
10543
10587
  symbol,
@@ -11054,6 +11098,46 @@ class StrategyConnectionService {
11054
11098
  const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11055
11099
  return await strategy.getPositionCountdownMinutes(symbol, timestamp);
11056
11100
  };
11101
+ /**
11102
+ * Returns the number of minutes the position has been active since it opened.
11103
+ *
11104
+ * Delegates to ClientStrategy.getPositionActiveMinutes().
11105
+ * Returns null if no pending signal exists.
11106
+ *
11107
+ * @param backtest - Whether running in backtest mode
11108
+ * @param symbol - Trading pair symbol
11109
+ * @param context - Execution context with strategyName, exchangeName, frameName
11110
+ * @returns Promise resolving to active minutes (≥ 0) or null
11111
+ */
11112
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
11113
+ this.loggerService.log("strategyConnectionService getPositionActiveMinutes", {
11114
+ symbol,
11115
+ context,
11116
+ });
11117
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11118
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11119
+ return await strategy.getPositionActiveMinutes(symbol, timestamp);
11120
+ };
11121
+ /**
11122
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
11123
+ *
11124
+ * Delegates to ClientStrategy.getPositionWaitingMinutes().
11125
+ * Returns null if no scheduled 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 waiting minutes (≥ 0) or null
11131
+ */
11132
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
11133
+ this.loggerService.log("strategyConnectionService getPositionWaitingMinutes", {
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.getPositionWaitingMinutes(symbol, timestamp);
11140
+ };
11057
11141
  /**
11058
11142
  * Returns the best price reached in the profit direction during this position's life.
11059
11143
  *
@@ -11458,7 +11542,7 @@ class StrategyConnectionService {
11458
11542
  }
11459
11543
  return;
11460
11544
  }
11461
- const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11545
+ const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11462
11546
  if (!this.getStrategy.has(key)) {
11463
11547
  return;
11464
11548
  }
@@ -12627,7 +12711,7 @@ class ClientRisk {
12627
12711
  * @param backtest - Whether running in backtest mode
12628
12712
  * @returns Unique string key for memoization
12629
12713
  */
12630
- const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
12714
+ const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
12631
12715
  const parts = [riskName, exchangeName];
12632
12716
  if (frameName)
12633
12717
  parts.push(frameName);
@@ -12727,7 +12811,7 @@ class RiskConnectionService {
12727
12811
  * @param backtest - True if backtest mode, false if live mode
12728
12812
  * @returns Configured ClientRisk instance
12729
12813
  */
12730
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12814
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12731
12815
  const schema = this.riskSchemaService.get(riskName);
12732
12816
  return new ClientRisk({
12733
12817
  ...schema,
@@ -12796,7 +12880,7 @@ class RiskConnectionService {
12796
12880
  payload,
12797
12881
  });
12798
12882
  if (payload) {
12799
- const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12883
+ const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12800
12884
  this.getRisk.clear(key);
12801
12885
  }
12802
12886
  else {
@@ -13840,7 +13924,7 @@ class ClientAction {
13840
13924
  * @param backtest - Whether running in backtest mode
13841
13925
  * @returns Unique string key for memoization
13842
13926
  */
13843
- const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
13927
+ const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
13844
13928
  const parts = [actionName, strategyName, exchangeName];
13845
13929
  if (frameName)
13846
13930
  parts.push(frameName);
@@ -13892,7 +13976,7 @@ class ActionConnectionService {
13892
13976
  * @param backtest - True if backtest mode, false if live mode
13893
13977
  * @returns Configured ClientAction instance
13894
13978
  */
13895
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13979
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13896
13980
  const schema = this.actionSchemaService.get(actionName);
13897
13981
  return new ClientAction({
13898
13982
  ...schema,
@@ -14103,7 +14187,7 @@ class ActionConnectionService {
14103
14187
  await Promise.all(actions.map(async (action) => await action.dispose()));
14104
14188
  return;
14105
14189
  }
14106
- const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14190
+ const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14107
14191
  if (!this.getAction.has(key)) {
14108
14192
  return;
14109
14193
  }
@@ -14114,14 +14198,14 @@ class ActionConnectionService {
14114
14198
  }
14115
14199
  }
14116
14200
 
14117
- const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
14201
+ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14118
14202
  /**
14119
14203
  * Creates a unique key for memoizing validate calls.
14120
14204
  * Key format: "exchangeName"
14121
14205
  * @param exchangeName - Exchange name
14122
14206
  * @returns Unique string key for memoization
14123
14207
  */
14124
- const CREATE_KEY_FN$r = (exchangeName) => {
14208
+ const CREATE_KEY_FN$s = (exchangeName) => {
14125
14209
  return exchangeName;
14126
14210
  };
14127
14211
  /**
@@ -14145,11 +14229,11 @@ class ExchangeCoreService {
14145
14229
  * @param exchangeName - Name of the exchange to validate
14146
14230
  * @returns Promise that resolves when validation is complete
14147
14231
  */
14148
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
14149
- this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14232
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14233
+ this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14150
14234
  exchangeName,
14151
14235
  });
14152
- this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$2);
14236
+ this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$3);
14153
14237
  });
14154
14238
  /**
14155
14239
  * Fetches historical candles with execution context.
@@ -14390,14 +14474,14 @@ class ExchangeCoreService {
14390
14474
  }
14391
14475
  }
14392
14476
 
14393
- const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14477
+ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14394
14478
  /**
14395
14479
  * Creates a unique key for memoizing validate calls.
14396
14480
  * Key format: "strategyName:exchangeName:frameName"
14397
14481
  * @param context - Execution context with strategyName, exchangeName, frameName
14398
14482
  * @returns Unique string key for memoization
14399
14483
  */
14400
- const CREATE_KEY_FN$q = (context) => {
14484
+ const CREATE_KEY_FN$r = (context) => {
14401
14485
  const parts = [context.strategyName, context.exchangeName];
14402
14486
  if (context.frameName)
14403
14487
  parts.push(context.frameName);
@@ -14429,16 +14513,16 @@ class StrategyCoreService {
14429
14513
  * @param context - Execution context with strategyName, exchangeName, frameName
14430
14514
  * @returns Promise that resolves when validation is complete
14431
14515
  */
14432
- this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
14433
- this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14516
+ this.validate = memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14517
+ this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14434
14518
  context,
14435
14519
  });
14436
14520
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
14437
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
14438
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
14439
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
14440
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
14441
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
14521
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$2);
14522
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$2);
14523
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$2);
14524
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2);
14525
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2));
14442
14526
  });
14443
14527
  /**
14444
14528
  * Retrieves the currently active pending signal for the symbol.
@@ -15374,6 +15458,38 @@ class StrategyCoreService {
15374
15458
  await this.validate(context);
15375
15459
  return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
15376
15460
  };
15461
+ /**
15462
+ * Returns the number of minutes the position has been active since it opened.
15463
+ *
15464
+ * @param backtest - Whether running in backtest mode
15465
+ * @param symbol - Trading pair symbol
15466
+ * @param context - Execution context with strategyName, exchangeName, frameName
15467
+ * @returns Promise resolving to active minutes (≥ 0) or null
15468
+ */
15469
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
15470
+ this.loggerService.log("strategyCoreService getPositionActiveMinutes", {
15471
+ symbol,
15472
+ context,
15473
+ });
15474
+ await this.validate(context);
15475
+ return await this.strategyConnectionService.getPositionActiveMinutes(backtest, symbol, context);
15476
+ };
15477
+ /**
15478
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
15479
+ *
15480
+ * @param backtest - Whether running in backtest mode
15481
+ * @param symbol - Trading pair symbol
15482
+ * @param context - Execution context with strategyName, exchangeName, frameName
15483
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
15484
+ */
15485
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
15486
+ this.loggerService.log("strategyCoreService getPositionWaitingMinutes", {
15487
+ symbol,
15488
+ context,
15489
+ });
15490
+ await this.validate(context);
15491
+ return await this.strategyConnectionService.getPositionWaitingMinutes(backtest, symbol, context);
15492
+ };
15377
15493
  /**
15378
15494
  * Returns the best price reached in the profit direction during this position's life.
15379
15495
  *
@@ -15767,7 +15883,7 @@ class SizingGlobalService {
15767
15883
  * @param context - Context with riskName, exchangeName, frameName
15768
15884
  * @returns Unique string key for memoization
15769
15885
  */
15770
- const CREATE_KEY_FN$p = (context) => {
15886
+ const CREATE_KEY_FN$q = (context) => {
15771
15887
  const parts = [context.riskName, context.exchangeName];
15772
15888
  if (context.frameName)
15773
15889
  parts.push(context.frameName);
@@ -15793,7 +15909,7 @@ class RiskGlobalService {
15793
15909
  * @param payload - Payload with riskName, exchangeName and frameName
15794
15910
  * @returns Promise that resolves when validation is complete
15795
15911
  */
15796
- this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15912
+ this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
15797
15913
  this.loggerService.log("riskGlobalService validate", {
15798
15914
  context,
15799
15915
  });
@@ -15864,14 +15980,14 @@ class RiskGlobalService {
15864
15980
  }
15865
15981
  }
15866
15982
 
15867
- const METHOD_NAME_VALIDATE = "actionCoreService validate";
15983
+ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
15868
15984
  /**
15869
15985
  * Creates a unique key for memoizing validate calls.
15870
15986
  * Key format: "strategyName:exchangeName:frameName"
15871
15987
  * @param context - Execution context with strategyName, exchangeName, frameName
15872
15988
  * @returns Unique string key for memoization
15873
15989
  */
15874
- const CREATE_KEY_FN$o = (context) => {
15990
+ const CREATE_KEY_FN$p = (context) => {
15875
15991
  const parts = [context.strategyName, context.exchangeName];
15876
15992
  if (context.frameName)
15877
15993
  parts.push(context.frameName);
@@ -15915,17 +16031,17 @@ class ActionCoreService {
15915
16031
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15916
16032
  * @returns Promise that resolves when all validations complete
15917
16033
  */
15918
- this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15919
- this.loggerService.log(METHOD_NAME_VALIDATE, {
16034
+ this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
16035
+ this.loggerService.log(METHOD_NAME_VALIDATE$1, {
15920
16036
  context,
15921
16037
  });
15922
16038
  const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
15923
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
15924
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
15925
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
15926
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
15927
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
15928
- actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
16039
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
16040
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
16041
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
16042
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
16043
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
16044
+ actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$1));
15929
16045
  });
15930
16046
  /**
15931
16047
  * Initializes all ClientAction instances for the strategy.
@@ -20958,7 +21074,7 @@ const ReportWriter = new ReportWriterAdapter();
20958
21074
  * @param backtest - Whether running in backtest mode
20959
21075
  * @returns Unique string key for memoization
20960
21076
  */
20961
- const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21077
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
20962
21078
  const parts = [symbol, strategyName, exchangeName];
20963
21079
  if (frameName)
20964
21080
  parts.push(frameName);
@@ -21204,7 +21320,7 @@ class BacktestMarkdownService {
21204
21320
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21205
21321
  * Each combination gets its own isolated storage instance.
21206
21322
  */
21207
- this.getStorage = 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));
21323
+ this.getStorage = 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));
21208
21324
  /**
21209
21325
  * Processes tick events and accumulates closed signals.
21210
21326
  * Should be called from IStrategyCallbacks.onTick.
@@ -21361,7 +21477,7 @@ class BacktestMarkdownService {
21361
21477
  payload,
21362
21478
  });
21363
21479
  if (payload) {
21364
- const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21480
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21365
21481
  this.getStorage.clear(key);
21366
21482
  }
21367
21483
  else {
@@ -21423,7 +21539,7 @@ class BacktestMarkdownService {
21423
21539
  * @param backtest - Whether running in backtest mode
21424
21540
  * @returns Unique string key for memoization
21425
21541
  */
21426
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
21542
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21427
21543
  const parts = [symbol, strategyName, exchangeName];
21428
21544
  if (frameName)
21429
21545
  parts.push(frameName);
@@ -21918,7 +22034,7 @@ class LiveMarkdownService {
21918
22034
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21919
22035
  * Each combination gets its own isolated storage instance.
21920
22036
  */
21921
- this.getStorage = 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));
22037
+ this.getStorage = 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));
21922
22038
  /**
21923
22039
  * Subscribes to live signal emitter to receive tick events.
21924
22040
  * Protected against multiple subscriptions.
@@ -22136,7 +22252,7 @@ class LiveMarkdownService {
22136
22252
  payload,
22137
22253
  });
22138
22254
  if (payload) {
22139
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22255
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22140
22256
  this.getStorage.clear(key);
22141
22257
  }
22142
22258
  else {
@@ -22156,7 +22272,7 @@ class LiveMarkdownService {
22156
22272
  * @param backtest - Whether running in backtest mode
22157
22273
  * @returns Unique string key for memoization
22158
22274
  */
22159
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22275
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22160
22276
  const parts = [symbol, strategyName, exchangeName];
22161
22277
  if (frameName)
22162
22278
  parts.push(frameName);
@@ -22445,7 +22561,7 @@ class ScheduleMarkdownService {
22445
22561
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22446
22562
  * Each combination gets its own isolated storage instance.
22447
22563
  */
22448
- this.getStorage = 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));
22564
+ this.getStorage = 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));
22449
22565
  /**
22450
22566
  * Subscribes to signal emitter to receive scheduled signal events.
22451
22567
  * Protected against multiple subscriptions.
@@ -22648,7 +22764,7 @@ class ScheduleMarkdownService {
22648
22764
  payload,
22649
22765
  });
22650
22766
  if (payload) {
22651
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22767
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22652
22768
  this.getStorage.clear(key);
22653
22769
  }
22654
22770
  else {
@@ -22668,7 +22784,7 @@ class ScheduleMarkdownService {
22668
22784
  * @param backtest - Whether running in backtest mode
22669
22785
  * @returns Unique string key for memoization
22670
22786
  */
22671
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22787
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22672
22788
  const parts = [symbol, strategyName, exchangeName];
22673
22789
  if (frameName)
22674
22790
  parts.push(frameName);
@@ -22913,7 +23029,7 @@ class PerformanceMarkdownService {
22913
23029
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22914
23030
  * Each combination gets its own isolated storage instance.
22915
23031
  */
22916
- this.getStorage = 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));
23032
+ this.getStorage = 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));
22917
23033
  /**
22918
23034
  * Subscribes to performance emitter to receive performance events.
22919
23035
  * Protected against multiple subscriptions.
@@ -23080,7 +23196,7 @@ class PerformanceMarkdownService {
23080
23196
  payload,
23081
23197
  });
23082
23198
  if (payload) {
23083
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23199
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23084
23200
  this.getStorage.clear(key);
23085
23201
  }
23086
23202
  else {
@@ -23559,7 +23675,7 @@ class WalkerMarkdownService {
23559
23675
  * @param backtest - Whether running in backtest mode
23560
23676
  * @returns Unique string key for memoization
23561
23677
  */
23562
- const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
23678
+ const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
23563
23679
  const parts = [exchangeName];
23564
23680
  if (frameName)
23565
23681
  parts.push(frameName);
@@ -24006,7 +24122,7 @@ class HeatMarkdownService {
24006
24122
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
24007
24123
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
24008
24124
  */
24009
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24125
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24010
24126
  /**
24011
24127
  * Subscribes to signal emitter to receive tick events.
24012
24128
  * Protected against multiple subscriptions.
@@ -24224,7 +24340,7 @@ class HeatMarkdownService {
24224
24340
  payload,
24225
24341
  });
24226
24342
  if (payload) {
24227
- const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
24343
+ const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24228
24344
  this.getStorage.clear(key);
24229
24345
  }
24230
24346
  else {
@@ -25255,7 +25371,7 @@ class ClientPartial {
25255
25371
  * @param backtest - Whether running in backtest mode
25256
25372
  * @returns Unique string key for memoization
25257
25373
  */
25258
- const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25374
+ const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25259
25375
  /**
25260
25376
  * Creates a callback function for emitting profit events to partialProfitSubject.
25261
25377
  *
@@ -25377,7 +25493,7 @@ class PartialConnectionService {
25377
25493
  * Key format: "signalId:backtest" or "signalId:live"
25378
25494
  * Value: ClientPartial instance with logger and event emitters
25379
25495
  */
25380
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
25496
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25381
25497
  return new ClientPartial({
25382
25498
  signalId,
25383
25499
  logger: this.loggerService,
@@ -25467,7 +25583,7 @@ class PartialConnectionService {
25467
25583
  const partial = this.getPartial(data.id, backtest);
25468
25584
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25469
25585
  await partial.clear(symbol, data, priceClose, backtest);
25470
- const key = CREATE_KEY_FN$i(data.id, backtest);
25586
+ const key = CREATE_KEY_FN$j(data.id, backtest);
25471
25587
  this.getPartial.clear(key);
25472
25588
  };
25473
25589
  }
@@ -25483,7 +25599,7 @@ class PartialConnectionService {
25483
25599
  * @param backtest - Whether running in backtest mode
25484
25600
  * @returns Unique string key for memoization
25485
25601
  */
25486
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
25602
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
25487
25603
  const parts = [symbol, strategyName, exchangeName];
25488
25604
  if (frameName)
25489
25605
  parts.push(frameName);
@@ -25706,7 +25822,7 @@ class PartialMarkdownService {
25706
25822
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25707
25823
  * Each combination gets its own isolated storage instance.
25708
25824
  */
25709
- this.getStorage = 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));
25825
+ this.getStorage = 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));
25710
25826
  /**
25711
25827
  * Subscribes to partial profit/loss signal emitters to receive events.
25712
25828
  * Protected against multiple subscriptions.
@@ -25916,7 +26032,7 @@ class PartialMarkdownService {
25916
26032
  payload,
25917
26033
  });
25918
26034
  if (payload) {
25919
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26035
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25920
26036
  this.getStorage.clear(key);
25921
26037
  }
25922
26038
  else {
@@ -25932,7 +26048,7 @@ class PartialMarkdownService {
25932
26048
  * @param context - Context with strategyName, exchangeName, frameName
25933
26049
  * @returns Unique string key for memoization
25934
26050
  */
25935
- const CREATE_KEY_FN$g = (context) => {
26051
+ const CREATE_KEY_FN$h = (context) => {
25936
26052
  const parts = [context.strategyName, context.exchangeName];
25937
26053
  if (context.frameName)
25938
26054
  parts.push(context.frameName);
@@ -26006,7 +26122,7 @@ class PartialGlobalService {
26006
26122
  * @param context - Context with strategyName, exchangeName and frameName
26007
26123
  * @param methodName - Name of the calling method for error tracking
26008
26124
  */
26009
- this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
26125
+ this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
26010
26126
  this.loggerService.log("partialGlobalService validate", {
26011
26127
  context,
26012
26128
  methodName,
@@ -26461,7 +26577,7 @@ class ClientBreakeven {
26461
26577
  * @param backtest - Whether running in backtest mode
26462
26578
  * @returns Unique string key for memoization
26463
26579
  */
26464
- const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26580
+ const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26465
26581
  /**
26466
26582
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26467
26583
  *
@@ -26547,7 +26663,7 @@ class BreakevenConnectionService {
26547
26663
  * Key format: "signalId:backtest" or "signalId:live"
26548
26664
  * Value: ClientBreakeven instance with logger and event emitter
26549
26665
  */
26550
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
26666
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
26551
26667
  return new ClientBreakeven({
26552
26668
  signalId,
26553
26669
  logger: this.loggerService,
@@ -26608,7 +26724,7 @@ class BreakevenConnectionService {
26608
26724
  const breakeven = this.getBreakeven(data.id, backtest);
26609
26725
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26610
26726
  await breakeven.clear(symbol, data, priceClose, backtest);
26611
- const key = CREATE_KEY_FN$f(data.id, backtest);
26727
+ const key = CREATE_KEY_FN$g(data.id, backtest);
26612
26728
  this.getBreakeven.clear(key);
26613
26729
  };
26614
26730
  }
@@ -26624,7 +26740,7 @@ class BreakevenConnectionService {
26624
26740
  * @param backtest - Whether running in backtest mode
26625
26741
  * @returns Unique string key for memoization
26626
26742
  */
26627
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
26743
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
26628
26744
  const parts = [symbol, strategyName, exchangeName];
26629
26745
  if (frameName)
26630
26746
  parts.push(frameName);
@@ -26799,7 +26915,7 @@ class BreakevenMarkdownService {
26799
26915
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26800
26916
  * Each combination gets its own isolated storage instance.
26801
26917
  */
26802
- this.getStorage = 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));
26918
+ this.getStorage = 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));
26803
26919
  /**
26804
26920
  * Subscribes to breakeven signal emitter to receive events.
26805
26921
  * Protected against multiple subscriptions.
@@ -26988,7 +27104,7 @@ class BreakevenMarkdownService {
26988
27104
  payload,
26989
27105
  });
26990
27106
  if (payload) {
26991
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27107
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26992
27108
  this.getStorage.clear(key);
26993
27109
  }
26994
27110
  else {
@@ -27004,7 +27120,7 @@ class BreakevenMarkdownService {
27004
27120
  * @param context - Context with strategyName, exchangeName, frameName
27005
27121
  * @returns Unique string key for memoization
27006
27122
  */
27007
- const CREATE_KEY_FN$d = (context) => {
27123
+ const CREATE_KEY_FN$e = (context) => {
27008
27124
  const parts = [context.strategyName, context.exchangeName];
27009
27125
  if (context.frameName)
27010
27126
  parts.push(context.frameName);
@@ -27078,7 +27194,7 @@ class BreakevenGlobalService {
27078
27194
  * @param context - Context with strategyName, exchangeName and frameName
27079
27195
  * @param methodName - Name of the calling method for error tracking
27080
27196
  */
27081
- this.validate = memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
27197
+ this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
27082
27198
  this.loggerService.log("breakevenGlobalService validate", {
27083
27199
  context,
27084
27200
  methodName,
@@ -27299,7 +27415,7 @@ class ConfigValidationService {
27299
27415
  * @param backtest - Whether running in backtest mode
27300
27416
  * @returns Unique string key for memoization
27301
27417
  */
27302
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
27418
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27303
27419
  const parts = [symbol, strategyName, exchangeName];
27304
27420
  if (frameName)
27305
27421
  parts.push(frameName);
@@ -27466,7 +27582,7 @@ class RiskMarkdownService {
27466
27582
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27467
27583
  * Each combination gets its own isolated storage instance.
27468
27584
  */
27469
- this.getStorage = 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));
27585
+ this.getStorage = 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));
27470
27586
  /**
27471
27587
  * Subscribes to risk rejection emitter to receive rejection events.
27472
27588
  * Protected against multiple subscriptions.
@@ -27655,7 +27771,7 @@ class RiskMarkdownService {
27655
27771
  payload,
27656
27772
  });
27657
27773
  if (payload) {
27658
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27774
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27659
27775
  this.getStorage.clear(key);
27660
27776
  }
27661
27777
  else {
@@ -30037,7 +30153,7 @@ class HighestProfitReportService {
30037
30153
  * @returns Colon-separated key string for memoization
30038
30154
  * @internal
30039
30155
  */
30040
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30156
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
30041
30157
  const parts = [symbol, strategyName, exchangeName];
30042
30158
  if (frameName)
30043
30159
  parts.push(frameName);
@@ -30279,7 +30395,7 @@ class StrategyMarkdownService {
30279
30395
  *
30280
30396
  * @internal
30281
30397
  */
30282
- this.getStorage = 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));
30398
+ this.getStorage = 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));
30283
30399
  /**
30284
30400
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30285
30401
  *
@@ -30853,7 +30969,7 @@ class StrategyMarkdownService {
30853
30969
  this.clear = async (payload) => {
30854
30970
  this.loggerService.log("strategyMarkdownService clear", { payload });
30855
30971
  if (payload) {
30856
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30972
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30857
30973
  this.getStorage.clear(key);
30858
30974
  }
30859
30975
  else {
@@ -30961,7 +31077,7 @@ class StrategyMarkdownService {
30961
31077
  * Creates a unique key for memoizing ReportStorage instances.
30962
31078
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30963
31079
  */
30964
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31080
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30965
31081
  const parts = [symbol, strategyName, exchangeName];
30966
31082
  if (frameName)
30967
31083
  parts.push(frameName);
@@ -31154,7 +31270,7 @@ let ReportStorage$2 = class ReportStorage {
31154
31270
  class SyncMarkdownService {
31155
31271
  constructor() {
31156
31272
  this.loggerService = inject(TYPES.loggerService);
31157
- this.getStorage = 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));
31273
+ this.getStorage = 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));
31158
31274
  /**
31159
31275
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31160
31276
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31350,7 +31466,7 @@ class SyncMarkdownService {
31350
31466
  this.clear = async (payload) => {
31351
31467
  this.loggerService.log("syncMarkdownService clear", { payload });
31352
31468
  if (payload) {
31353
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31469
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31354
31470
  this.getStorage.clear(key);
31355
31471
  }
31356
31472
  else {
@@ -31363,7 +31479,7 @@ class SyncMarkdownService {
31363
31479
  /**
31364
31480
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31365
31481
  */
31366
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31482
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31367
31483
  const parts = [symbol, strategyName, exchangeName];
31368
31484
  if (frameName)
31369
31485
  parts.push(frameName);
@@ -31539,7 +31655,7 @@ let ReportStorage$1 = class ReportStorage {
31539
31655
  class HighestProfitMarkdownService {
31540
31656
  constructor() {
31541
31657
  this.loggerService = inject(TYPES.loggerService);
31542
- this.getStorage = 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));
31658
+ this.getStorage = 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));
31543
31659
  /**
31544
31660
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31545
31661
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31705,7 +31821,7 @@ class HighestProfitMarkdownService {
31705
31821
  this.clear = async (payload) => {
31706
31822
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31707
31823
  if (payload) {
31708
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31824
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31709
31825
  this.getStorage.clear(key);
31710
31826
  }
31711
31827
  else {
@@ -31727,7 +31843,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31727
31843
  * @param backtest - Whether running in backtest mode
31728
31844
  * @returns Unique string key for memoization
31729
31845
  */
31730
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31846
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31731
31847
  const parts = [symbol, strategyName, exchangeName];
31732
31848
  if (frameName)
31733
31849
  parts.push(frameName);
@@ -31770,7 +31886,7 @@ class PriceMetaService {
31770
31886
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31771
31887
  * Instances are cached until clear() is called.
31772
31888
  */
31773
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31889
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31774
31890
  /**
31775
31891
  * Returns the current market price for the given symbol and context.
31776
31892
  *
@@ -31799,10 +31915,10 @@ class PriceMetaService {
31799
31915
  if (source.data) {
31800
31916
  return source.data;
31801
31917
  }
31802
- 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...`);
31918
+ 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...`);
31803
31919
  const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31804
31920
  if (typeof currentPrice === "symbol") {
31805
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31921
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31806
31922
  }
31807
31923
  return currentPrice;
31808
31924
  };
@@ -31844,7 +31960,7 @@ class PriceMetaService {
31844
31960
  this.getSource.clear();
31845
31961
  return;
31846
31962
  }
31847
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31963
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31848
31964
  this.getSource.clear(key);
31849
31965
  };
31850
31966
  }
@@ -31862,7 +31978,7 @@ const LISTEN_TIMEOUT = 120000;
31862
31978
  * @param backtest - Whether running in backtest mode
31863
31979
  * @returns Unique string key for memoization
31864
31980
  */
31865
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31981
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31866
31982
  const parts = [symbol, strategyName, exchangeName];
31867
31983
  if (frameName)
31868
31984
  parts.push(frameName);
@@ -31905,7 +32021,7 @@ class TimeMetaService {
31905
32021
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31906
32022
  * Instances are cached until clear() is called.
31907
32023
  */
31908
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
32024
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31909
32025
  /**
31910
32026
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31911
32027
  *
@@ -31933,10 +32049,10 @@ class TimeMetaService {
31933
32049
  if (source.data) {
31934
32050
  return source.data;
31935
32051
  }
31936
- 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...`);
32052
+ 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...`);
31937
32053
  const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31938
32054
  if (typeof timestamp === "symbol") {
31939
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32055
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31940
32056
  }
31941
32057
  return timestamp;
31942
32058
  };
@@ -31978,7 +32094,7 @@ class TimeMetaService {
31978
32094
  this.getSource.clear();
31979
32095
  return;
31980
32096
  }
31981
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32097
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31982
32098
  this.getSource.clear(key);
31983
32099
  };
31984
32100
  }
@@ -32074,7 +32190,7 @@ class MaxDrawdownReportService {
32074
32190
  /**
32075
32191
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
32076
32192
  */
32077
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32193
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32078
32194
  const parts = [symbol, strategyName, exchangeName];
32079
32195
  if (frameName)
32080
32196
  parts.push(frameName);
@@ -32198,7 +32314,7 @@ class ReportStorage {
32198
32314
  class MaxDrawdownMarkdownService {
32199
32315
  constructor() {
32200
32316
  this.loggerService = inject(TYPES.loggerService);
32201
- this.getStorage = 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));
32317
+ this.getStorage = 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));
32202
32318
  /**
32203
32319
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32204
32320
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32277,7 +32393,7 @@ class MaxDrawdownMarkdownService {
32277
32393
  this.clear = async (payload) => {
32278
32394
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32279
32395
  if (payload) {
32280
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32396
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32281
32397
  this.getStorage.clear(key);
32282
32398
  }
32283
32399
  else {
@@ -32287,6 +32403,125 @@ class MaxDrawdownMarkdownService {
32287
32403
  }
32288
32404
  }
32289
32405
 
32406
+ const METHOD_NAME_COMMIT_SIGNAL_NOTIFY = "notificationHelperService.commitSignalNotify";
32407
+ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32408
+ /**
32409
+ * Creates a unique key for memoizing validate calls.
32410
+ * Key format: "strategyName:exchangeName:frameName"
32411
+ * @param context - Execution context with strategyName, exchangeName, frameName
32412
+ * @returns Unique string key for memoization
32413
+ */
32414
+ const CREATE_KEY_FN$6 = (context) => {
32415
+ const parts = [context.strategyName, context.exchangeName];
32416
+ if (context.frameName)
32417
+ parts.push(context.frameName);
32418
+ return parts.join(":");
32419
+ };
32420
+ /**
32421
+ * Helper service for emitting signal info notifications.
32422
+ *
32423
+ * Handles validation (memoized per context) and emission of `signal.info` events
32424
+ * via `signalNotifySubject`. Used internally by the framework action pipeline —
32425
+ * end users interact with this via `commitSignalNotify()` in `onActivePing` callbacks.
32426
+ */
32427
+ class NotificationHelperService {
32428
+ constructor() {
32429
+ this.loggerService = inject(TYPES.loggerService);
32430
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
32431
+ this.riskValidationService = inject(TYPES.riskValidationService);
32432
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
32433
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
32434
+ this.frameValidationService = inject(TYPES.frameValidationService);
32435
+ this.actionValidationService = inject(TYPES.actionValidationService);
32436
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
32437
+ this.timeMetaService = inject(TYPES.timeMetaService);
32438
+ /**
32439
+ * Validates strategy, exchange, frame, risk, and action schemas for the given context.
32440
+ *
32441
+ * Memoized per unique `"strategyName:exchangeName[:frameName]"` key — subsequent calls
32442
+ * with the same context are no-ops, so validation runs at most once per context.
32443
+ *
32444
+ * @param context - Routing context: strategyName, exchangeName, frameName
32445
+ * @throws {Error} If any registered schema fails validation
32446
+ */
32447
+ this.validate = memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
32448
+ this.loggerService.log(METHOD_NAME_VALIDATE, {
32449
+ context,
32450
+ });
32451
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
32452
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
32453
+ const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
32454
+ context.frameName &&
32455
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
32456
+ riskName &&
32457
+ this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
32458
+ riskList &&
32459
+ riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
32460
+ actions &&
32461
+ actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
32462
+ });
32463
+ /**
32464
+ * Emits a `signal.info` notification for the currently active pending signal.
32465
+ *
32466
+ * Validates all schemas (via memoized `validate`), resolves the pending signal
32467
+ * for the given symbol, then emits a `SignalInfoContract` via `signalNotifySubject`,
32468
+ * which is routed to all registered `listenSignalNotify` callbacks and persisted
32469
+ * by `NotificationAdapter`.
32470
+ *
32471
+ * @param payload - Optional notification fields (notificationId, notificationNote)
32472
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
32473
+ * @param currentPrice - Market price at the time of the call
32474
+ * @param context - Routing context: strategyName, exchangeName, frameName
32475
+ * @param backtest - true when called during a backtest run
32476
+ *
32477
+ * @throws {Error} If no active pending signal is found for the given symbol
32478
+ *
32479
+ * @example
32480
+ * ```typescript
32481
+ * // Inside onActivePing callback:
32482
+ * await commitSignalNotify("BTCUSDT", {
32483
+ * notificationNote: "RSI crossed 70, consider closing",
32484
+ * notificationId: "msg-123",
32485
+ * });
32486
+ * ```
32487
+ */
32488
+ this.commitSignalNotify = async (payload, symbol, currentPrice, context, backtest) => {
32489
+ this.loggerService.info(METHOD_NAME_COMMIT_SIGNAL_NOTIFY, {
32490
+ symbol,
32491
+ context,
32492
+ backtest,
32493
+ currentPrice,
32494
+ });
32495
+ this.validate(context);
32496
+ const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32497
+ strategyName: context.strategyName,
32498
+ exchangeName: context.exchangeName,
32499
+ frameName: context.frameName,
32500
+ });
32501
+ if (!pendingSignal) {
32502
+ throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
32503
+ }
32504
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, {
32505
+ strategyName: context.strategyName,
32506
+ exchangeName: context.exchangeName,
32507
+ frameName: context.frameName,
32508
+ }, backtest);
32509
+ await signalNotifySubject.next({
32510
+ backtest,
32511
+ symbol,
32512
+ currentPrice,
32513
+ data: pendingSignal,
32514
+ exchangeName: context.exchangeName,
32515
+ strategyName: context.strategyName,
32516
+ frameName: context.frameName,
32517
+ note: payload.notificationNote || pendingSignal.note,
32518
+ notificationId: payload.notificationId,
32519
+ timestamp,
32520
+ });
32521
+ };
32522
+ }
32523
+ }
32524
+
32290
32525
  {
32291
32526
  provide(TYPES.loggerService, () => new LoggerService());
32292
32527
  }
@@ -32375,6 +32610,9 @@ class MaxDrawdownMarkdownService {
32375
32610
  provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
32376
32611
  provide(TYPES.maxDrawdownReportService, () => new MaxDrawdownReportService());
32377
32612
  }
32613
+ {
32614
+ provide(TYPES.notificationHelperService, () => new NotificationHelperService());
32615
+ }
32378
32616
  {
32379
32617
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
32380
32618
  provide(TYPES.strategyValidationService, () => new StrategyValidationService());
@@ -32475,6 +32713,9 @@ const reportServices = {
32475
32713
  highestProfitReportService: inject(TYPES.highestProfitReportService),
32476
32714
  maxDrawdownReportService: inject(TYPES.maxDrawdownReportService),
32477
32715
  };
32716
+ const helperServices = {
32717
+ notificationHelperService: inject(TYPES.notificationHelperService),
32718
+ };
32478
32719
  const validationServices = {
32479
32720
  exchangeValidationService: inject(TYPES.exchangeValidationService),
32480
32721
  strategyValidationService: inject(TYPES.strategyValidationService),
@@ -32500,6 +32741,7 @@ const backtest = {
32500
32741
  ...markdownServices,
32501
32742
  ...reportServices,
32502
32743
  ...validationServices,
32744
+ ...helperServices,
32503
32745
  };
32504
32746
  init();
32505
32747
 
@@ -35394,6 +35636,8 @@ const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
35394
35636
  const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
35395
35637
  const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
35396
35638
  const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
35639
+ const GET_POSITION_ACTIVE_MINUTES_METHOD_NAME = "strategy.getPositionActiveMinutes";
35640
+ const GET_POSITION_WAITING_MINUTES_METHOD_NAME = "strategy.getPositionWaitingMinutes";
35397
35641
  const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
35398
35642
  const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
35399
35643
  const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
@@ -35416,6 +35660,7 @@ const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap
35416
35660
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35417
35661
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
35418
35662
  const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35663
+ const COMMIT_SIGNAL_NOTIFY_METHOD_NAME = "strategy.commitSignalNotify";
35419
35664
  /**
35420
35665
  * Cancels the scheduled signal without stopping the strategy.
35421
35666
  *
@@ -36693,6 +36938,62 @@ async function getPositionCountdownMinutes(symbol) {
36693
36938
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36694
36939
  return await backtest.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36695
36940
  }
36941
+ /**
36942
+ * Returns the number of minutes the position has been active since it opened.
36943
+ *
36944
+ * Returns null if no pending signal exists.
36945
+ *
36946
+ * @param symbol - Trading pair symbol
36947
+ * @returns Promise resolving to active minutes (≥ 0) or null
36948
+ *
36949
+ * @example
36950
+ * ```typescript
36951
+ * import { getPositionActiveMinutes } from "backtest-kit";
36952
+ *
36953
+ * const minutes = await getPositionActiveMinutes("BTCUSDT");
36954
+ * // e.g. 120 (position has been open for 2 hours)
36955
+ * ```
36956
+ */
36957
+ async function getPositionActiveMinutes(symbol) {
36958
+ backtest.loggerService.info(GET_POSITION_ACTIVE_MINUTES_METHOD_NAME, { symbol });
36959
+ if (!ExecutionContextService.hasContext()) {
36960
+ throw new Error("getPositionActiveMinutes requires an execution context");
36961
+ }
36962
+ if (!MethodContextService.hasContext()) {
36963
+ throw new Error("getPositionActiveMinutes requires a method context");
36964
+ }
36965
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36966
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36967
+ return await backtest.strategyCoreService.getPositionActiveMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36968
+ }
36969
+ /**
36970
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
36971
+ *
36972
+ * Returns null if no scheduled signal exists.
36973
+ *
36974
+ * @param symbol - Trading pair symbol
36975
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
36976
+ *
36977
+ * @example
36978
+ * ```typescript
36979
+ * import { getPositionWaitingMinutes } from "backtest-kit";
36980
+ *
36981
+ * const minutes = await getPositionWaitingMinutes("BTCUSDT");
36982
+ * // e.g. 15 (scheduled signal has been waiting 15 minutes for activation)
36983
+ * ```
36984
+ */
36985
+ async function getPositionWaitingMinutes(symbol) {
36986
+ backtest.loggerService.info(GET_POSITION_WAITING_MINUTES_METHOD_NAME, { symbol });
36987
+ if (!ExecutionContextService.hasContext()) {
36988
+ throw new Error("getPositionWaitingMinutes requires an execution context");
36989
+ }
36990
+ if (!MethodContextService.hasContext()) {
36991
+ throw new Error("getPositionWaitingMinutes requires a method context");
36992
+ }
36993
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36994
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36995
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36996
+ }
36696
36997
  /**
36697
36998
  * Returns the best price reached in the profit direction during this position's life.
36698
36999
  *
@@ -37380,6 +37681,50 @@ async function hasNoScheduledSignal(symbol) {
37380
37681
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37381
37682
  return await not(backtest.strategyCoreService.hasScheduledSignal(isBacktest, symbol, { exchangeName, frameName, strategyName }));
37382
37683
  }
37684
+ /**
37685
+ * Emits a `signal.info` notification for the currently active pending signal.
37686
+ *
37687
+ * Broadcasts a user-defined informational note without affecting position state.
37688
+ * Useful for annotating strategy decisions, triggering external alerts, or logging
37689
+ * mid-position events (e.g. RSI crossing a threshold, volume spike detected).
37690
+ *
37691
+ * Automatically reads backtest/live mode from execution context.
37692
+ * Automatically reads strategyName, exchangeName, frameName from method context.
37693
+ * Automatically fetches current price via getAveragePrice.
37694
+ *
37695
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
37696
+ * @param payload - Optional notification fields
37697
+ * @param payload.notificationNote - Human-readable note. Falls back to signal.note if omitted.
37698
+ * @param payload.notificationId - Optional correlation ID for external systems (e.g. Telegram message ID).
37699
+ *
37700
+ * @throws {Error} If called outside an execution context
37701
+ * @throws {Error} If called outside a method context
37702
+ * @throws {Error} If no active pending signal exists for the given symbol
37703
+ *
37704
+ * @example
37705
+ * ```typescript
37706
+ * import { commitSignalNotify } from "backtest-kit";
37707
+ *
37708
+ * // Inside onActivePing callback:
37709
+ * await commitSignalNotify("BTCUSDT", {
37710
+ * notificationNote: "RSI crossed 70, consider closing",
37711
+ * notificationId: "msg-123",
37712
+ * });
37713
+ * ```
37714
+ */
37715
+ async function commitSignalNotify(symbol, payload = {}) {
37716
+ backtest.loggerService.info(COMMIT_SIGNAL_NOTIFY_METHOD_NAME, { symbol, payload });
37717
+ if (!ExecutionContextService.hasContext()) {
37718
+ throw new Error("commitSignalNotify requires an execution context");
37719
+ }
37720
+ if (!MethodContextService.hasContext()) {
37721
+ throw new Error("commitSignalNotify requires a method context");
37722
+ }
37723
+ const currentPrice = await getAveragePrice(symbol);
37724
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37725
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37726
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, { strategyName, exchangeName, frameName }, isBacktest);
37727
+ }
37383
37728
 
37384
37729
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
37385
37730
  /**
@@ -37463,6 +37808,8 @@ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
37463
37808
  const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
37464
37809
  const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
37465
37810
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
37811
+ const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
37812
+ const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
37466
37813
  /**
37467
37814
  * Subscribes to all signal events with queued async processing.
37468
37815
  *
@@ -38854,6 +39201,46 @@ function listenMaxDrawdownOnce(filterFn, fn) {
38854
39201
  };
38855
39202
  return disposeFn = listenMaxDrawdown(wrappedFn);
38856
39203
  }
39204
+ /**
39205
+ * Subscribes to signal info events with queued async processing.
39206
+ * Emits when a strategy calls commitSignalInfo() to broadcast a user-defined note for an open position.
39207
+ * Events are processed sequentially in order received, even if callback is async.
39208
+ * Uses queued wrapper to prevent concurrent execution of the callback.
39209
+ * @param fn - Callback function to handle signal info events
39210
+ * @return Unsubscribe function to stop listening to events
39211
+ */
39212
+ function listenSignalNotify(fn) {
39213
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_METHOD_NAME);
39214
+ const wrappedFn = async (event) => {
39215
+ if (await backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39216
+ strategyName: event.strategyName,
39217
+ exchangeName: event.exchangeName,
39218
+ frameName: event.frameName,
39219
+ })) {
39220
+ await fn(event);
39221
+ }
39222
+ };
39223
+ return signalNotifySubject.subscribe(queued(wrappedFn));
39224
+ }
39225
+ /**
39226
+ * Subscribes to filtered signal info events with one-time execution.
39227
+ * Listens for events matching the filter predicate, then executes callback once
39228
+ * and automatically unsubscribes.
39229
+ * @param filterFn - Predicate to filter which events trigger the callback
39230
+ * @param fn - Callback function to handle the filtered event (called only once)
39231
+ * @return Unsubscribe function to cancel the listener before it fires
39232
+ */
39233
+ function listenSignalNotifyOnce(filterFn, fn) {
39234
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME);
39235
+ let disposeFn;
39236
+ const wrappedFn = async (event) => {
39237
+ if (filterFn(event)) {
39238
+ await fn(event);
39239
+ disposeFn && disposeFn();
39240
+ }
39241
+ };
39242
+ return disposeFn = listenSignalNotify(wrappedFn);
39243
+ }
38857
39244
 
38858
39245
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
38859
39246
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -38877,6 +39264,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPar
38877
39264
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
38878
39265
  const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
38879
39266
  const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
39267
+ const BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "BacktestUtils.getPositionActiveMinutes";
39268
+ const BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "BacktestUtils.getPositionWaitingMinutes";
38880
39269
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
38881
39270
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
38882
39271
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
@@ -38910,6 +39299,7 @@ const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingSto
38910
39299
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
38911
39300
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
38912
39301
  const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
39302
+ const BACKTEST_METHOD_NAME_SIGNAL_NOTIFY = "Backtest.commitSignalNotify";
38913
39303
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
38914
39304
  const BACKTEST_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "BacktestUtils.hasNoPendingSignal";
38915
39305
  const BACKTEST_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "BacktestUtils.hasNoScheduledSignal";
@@ -39831,6 +40221,60 @@ class BacktestUtils {
39831
40221
  }
39832
40222
  return await backtest.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
39833
40223
  };
40224
+ /**
40225
+ * Returns the number of minutes the position has been active since it opened.
40226
+ *
40227
+ * Returns null if no pending signal exists.
40228
+ *
40229
+ * @param symbol - Trading pair symbol
40230
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40231
+ * @returns Active minutes (≥ 0), or null if no active position
40232
+ */
40233
+ this.getPositionActiveMinutes = async (symbol, context) => {
40234
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
40235
+ symbol,
40236
+ context,
40237
+ });
40238
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40239
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40240
+ {
40241
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40242
+ riskName &&
40243
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40244
+ riskList &&
40245
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40246
+ actions &&
40247
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40248
+ }
40249
+ return await backtest.strategyCoreService.getPositionActiveMinutes(true, symbol, context);
40250
+ };
40251
+ /**
40252
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
40253
+ *
40254
+ * Returns null if no scheduled signal exists.
40255
+ *
40256
+ * @param symbol - Trading pair symbol
40257
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40258
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
40259
+ */
40260
+ this.getPositionWaitingMinutes = async (symbol, context) => {
40261
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
40262
+ symbol,
40263
+ context,
40264
+ });
40265
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40266
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40267
+ {
40268
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40269
+ riskName &&
40270
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40271
+ riskList &&
40272
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40273
+ actions &&
40274
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40275
+ }
40276
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(true, symbol, context);
40277
+ };
39834
40278
  /**
39835
40279
  * Returns the best price reached in the profit direction during this position's life.
39836
40280
  *
@@ -41265,6 +41709,33 @@ class BacktestUtils {
41265
41709
  });
41266
41710
  return await backtest.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
41267
41711
  };
41712
+ /**
41713
+ * Emits a `signal.info` notification for the currently active pending signal.
41714
+ *
41715
+ * @param symbol - Trading pair symbol
41716
+ * @param currentPrice - Market price at the time of the call
41717
+ * @param context - Execution context with strategyName, exchangeName, frameName
41718
+ * @param payload - Optional notification fields (notificationNote, notificationId)
41719
+ *
41720
+ * @throws {Error} If no active pending signal exists for the given symbol
41721
+ *
41722
+ * @example
41723
+ * ```typescript
41724
+ * await Backtest.commitSignalNotify("BTCUSDT", 42000, {
41725
+ * strategyName: "my-strategy",
41726
+ * exchangeName: "binance",
41727
+ * frameName: "1h"
41728
+ * }, { notificationNote: "RSI crossed 70" });
41729
+ * ```
41730
+ */
41731
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
41732
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_SIGNAL_NOTIFY, {
41733
+ symbol,
41734
+ currentPrice,
41735
+ context,
41736
+ });
41737
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, context, true);
41738
+ };
41268
41739
  /**
41269
41740
  * Gets statistical data from all closed signals for a symbol-strategy pair.
41270
41741
  *
@@ -41445,6 +41916,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
41445
41916
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
41446
41917
  const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
41447
41918
  const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
41919
+ const LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "LiveUtils.getPositionActiveMinutes";
41920
+ const LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "LiveUtils.getPositionWaitingMinutes";
41448
41921
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
41449
41922
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
41450
41923
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
@@ -41478,6 +41951,7 @@ const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
41478
41951
  const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
41479
41952
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
41480
41953
  const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
41954
+ const LIVE_METHOD_NAME_SIGNAL_NOTIFY = "Live.commitSignalNotify";
41481
41955
  const LIVE_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "LiveUtils.hasNoPendingSignal";
41482
41956
  const LIVE_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "LiveUtils.hasNoScheduledSignal";
41483
41957
  /**
@@ -42478,6 +42952,68 @@ class LiveUtils {
42478
42952
  frameName: "",
42479
42953
  });
42480
42954
  };
42955
+ /**
42956
+ * Returns the number of minutes the position has been active since it opened.
42957
+ *
42958
+ * Returns null if no pending signal exists.
42959
+ *
42960
+ * @param symbol - Trading pair symbol
42961
+ * @param context - Execution context with strategyName and exchangeName
42962
+ * @returns Active minutes (≥ 0), or null if no active position
42963
+ */
42964
+ this.getPositionActiveMinutes = async (symbol, context) => {
42965
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
42966
+ symbol,
42967
+ context,
42968
+ });
42969
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42970
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42971
+ {
42972
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42973
+ riskName &&
42974
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
42975
+ riskList &&
42976
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
42977
+ actions &&
42978
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
42979
+ }
42980
+ return await backtest.strategyCoreService.getPositionActiveMinutes(false, symbol, {
42981
+ strategyName: context.strategyName,
42982
+ exchangeName: context.exchangeName,
42983
+ frameName: "",
42984
+ });
42985
+ };
42986
+ /**
42987
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
42988
+ *
42989
+ * Returns null if no scheduled signal exists.
42990
+ *
42991
+ * @param symbol - Trading pair symbol
42992
+ * @param context - Execution context with strategyName and exchangeName
42993
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
42994
+ */
42995
+ this.getPositionWaitingMinutes = async (symbol, context) => {
42996
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
42997
+ symbol,
42998
+ context,
42999
+ });
43000
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43001
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43002
+ {
43003
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43004
+ riskName &&
43005
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43006
+ riskList &&
43007
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43008
+ actions &&
43009
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43010
+ }
43011
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(false, symbol, {
43012
+ strategyName: context.strategyName,
43013
+ exchangeName: context.exchangeName,
43014
+ frameName: "",
43015
+ });
43016
+ };
42481
43017
  /**
42482
43018
  * Returns the best price reached in the profit direction during this position's life.
42483
43019
  *
@@ -44174,6 +44710,36 @@ class LiveUtils {
44174
44710
  frameName: "",
44175
44711
  }, cost);
44176
44712
  };
44713
+ /**
44714
+ * Emits a `signal.info` notification for the currently active pending signal.
44715
+ *
44716
+ * @param symbol - Trading pair symbol
44717
+ * @param currentPrice - Market price at the time of the call
44718
+ * @param context - Execution context with strategyName and exchangeName
44719
+ * @param payload - Optional notification fields (notificationNote, notificationId)
44720
+ *
44721
+ * @throws {Error} If no active pending signal exists for the given symbol
44722
+ *
44723
+ * @example
44724
+ * ```typescript
44725
+ * await Live.commitSignalNotify("BTCUSDT", 42000, {
44726
+ * strategyName: "my-strategy",
44727
+ * exchangeName: "binance",
44728
+ * }, { notificationNote: "RSI crossed 70" });
44729
+ * ```
44730
+ */
44731
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
44732
+ backtest.loggerService.info(LIVE_METHOD_NAME_SIGNAL_NOTIFY, {
44733
+ symbol,
44734
+ currentPrice,
44735
+ context,
44736
+ });
44737
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, {
44738
+ strategyName: context.strategyName,
44739
+ exchangeName: context.exchangeName,
44740
+ frameName: "",
44741
+ }, false);
44742
+ };
44177
44743
  /**
44178
44744
  * Gets statistical data from all live trading events for a symbol-strategy pair.
44179
44745
  *
@@ -47901,7 +48467,7 @@ const LOGGER_SERVICE$2 = new LoggerService();
47901
48467
  * Default configuration that enables all report services.
47902
48468
  * Used when no specific configuration is provided to enable().
47903
48469
  */
47904
- const WILDCARD_TARGET$1 = {
48470
+ const WILDCARD_TARGET$2 = {
47905
48471
  backtest: true,
47906
48472
  strategy: true,
47907
48473
  breakeven: true,
@@ -47952,7 +48518,7 @@ class ReportUtils {
47952
48518
  *
47953
48519
  * @returns Cleanup function that unsubscribes from all enabled services
47954
48520
  */
47955
- 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) => {
48521
+ 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) => {
47956
48522
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
47957
48523
  backtest: bt,
47958
48524
  breakeven,
@@ -48044,7 +48610,7 @@ class ReportUtils {
48044
48610
  * Report.disable();
48045
48611
  * ```
48046
48612
  */
48047
- 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) => {
48613
+ 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) => {
48048
48614
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
48049
48615
  backtest: bt,
48050
48616
  breakeven,
@@ -48168,7 +48734,7 @@ const LOGGER_SERVICE$1 = new LoggerService();
48168
48734
  * Default configuration that enables all markdown services.
48169
48735
  * Used when no specific configuration is provided to `enable()`.
48170
48736
  */
48171
- const WILDCARD_TARGET = {
48737
+ const WILDCARD_TARGET$1 = {
48172
48738
  backtest: true,
48173
48739
  breakeven: true,
48174
48740
  heat: true,
@@ -48219,7 +48785,7 @@ class MarkdownUtils {
48219
48785
  *
48220
48786
  * @returns Cleanup function that unsubscribes from all enabled services
48221
48787
  */
48222
- 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) => {
48788
+ 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) => {
48223
48789
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_ENABLE, {
48224
48790
  backtest: bt,
48225
48791
  breakeven,
@@ -48313,7 +48879,7 @@ class MarkdownUtils {
48313
48879
  * Markdown.disable();
48314
48880
  * ```
48315
48881
  */
48316
- 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) => {
48882
+ 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) => {
48317
48883
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_DISABLE, {
48318
48884
  backtest: bt,
48319
48885
  breakeven,
@@ -48368,6 +48934,87 @@ class MarkdownUtils {
48368
48934
  backtest.maxDrawdownMarkdownService.unsubscribe();
48369
48935
  }
48370
48936
  };
48937
+ /**
48938
+ * Clears markdown report data selectively.
48939
+ *
48940
+ * Clears accumulated data for specified markdown services without unsubscribing.
48941
+ * Use this method to reset report data for specific services while keeping them active.
48942
+ *
48943
+ * Each cleared service will:
48944
+ * - Clear accumulated data for all reports
48945
+ * - Start fresh with new data for future events
48946
+ * - Not affect event subscriptions or report generation status
48947
+ *
48948
+ * @param config - Service configuration object specifying which services to clear. Defaults to clearing all services.
48949
+ * @param config.backtest - Clear backtest result report data
48950
+ * @param config.breakeven - Clear breakeven event tracking data
48951
+ * @param config.partial - Clear partial profit/loss event tracking data
48952
+ * @param config.heat - Clear portfolio heatmap analysis data
48953
+ * @param config.walker - Clear walker strategy comparison report data
48954
+ * @param config.performance - Clear performance bottleneck analysis data
48955
+ * @param config.risk - Clear risk rejection tracking data
48956
+ * @param config.schedule - Clear scheduled signal tracking data
48957
+ * @param config.live - Clear live trading event report data
48958
+ * @param config.strategy - Clear strategy report data
48959
+ * @param config.sync - Clear sync report data
48960
+ * @param config.highest_profit - Clear highest profit report data
48961
+ * @param config.max_drawdown - Clear max drawdown report data
48962
+ */
48963
+ 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) => {
48964
+ LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48965
+ backtest: bt,
48966
+ breakeven,
48967
+ heat,
48968
+ live,
48969
+ partial,
48970
+ performance,
48971
+ risk,
48972
+ strategy,
48973
+ schedule,
48974
+ walker,
48975
+ sync,
48976
+ highest_profit,
48977
+ });
48978
+ if (bt) {
48979
+ backtest.backtestMarkdownService.clear();
48980
+ }
48981
+ if (breakeven) {
48982
+ backtest.breakevenMarkdownService.clear();
48983
+ }
48984
+ if (heat) {
48985
+ backtest.heatMarkdownService.clear();
48986
+ }
48987
+ if (live) {
48988
+ backtest.liveMarkdownService.clear();
48989
+ }
48990
+ if (partial) {
48991
+ backtest.partialMarkdownService.clear();
48992
+ }
48993
+ if (performance) {
48994
+ backtest.performanceMarkdownService.clear();
48995
+ }
48996
+ if (risk) {
48997
+ backtest.riskMarkdownService.clear();
48998
+ }
48999
+ if (strategy) {
49000
+ backtest.strategyMarkdownService.clear();
49001
+ }
49002
+ if (schedule) {
49003
+ backtest.scheduleMarkdownService.clear();
49004
+ }
49005
+ if (walker) {
49006
+ backtest.walkerMarkdownService.clear();
49007
+ }
49008
+ if (sync) {
49009
+ backtest.syncMarkdownService.clear();
49010
+ }
49011
+ if (highest_profit) {
49012
+ backtest.highestProfitMarkdownService.clear();
49013
+ }
49014
+ if (max_drawdown) {
49015
+ backtest.maxDrawdownMarkdownService.clear();
49016
+ }
49017
+ };
48371
49018
  }
48372
49019
  }
48373
49020
  /**
@@ -48410,15 +49057,6 @@ class MarkdownAdapter extends MarkdownUtils {
48410
49057
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
48411
49058
  MarkdownWriter.useJsonl();
48412
49059
  }
48413
- /**
48414
- * Clears the memoized storage cache.
48415
- * Call this when process.cwd() changes between strategy iterations
48416
- * so new storage instances are created with the updated base path.
48417
- */
48418
- clear() {
48419
- LOGGER_SERVICE$1.log(MARKDOWN_METHOD_NAME_CLEAR);
48420
- MarkdownWriter.clear();
48421
- }
48422
49060
  /**
48423
49061
  * Switches to a dummy markdown adapter that discards all writes.
48424
49062
  * All future markdown writes will be no-ops.
@@ -49115,6 +49753,7 @@ const SUBJECT_ISOLATION_LIST = [
49115
49753
  strategyCommitSubject,
49116
49754
  syncSubject,
49117
49755
  validationSubject,
49756
+ signalNotifySubject,
49118
49757
  ];
49119
49758
  /**
49120
49759
  * Creates a snapshot function for a given subject by clearing its internal
@@ -50733,6 +51372,8 @@ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.
50733
51372
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
50734
51373
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
50735
51374
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
51375
+ const REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "ReflectUtils.getPositionActiveMinutes";
51376
+ const REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "ReflectUtils.getPositionWaitingMinutes";
50736
51377
  const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
50737
51378
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
50738
51379
  const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
@@ -51007,6 +51648,52 @@ class ReflectUtils {
51007
51648
  }
51008
51649
  return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
51009
51650
  };
51651
+ /**
51652
+ * Returns the number of minutes the position has been active since it opened.
51653
+ *
51654
+ * Returns null if no pending signal exists.
51655
+ *
51656
+ * @param symbol - Trading pair symbol
51657
+ * @param context - Execution context with strategyName, exchangeName and frameName
51658
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51659
+ * @returns Promise resolving to active minutes (≥ 0) or null
51660
+ */
51661
+ this.getPositionActiveMinutes = async (symbol, context, backtest$1 = false) => {
51662
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, { symbol, context });
51663
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51664
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51665
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51666
+ {
51667
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51668
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
51669
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
51670
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
51671
+ }
51672
+ return await backtest.strategyCoreService.getPositionActiveMinutes(backtest$1, symbol, context);
51673
+ };
51674
+ /**
51675
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
51676
+ *
51677
+ * Returns null if no scheduled signal exists.
51678
+ *
51679
+ * @param symbol - Trading pair symbol
51680
+ * @param context - Execution context with strategyName, exchangeName and frameName
51681
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51682
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
51683
+ */
51684
+ this.getPositionWaitingMinutes = async (symbol, context, backtest$1 = false) => {
51685
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES, { symbol, context });
51686
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51687
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51688
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51689
+ {
51690
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51691
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
51692
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
51693
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
51694
+ }
51695
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(backtest$1, symbol, context);
51696
+ };
51010
51697
  /**
51011
51698
  * Returns the number of minutes elapsed since the highest profit price was recorded.
51012
51699
  *
@@ -53283,6 +53970,23 @@ const StorageLive = new StorageLiveAdapter();
53283
53970
  */
53284
53971
  const StorageBacktest = new StorageBacktestAdapter();
53285
53972
 
53973
+ /**
53974
+ * Default configuration that enables all notification types.
53975
+ * Used when no specific configuration is provided to enable().
53976
+ */
53977
+ const WILDCARD_TARGET = {
53978
+ signal: true,
53979
+ partial_profit: true,
53980
+ partial_loss: true,
53981
+ breakeven: true,
53982
+ strategy_commit: true,
53983
+ signal_sync: true,
53984
+ risk: true,
53985
+ info: true,
53986
+ common_error: true,
53987
+ critical_error: true,
53988
+ validation_error: true,
53989
+ };
53286
53990
  /**
53287
53991
  * Generates a unique key for notification identification.
53288
53992
  * @returns Random string identifier
@@ -53960,7 +54664,44 @@ const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
53960
54664
  message: getErrorMessage(error),
53961
54665
  backtest: false,
53962
54666
  });
54667
+ /**
54668
+ * Creates a notification model for signal info events.
54669
+ * @param data - The signal info contract data
54670
+ * @returns NotificationModel for signal info event
54671
+ */
54672
+ const CREATE_SIGNAL_INFO_NOTIFICATION_FN = (data) => ({
54673
+ type: "signal.info",
54674
+ id: CREATE_KEY_FN$2(),
54675
+ timestamp: data.timestamp,
54676
+ backtest: data.backtest,
54677
+ symbol: data.symbol,
54678
+ strategyName: data.strategyName,
54679
+ exchangeName: data.exchangeName,
54680
+ signalId: data.data.id,
54681
+ currentPrice: data.currentPrice,
54682
+ position: data.data.position,
54683
+ priceOpen: data.data.priceOpen,
54684
+ priceTakeProfit: data.data.priceTakeProfit,
54685
+ priceStopLoss: data.data.priceStopLoss,
54686
+ originalPriceTakeProfit: data.data.originalPriceTakeProfit,
54687
+ originalPriceStopLoss: data.data.originalPriceStopLoss,
54688
+ originalPriceOpen: data.data.originalPriceOpen,
54689
+ totalEntries: data.data.totalEntries,
54690
+ totalPartials: data.data.totalPartials,
54691
+ pnl: data.data.pnl,
54692
+ pnlPercentage: data.data.pnl.pnlPercentage,
54693
+ pnlPriceOpen: data.data.pnl.priceOpen,
54694
+ pnlPriceClose: data.data.pnl.priceClose,
54695
+ pnlCost: data.data.pnl.pnlCost,
54696
+ pnlEntries: data.data.pnl.pnlEntries,
54697
+ note: data.note,
54698
+ notificationId: data.notificationId,
54699
+ scheduledAt: data.data.scheduledAt,
54700
+ pendingAt: data.data.pendingAt,
54701
+ createdAt: data.timestamp,
54702
+ });
53963
54703
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryBacktestUtils.handleSignal";
54704
+ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryBacktestUtils.handleSignalNotify";
53964
54705
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryBacktestUtils.handlePartialProfit";
53965
54706
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryBacktestUtils.handlePartialLoss";
53966
54707
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryBacktestUtils.handleBreakeven";
@@ -53973,6 +54714,7 @@ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "Notifi
53973
54714
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA = "NotificationMemoryBacktestUtils.getData";
53974
54715
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_DISPOSE = "NotificationMemoryBacktestUtils.dispose";
53975
54716
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryLiveUtils.handleSignal";
54717
+ const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryLiveUtils.handleSignalNotify";
53976
54718
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryLiveUtils.handlePartialProfit";
53977
54719
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryLiveUtils.handlePartialLoss";
53978
54720
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryLiveUtils.handleBreakeven";
@@ -54001,6 +54743,7 @@ const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_CLEAR = "NotificationLiveAdapter.cle
54001
54743
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistBacktestUtils.waitForInit";
54002
54744
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistBacktestUtils._updateNotifications";
54003
54745
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistBacktestUtils.handleSignal";
54746
+ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistBacktestUtils.handleSignalNotify";
54004
54747
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistBacktestUtils.handlePartialProfit";
54005
54748
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistBacktestUtils.handlePartialLoss";
54006
54749
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistBacktestUtils.handleBreakeven";
@@ -54015,6 +54758,7 @@ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_DISPOSE = "NotificationPersistBa
54015
54758
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistLiveUtils.waitForInit";
54016
54759
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistLiveUtils._updateNotifications";
54017
54760
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistLiveUtils.handleSignal";
54761
+ const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistLiveUtils.handleSignalNotify";
54018
54762
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistLiveUtils.handlePartialProfit";
54019
54763
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistLiveUtils.handlePartialLoss";
54020
54764
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistLiveUtils.handleBreakeven";
@@ -54057,6 +54801,12 @@ class NotificationMemoryBacktestUtils {
54057
54801
  this._addNotification(notification);
54058
54802
  }
54059
54803
  };
54804
+ this.handleSignalNotify = async (data) => {
54805
+ backtest.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54806
+ signalId: data.data.id,
54807
+ });
54808
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54809
+ };
54060
54810
  /**
54061
54811
  * Handles partial profit availability event.
54062
54812
  * @param data - The partial profit contract data
@@ -54201,6 +54951,8 @@ class NotificationDummyBacktestUtils {
54201
54951
  */
54202
54952
  this.handleSignal = async () => {
54203
54953
  };
54954
+ this.handleSignalNotify = async () => {
54955
+ };
54204
54956
  /**
54205
54957
  * No-op handler for partial profit event.
54206
54958
  */
@@ -54308,6 +55060,14 @@ class NotificationPersistBacktestUtils {
54308
55060
  await this._updateNotifications();
54309
55061
  }
54310
55062
  };
55063
+ this.handleSignalNotify = async (data) => {
55064
+ backtest.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55065
+ signalId: data.data.id,
55066
+ });
55067
+ await this.waitForInit();
55068
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55069
+ await this._updateNotifications();
55070
+ };
54311
55071
  /**
54312
55072
  * Handles partial profit availability event.
54313
55073
  * @param data - The partial profit contract data
@@ -54509,6 +55269,12 @@ class NotificationMemoryLiveUtils {
54509
55269
  this._addNotification(notification);
54510
55270
  }
54511
55271
  };
55272
+ this.handleSignalNotify = async (data) => {
55273
+ backtest.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55274
+ signalId: data.data.id,
55275
+ });
55276
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55277
+ };
54512
55278
  /**
54513
55279
  * Handles partial profit availability event.
54514
55280
  * @param data - The partial profit contract data
@@ -54653,6 +55419,8 @@ class NotificationDummyLiveUtils {
54653
55419
  */
54654
55420
  this.handleSignal = async () => {
54655
55421
  };
55422
+ this.handleSignalNotify = async () => {
55423
+ };
54656
55424
  /**
54657
55425
  * No-op handler for partial profit event.
54658
55426
  */
@@ -54761,6 +55529,14 @@ class NotificationPersistLiveUtils {
54761
55529
  await this._updateNotifications();
54762
55530
  }
54763
55531
  };
55532
+ this.handleSignalNotify = async (data) => {
55533
+ backtest.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55534
+ signalId: data.data.id,
55535
+ });
55536
+ await this.waitForInit();
55537
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55538
+ await this._updateNotifications();
55539
+ };
54764
55540
  /**
54765
55541
  * Handles partial profit availability event.
54766
55542
  * @param data - The partial profit contract data
@@ -54954,6 +55730,9 @@ class NotificationBacktestAdapter {
54954
55730
  this.handleSignal = async (data) => {
54955
55731
  return await this._notificationBacktestUtils.handleSignal(data);
54956
55732
  };
55733
+ this.handleSignalNotify = async (data) => {
55734
+ return await this._notificationBacktestUtils.handleSignalNotify(data);
55735
+ };
54957
55736
  /**
54958
55737
  * Handles partial profit availability event.
54959
55738
  * Proxies call to the underlying notification adapter.
@@ -55109,6 +55888,9 @@ class NotificationLiveAdapter {
55109
55888
  this.handleSignal = async (data) => {
55110
55889
  return await this._notificationLiveUtils.handleSignal(data);
55111
55890
  };
55891
+ this.handleSignalNotify = async (data) => {
55892
+ return await this._notificationLiveUtils.handleSignalNotify(data);
55893
+ };
55112
55894
  /**
55113
55895
  * Handles partial profit availability event.
55114
55896
  * Proxies call to the underlying notification adapter.
@@ -55260,59 +56042,153 @@ class NotificationAdapter {
55260
56042
  *
55261
56043
  * @returns Cleanup function that unsubscribes from all emitters
55262
56044
  */
55263
- this.enable = singleshot(() => {
56045
+ this.enable = 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) => {
55264
56046
  backtest.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE);
55265
56047
  let unLive;
55266
56048
  let unBacktest;
55267
56049
  {
55268
- const unBacktestSignal = signalBacktestEmitter.subscribe((data) => NotificationBacktest.handleSignal(data));
56050
+ const unBacktestSignal = signalBacktestEmitter.subscribe(async (data) => {
56051
+ if (signal) {
56052
+ await NotificationBacktest.handleSignal(data);
56053
+ }
56054
+ });
55269
56055
  const unBacktestPartialProfit = partialProfitSubject
55270
56056
  .filter(({ backtest }) => backtest)
55271
- .connect((data) => NotificationBacktest.handlePartialProfit(data));
56057
+ .connect(async (data) => {
56058
+ if (partial_profit) {
56059
+ await NotificationBacktest.handlePartialProfit(data);
56060
+ }
56061
+ });
55272
56062
  const unBacktestPartialLoss = partialLossSubject
55273
56063
  .filter(({ backtest }) => backtest)
55274
- .connect((data) => NotificationBacktest.handlePartialLoss(data));
56064
+ .connect(async (data) => {
56065
+ if (partial_loss) {
56066
+ await NotificationBacktest.handlePartialLoss(data);
56067
+ }
56068
+ });
55275
56069
  const unBacktestBreakeven = breakevenSubject
55276
56070
  .filter(({ backtest }) => backtest)
55277
- .connect((data) => NotificationBacktest.handleBreakeven(data));
56071
+ .connect(async (data) => {
56072
+ if (breakeven) {
56073
+ await NotificationBacktest.handleBreakeven(data);
56074
+ }
56075
+ });
55278
56076
  const unBacktestStrategyCommit = strategyCommitSubject
55279
56077
  .filter(({ backtest }) => backtest)
55280
- .connect((data) => NotificationBacktest.handleStrategyCommit(data));
56078
+ .connect(async (data) => {
56079
+ if (strategy_commit) {
56080
+ await NotificationBacktest.handleStrategyCommit(data);
56081
+ }
56082
+ });
55281
56083
  const unBacktestSync = syncSubject
55282
56084
  .filter(({ backtest }) => backtest)
55283
- .connect((data) => NotificationBacktest.handleSync(data));
56085
+ .connect(async (data) => {
56086
+ if (signal_sync) {
56087
+ await NotificationBacktest.handleSync(data);
56088
+ }
56089
+ });
55284
56090
  const unBacktestRisk = riskSubject
55285
56091
  .filter(({ backtest }) => backtest)
55286
- .connect((data) => NotificationBacktest.handleRisk(data));
55287
- const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
55288
- const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
55289
- const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
55290
- unBacktest = compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation());
56092
+ .connect(async (data) => {
56093
+ if (risk) {
56094
+ await NotificationBacktest.handleRisk(data);
56095
+ }
56096
+ });
56097
+ const unBacktestError = errorEmitter.subscribe(async (error) => {
56098
+ if (common_error) {
56099
+ await NotificationBacktest.handleError(error);
56100
+ }
56101
+ });
56102
+ const unBacktestExit = exitEmitter.subscribe(async (error) => {
56103
+ if (critical_error) {
56104
+ await NotificationBacktest.handleCriticalError(error);
56105
+ }
56106
+ });
56107
+ const unBacktestValidation = validationSubject.subscribe(async (error) => {
56108
+ if (validation_error) {
56109
+ await NotificationBacktest.handleValidationError(error);
56110
+ }
56111
+ });
56112
+ const unBacktestSignalNotify = signalNotifySubject
56113
+ .filter(({ backtest }) => backtest)
56114
+ .connect(async (data) => {
56115
+ if (info) {
56116
+ await NotificationBacktest.handleSignalNotify(data);
56117
+ }
56118
+ });
56119
+ unBacktest = compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
55291
56120
  }
55292
56121
  {
55293
- const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
56122
+ const unLiveSignal = signalLiveEmitter.subscribe(async (data) => {
56123
+ if (signal) {
56124
+ await NotificationLive.handleSignal(data);
56125
+ }
56126
+ });
55294
56127
  const unLivePartialProfit = partialProfitSubject
55295
56128
  .filter(({ backtest }) => !backtest)
55296
- .connect((data) => NotificationLive.handlePartialProfit(data));
56129
+ .connect(async (data) => {
56130
+ if (partial_profit) {
56131
+ await NotificationLive.handlePartialProfit(data);
56132
+ }
56133
+ });
55297
56134
  const unLivePartialLoss = partialLossSubject
55298
56135
  .filter(({ backtest }) => !backtest)
55299
- .connect((data) => NotificationLive.handlePartialLoss(data));
56136
+ .connect(async (data) => {
56137
+ if (partial_loss) {
56138
+ await NotificationLive.handlePartialLoss(data);
56139
+ }
56140
+ });
55300
56141
  const unLiveBreakeven = breakevenSubject
55301
56142
  .filter(({ backtest }) => !backtest)
55302
- .connect((data) => NotificationLive.handleBreakeven(data));
56143
+ .connect(async (data) => {
56144
+ if (breakeven) {
56145
+ await NotificationLive.handleBreakeven(data);
56146
+ }
56147
+ });
55303
56148
  const unLiveStrategyCommit = strategyCommitSubject
55304
56149
  .filter(({ backtest }) => !backtest)
55305
- .connect((data) => NotificationLive.handleStrategyCommit(data));
56150
+ .connect(async (data) => {
56151
+ if (strategy_commit) {
56152
+ await NotificationLive.handleStrategyCommit(data);
56153
+ }
56154
+ });
55306
56155
  const unLiveSync = syncSubject
55307
56156
  .filter(({ backtest }) => !backtest)
55308
- .connect((data) => NotificationLive.handleSync(data));
56157
+ .connect(async (data) => {
56158
+ if (signal_sync) {
56159
+ await NotificationLive.handleSync(data);
56160
+ }
56161
+ });
55309
56162
  const unLiveRisk = riskSubject
55310
56163
  .filter(({ backtest }) => !backtest)
55311
- .connect((data) => NotificationLive.handleRisk(data));
55312
- const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
55313
- const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
55314
- const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
55315
- unLive = compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation());
56164
+ .connect(async (data) => {
56165
+ if (risk) {
56166
+ await NotificationLive.handleRisk(data);
56167
+ }
56168
+ });
56169
+ const unLiveError = errorEmitter.subscribe(async (error) => {
56170
+ if (common_error) {
56171
+ await NotificationLive.handleError(error);
56172
+ }
56173
+ });
56174
+ const unLiveExit = exitEmitter.subscribe(async (error) => {
56175
+ if (critical_error) {
56176
+ await NotificationLive.handleCriticalError(error);
56177
+ }
56178
+ });
56179
+ const unLiveValidation = validationSubject.subscribe(async (error) => {
56180
+ if (validation_error) {
56181
+ await NotificationLive.handleValidationError(error);
56182
+ }
56183
+ });
56184
+ const unLiveSignalNotify = signalNotifySubject
56185
+ .filter(({ backtest }) => !backtest)
56186
+ .connect(async (data) => {
56187
+ if (info) {
56188
+ await NotificationLive.handleSignalNotify(data);
56189
+ }
56190
+ });
56191
+ unLive = compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
55316
56192
  }
55317
56193
  return () => {
55318
56194
  unLive();
@@ -55550,11 +56426,17 @@ class CacheFnInstance {
55550
56426
  return cached;
55551
56427
  }
55552
56428
  }
56429
+ const value = this.fn(...args);
55553
56430
  const newCache = {
55554
56431
  when: currentWhen,
55555
- value: this.fn(...args),
56432
+ value,
55556
56433
  };
55557
56434
  this._cacheMap.set(key, newCache);
56435
+ if (value && value instanceof Promise) {
56436
+ value.catch(() => {
56437
+ this._cacheMap.delete(key);
56438
+ });
56439
+ }
55558
56440
  return newCache;
55559
56441
  };
55560
56442
  /**
@@ -56135,7 +57017,7 @@ class IntervalFnInstance {
56135
57017
  * within the same interval or when `fn` itself returned `null`
56136
57018
  * @throws Error if method context, execution context, or interval is missing
56137
57019
  */
56138
- this.run = async (...args) => {
57020
+ this.run = (...args) => {
56139
57021
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
56140
57022
  const step = INTERVAL_MINUTES[this.interval];
56141
57023
  {
@@ -56157,10 +57039,15 @@ class IntervalFnInstance {
56157
57039
  if (this._stateMap.get(stateKey) === currentAligned) {
56158
57040
  return null;
56159
57041
  }
56160
- const result = await this.fn.apply(null, args);
57042
+ const result = this.fn.apply(null, args);
56161
57043
  if (result !== null) {
56162
57044
  this._stateMap.set(stateKey, currentAligned);
56163
57045
  }
57046
+ if (result && result instanceof Promise) {
57047
+ result.catch(() => {
57048
+ this._stateMap.delete(stateKey);
57049
+ });
57050
+ }
56164
57051
  return result;
56165
57052
  };
56166
57053
  /**
@@ -57691,4 +58578,4 @@ const validateSignal = (signal, currentPrice) => {
57691
58578
  return !errors.length;
57692
58579
  };
57693
58580
 
57694
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
58581
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };