backtest-kit 6.12.0 → 6.14.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.
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,
@@ -982,6 +992,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
982
992
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
983
993
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
984
994
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
995
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
996
+ const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
997
+ const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
998
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
999
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
1000
+ const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
985
1001
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
986
1002
  const BASE_UNLINK_RETRY_COUNT = 5;
987
1003
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2828,6 +2844,115 @@ class PersistMemoryUtils {
2828
2844
  * ```
2829
2845
  */
2830
2846
  const PersistMemoryAdapter = new PersistMemoryUtils();
2847
+ /**
2848
+ * Utility class for managing recent signal persistence.
2849
+ *
2850
+ * Features:
2851
+ * - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
2852
+ * - Custom adapter support
2853
+ * - Atomic read/write operations
2854
+ * - Crash-safe recent signal state management
2855
+ *
2856
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2857
+ */
2858
+ class PersistRecentUtils {
2859
+ constructor() {
2860
+ this.PersistRecentFactory = PersistBase;
2861
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
2862
+ this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
2863
+ `./dump/data/recent/`,
2864
+ ]));
2865
+ /**
2866
+ * Reads the latest persisted recent signal for a given context.
2867
+ *
2868
+ * Returns null if no recent signal exists.
2869
+ *
2870
+ * @param symbol - Trading pair symbol
2871
+ * @param strategyName - Strategy identifier
2872
+ * @param exchangeName - Exchange identifier
2873
+ * @param frameName - Frame identifier
2874
+ * @returns Promise resolving to recent signal or null
2875
+ */
2876
+ this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
2877
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
2878
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2879
+ const isInitial = !this.getStorage.has(key);
2880
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2881
+ await stateStorage.waitForInit(isInitial);
2882
+ if (await stateStorage.hasValue(symbol)) {
2883
+ return await stateStorage.readValue(symbol);
2884
+ }
2885
+ return null;
2886
+ };
2887
+ /**
2888
+ * Writes the latest recent signal to disk with atomic file writes.
2889
+ *
2890
+ * Uses symbol as the entity ID within the per-context storage instance.
2891
+ * Uses atomic writes to prevent corruption on crashes.
2892
+ *
2893
+ * @param signalRow - Recent signal data to persist
2894
+ * @param symbol - Trading pair symbol
2895
+ * @param strategyName - Strategy identifier
2896
+ * @param exchangeName - Exchange identifier
2897
+ * @param frameName - Frame identifier
2898
+ * @returns Promise that resolves when write is complete
2899
+ */
2900
+ this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
2901
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
2902
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2903
+ const isInitial = !this.getStorage.has(key);
2904
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2905
+ await stateStorage.waitForInit(isInitial);
2906
+ await stateStorage.writeValue(symbol, signalRow);
2907
+ };
2908
+ }
2909
+ createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
2910
+ const parts = [symbol, strategyName, exchangeName];
2911
+ if (frameName)
2912
+ parts.push(frameName);
2913
+ parts.push(backtest ? "backtest" : "live");
2914
+ return parts;
2915
+ }
2916
+ /**
2917
+ * Registers a custom persistence adapter.
2918
+ *
2919
+ * @param Ctor - Custom PersistBase constructor
2920
+ */
2921
+ usePersistRecentAdapter(Ctor) {
2922
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
2923
+ this.PersistRecentFactory = Ctor;
2924
+ }
2925
+ /**
2926
+ * Clears the memoized storage cache.
2927
+ * Call this when process.cwd() changes between strategy iterations
2928
+ * so new storage instances are created with the updated base path.
2929
+ */
2930
+ clear() {
2931
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
2932
+ this.getStorage.clear();
2933
+ }
2934
+ /**
2935
+ * Switches to the default JSON persist adapter.
2936
+ * All future persistence writes will use JSON storage.
2937
+ */
2938
+ useJson() {
2939
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
2940
+ this.usePersistRecentAdapter(PersistBase);
2941
+ }
2942
+ /**
2943
+ * Switches to a dummy persist adapter that discards all writes.
2944
+ * All future persistence writes will be no-ops.
2945
+ */
2946
+ useDummy() {
2947
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
2948
+ this.usePersistRecentAdapter(PersistDummy);
2949
+ }
2950
+ }
2951
+ /**
2952
+ * Global singleton instance of PersistRecentUtils.
2953
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2954
+ */
2955
+ const PersistRecentAdapter = new PersistRecentUtils();
2831
2956
 
2832
2957
  var _a$2, _b$2;
2833
2958
  const BUSY_DELAY = 100;
@@ -10155,7 +10280,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10155
10280
  * @param backtest - Whether running in backtest mode
10156
10281
  * @returns Unique string key for memoization
10157
10282
  */
10158
- const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
10283
+ const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10159
10284
  const parts = [symbol, strategyName, exchangeName];
10160
10285
  if (frameName)
10161
10286
  parts.push(frameName);
@@ -10422,7 +10547,7 @@ class StrategyConnectionService {
10422
10547
  * @param backtest - Whether running in backtest mode
10423
10548
  * @returns Configured ClientStrategy instance
10424
10549
  */
10425
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10550
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10426
10551
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10427
10552
  return new ClientStrategy({
10428
10553
  symbol,
@@ -11343,7 +11468,7 @@ class StrategyConnectionService {
11343
11468
  }
11344
11469
  return;
11345
11470
  }
11346
- const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11471
+ const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11347
11472
  if (!this.getStrategy.has(key)) {
11348
11473
  return;
11349
11474
  }
@@ -12512,7 +12637,7 @@ class ClientRisk {
12512
12637
  * @param backtest - Whether running in backtest mode
12513
12638
  * @returns Unique string key for memoization
12514
12639
  */
12515
- const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12640
+ const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
12516
12641
  const parts = [riskName, exchangeName];
12517
12642
  if (frameName)
12518
12643
  parts.push(frameName);
@@ -12612,7 +12737,7 @@ class RiskConnectionService {
12612
12737
  * @param backtest - True if backtest mode, false if live mode
12613
12738
  * @returns Configured ClientRisk instance
12614
12739
  */
12615
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12740
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12616
12741
  const schema = this.riskSchemaService.get(riskName);
12617
12742
  return new ClientRisk({
12618
12743
  ...schema,
@@ -12681,7 +12806,7 @@ class RiskConnectionService {
12681
12806
  payload,
12682
12807
  });
12683
12808
  if (payload) {
12684
- const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12809
+ const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12685
12810
  this.getRisk.clear(key);
12686
12811
  }
12687
12812
  else {
@@ -13725,7 +13850,7 @@ class ClientAction {
13725
13850
  * @param backtest - Whether running in backtest mode
13726
13851
  * @returns Unique string key for memoization
13727
13852
  */
13728
- const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13853
+ const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
13729
13854
  const parts = [actionName, strategyName, exchangeName];
13730
13855
  if (frameName)
13731
13856
  parts.push(frameName);
@@ -13777,7 +13902,7 @@ class ActionConnectionService {
13777
13902
  * @param backtest - True if backtest mode, false if live mode
13778
13903
  * @returns Configured ClientAction instance
13779
13904
  */
13780
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13905
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13781
13906
  const schema = this.actionSchemaService.get(actionName);
13782
13907
  return new ClientAction({
13783
13908
  ...schema,
@@ -13988,7 +14113,7 @@ class ActionConnectionService {
13988
14113
  await Promise.all(actions.map(async (action) => await action.dispose()));
13989
14114
  return;
13990
14115
  }
13991
- const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14116
+ const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13992
14117
  if (!this.getAction.has(key)) {
13993
14118
  return;
13994
14119
  }
@@ -13999,14 +14124,14 @@ class ActionConnectionService {
13999
14124
  }
14000
14125
  }
14001
14126
 
14002
- const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
14127
+ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14003
14128
  /**
14004
14129
  * Creates a unique key for memoizing validate calls.
14005
14130
  * Key format: "exchangeName"
14006
14131
  * @param exchangeName - Exchange name
14007
14132
  * @returns Unique string key for memoization
14008
14133
  */
14009
- const CREATE_KEY_FN$q = (exchangeName) => {
14134
+ const CREATE_KEY_FN$s = (exchangeName) => {
14010
14135
  return exchangeName;
14011
14136
  };
14012
14137
  /**
@@ -14030,11 +14155,11 @@ class ExchangeCoreService {
14030
14155
  * @param exchangeName - Name of the exchange to validate
14031
14156
  * @returns Promise that resolves when validation is complete
14032
14157
  */
14033
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
14034
- this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14158
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14159
+ this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14035
14160
  exchangeName,
14036
14161
  });
14037
- this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$2);
14162
+ this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$3);
14038
14163
  });
14039
14164
  /**
14040
14165
  * Fetches historical candles with execution context.
@@ -14275,14 +14400,14 @@ class ExchangeCoreService {
14275
14400
  }
14276
14401
  }
14277
14402
 
14278
- const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14403
+ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14279
14404
  /**
14280
14405
  * Creates a unique key for memoizing validate calls.
14281
14406
  * Key format: "strategyName:exchangeName:frameName"
14282
14407
  * @param context - Execution context with strategyName, exchangeName, frameName
14283
14408
  * @returns Unique string key for memoization
14284
14409
  */
14285
- const CREATE_KEY_FN$p = (context) => {
14410
+ const CREATE_KEY_FN$r = (context) => {
14286
14411
  const parts = [context.strategyName, context.exchangeName];
14287
14412
  if (context.frameName)
14288
14413
  parts.push(context.frameName);
@@ -14314,16 +14439,16 @@ class StrategyCoreService {
14314
14439
  * @param context - Execution context with strategyName, exchangeName, frameName
14315
14440
  * @returns Promise that resolves when validation is complete
14316
14441
  */
14317
- this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
14318
- this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14442
+ this.validate = memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14443
+ this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14319
14444
  context,
14320
14445
  });
14321
14446
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
14322
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
14323
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
14324
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
14325
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
14326
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
14447
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$2);
14448
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$2);
14449
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$2);
14450
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2);
14451
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2));
14327
14452
  });
14328
14453
  /**
14329
14454
  * Retrieves the currently active pending signal for the symbol.
@@ -15652,7 +15777,7 @@ class SizingGlobalService {
15652
15777
  * @param context - Context with riskName, exchangeName, frameName
15653
15778
  * @returns Unique string key for memoization
15654
15779
  */
15655
- const CREATE_KEY_FN$o = (context) => {
15780
+ const CREATE_KEY_FN$q = (context) => {
15656
15781
  const parts = [context.riskName, context.exchangeName];
15657
15782
  if (context.frameName)
15658
15783
  parts.push(context.frameName);
@@ -15678,7 +15803,7 @@ class RiskGlobalService {
15678
15803
  * @param payload - Payload with riskName, exchangeName and frameName
15679
15804
  * @returns Promise that resolves when validation is complete
15680
15805
  */
15681
- this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15806
+ this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
15682
15807
  this.loggerService.log("riskGlobalService validate", {
15683
15808
  context,
15684
15809
  });
@@ -15749,14 +15874,14 @@ class RiskGlobalService {
15749
15874
  }
15750
15875
  }
15751
15876
 
15752
- const METHOD_NAME_VALIDATE = "actionCoreService validate";
15877
+ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
15753
15878
  /**
15754
15879
  * Creates a unique key for memoizing validate calls.
15755
15880
  * Key format: "strategyName:exchangeName:frameName"
15756
15881
  * @param context - Execution context with strategyName, exchangeName, frameName
15757
15882
  * @returns Unique string key for memoization
15758
15883
  */
15759
- const CREATE_KEY_FN$n = (context) => {
15884
+ const CREATE_KEY_FN$p = (context) => {
15760
15885
  const parts = [context.strategyName, context.exchangeName];
15761
15886
  if (context.frameName)
15762
15887
  parts.push(context.frameName);
@@ -15800,17 +15925,17 @@ class ActionCoreService {
15800
15925
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15801
15926
  * @returns Promise that resolves when all validations complete
15802
15927
  */
15803
- this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15804
- this.loggerService.log(METHOD_NAME_VALIDATE, {
15928
+ this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15929
+ this.loggerService.log(METHOD_NAME_VALIDATE$1, {
15805
15930
  context,
15806
15931
  });
15807
15932
  const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
15808
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
15809
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
15810
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
15811
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
15812
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
15813
- actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
15933
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
15934
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
15935
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
15936
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
15937
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
15938
+ actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$1));
15814
15939
  });
15815
15940
  /**
15816
15941
  * Initializes all ClientAction instances for the strategy.
@@ -20843,7 +20968,7 @@ const ReportWriter = new ReportWriterAdapter();
20843
20968
  * @param backtest - Whether running in backtest mode
20844
20969
  * @returns Unique string key for memoization
20845
20970
  */
20846
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20971
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
20847
20972
  const parts = [symbol, strategyName, exchangeName];
20848
20973
  if (frameName)
20849
20974
  parts.push(frameName);
@@ -21089,7 +21214,7 @@ class BacktestMarkdownService {
21089
21214
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21090
21215
  * Each combination gets its own isolated storage instance.
21091
21216
  */
21092
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21217
+ 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));
21093
21218
  /**
21094
21219
  * Processes tick events and accumulates closed signals.
21095
21220
  * Should be called from IStrategyCallbacks.onTick.
@@ -21246,7 +21371,7 @@ class BacktestMarkdownService {
21246
21371
  payload,
21247
21372
  });
21248
21373
  if (payload) {
21249
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21374
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21250
21375
  this.getStorage.clear(key);
21251
21376
  }
21252
21377
  else {
@@ -21308,7 +21433,7 @@ class BacktestMarkdownService {
21308
21433
  * @param backtest - Whether running in backtest mode
21309
21434
  * @returns Unique string key for memoization
21310
21435
  */
21311
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
21436
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21312
21437
  const parts = [symbol, strategyName, exchangeName];
21313
21438
  if (frameName)
21314
21439
  parts.push(frameName);
@@ -21803,7 +21928,7 @@ class LiveMarkdownService {
21803
21928
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21804
21929
  * Each combination gets its own isolated storage instance.
21805
21930
  */
21806
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21931
+ 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));
21807
21932
  /**
21808
21933
  * Subscribes to live signal emitter to receive tick events.
21809
21934
  * Protected against multiple subscriptions.
@@ -22021,7 +22146,7 @@ class LiveMarkdownService {
22021
22146
  payload,
22022
22147
  });
22023
22148
  if (payload) {
22024
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22149
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22025
22150
  this.getStorage.clear(key);
22026
22151
  }
22027
22152
  else {
@@ -22041,7 +22166,7 @@ class LiveMarkdownService {
22041
22166
  * @param backtest - Whether running in backtest mode
22042
22167
  * @returns Unique string key for memoization
22043
22168
  */
22044
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22169
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22045
22170
  const parts = [symbol, strategyName, exchangeName];
22046
22171
  if (frameName)
22047
22172
  parts.push(frameName);
@@ -22330,7 +22455,7 @@ class ScheduleMarkdownService {
22330
22455
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22331
22456
  * Each combination gets its own isolated storage instance.
22332
22457
  */
22333
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22458
+ 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));
22334
22459
  /**
22335
22460
  * Subscribes to signal emitter to receive scheduled signal events.
22336
22461
  * Protected against multiple subscriptions.
@@ -22533,7 +22658,7 @@ class ScheduleMarkdownService {
22533
22658
  payload,
22534
22659
  });
22535
22660
  if (payload) {
22536
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22661
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22537
22662
  this.getStorage.clear(key);
22538
22663
  }
22539
22664
  else {
@@ -22553,7 +22678,7 @@ class ScheduleMarkdownService {
22553
22678
  * @param backtest - Whether running in backtest mode
22554
22679
  * @returns Unique string key for memoization
22555
22680
  */
22556
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
22681
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22557
22682
  const parts = [symbol, strategyName, exchangeName];
22558
22683
  if (frameName)
22559
22684
  parts.push(frameName);
@@ -22798,7 +22923,7 @@ class PerformanceMarkdownService {
22798
22923
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22799
22924
  * Each combination gets its own isolated storage instance.
22800
22925
  */
22801
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22926
+ 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));
22802
22927
  /**
22803
22928
  * Subscribes to performance emitter to receive performance events.
22804
22929
  * Protected against multiple subscriptions.
@@ -22965,7 +23090,7 @@ class PerformanceMarkdownService {
22965
23090
  payload,
22966
23091
  });
22967
23092
  if (payload) {
22968
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23093
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22969
23094
  this.getStorage.clear(key);
22970
23095
  }
22971
23096
  else {
@@ -23444,7 +23569,7 @@ class WalkerMarkdownService {
23444
23569
  * @param backtest - Whether running in backtest mode
23445
23570
  * @returns Unique string key for memoization
23446
23571
  */
23447
- const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
23572
+ const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
23448
23573
  const parts = [exchangeName];
23449
23574
  if (frameName)
23450
23575
  parts.push(frameName);
@@ -23891,7 +24016,7 @@ class HeatMarkdownService {
23891
24016
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23892
24017
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23893
24018
  */
23894
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24019
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23895
24020
  /**
23896
24021
  * Subscribes to signal emitter to receive tick events.
23897
24022
  * Protected against multiple subscriptions.
@@ -24109,7 +24234,7 @@ class HeatMarkdownService {
24109
24234
  payload,
24110
24235
  });
24111
24236
  if (payload) {
24112
- const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
24237
+ const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24113
24238
  this.getStorage.clear(key);
24114
24239
  }
24115
24240
  else {
@@ -25140,7 +25265,7 @@ class ClientPartial {
25140
25265
  * @param backtest - Whether running in backtest mode
25141
25266
  * @returns Unique string key for memoization
25142
25267
  */
25143
- const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25268
+ const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25144
25269
  /**
25145
25270
  * Creates a callback function for emitting profit events to partialProfitSubject.
25146
25271
  *
@@ -25262,7 +25387,7 @@ class PartialConnectionService {
25262
25387
  * Key format: "signalId:backtest" or "signalId:live"
25263
25388
  * Value: ClientPartial instance with logger and event emitters
25264
25389
  */
25265
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
25390
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25266
25391
  return new ClientPartial({
25267
25392
  signalId,
25268
25393
  logger: this.loggerService,
@@ -25352,7 +25477,7 @@ class PartialConnectionService {
25352
25477
  const partial = this.getPartial(data.id, backtest);
25353
25478
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25354
25479
  await partial.clear(symbol, data, priceClose, backtest);
25355
- const key = CREATE_KEY_FN$h(data.id, backtest);
25480
+ const key = CREATE_KEY_FN$j(data.id, backtest);
25356
25481
  this.getPartial.clear(key);
25357
25482
  };
25358
25483
  }
@@ -25368,7 +25493,7 @@ class PartialConnectionService {
25368
25493
  * @param backtest - Whether running in backtest mode
25369
25494
  * @returns Unique string key for memoization
25370
25495
  */
25371
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
25496
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
25372
25497
  const parts = [symbol, strategyName, exchangeName];
25373
25498
  if (frameName)
25374
25499
  parts.push(frameName);
@@ -25591,7 +25716,7 @@ class PartialMarkdownService {
25591
25716
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25592
25717
  * Each combination gets its own isolated storage instance.
25593
25718
  */
25594
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25719
+ 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));
25595
25720
  /**
25596
25721
  * Subscribes to partial profit/loss signal emitters to receive events.
25597
25722
  * Protected against multiple subscriptions.
@@ -25801,7 +25926,7 @@ class PartialMarkdownService {
25801
25926
  payload,
25802
25927
  });
25803
25928
  if (payload) {
25804
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25929
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25805
25930
  this.getStorage.clear(key);
25806
25931
  }
25807
25932
  else {
@@ -25817,7 +25942,7 @@ class PartialMarkdownService {
25817
25942
  * @param context - Context with strategyName, exchangeName, frameName
25818
25943
  * @returns Unique string key for memoization
25819
25944
  */
25820
- const CREATE_KEY_FN$f = (context) => {
25945
+ const CREATE_KEY_FN$h = (context) => {
25821
25946
  const parts = [context.strategyName, context.exchangeName];
25822
25947
  if (context.frameName)
25823
25948
  parts.push(context.frameName);
@@ -25891,7 +26016,7 @@ class PartialGlobalService {
25891
26016
  * @param context - Context with strategyName, exchangeName and frameName
25892
26017
  * @param methodName - Name of the calling method for error tracking
25893
26018
  */
25894
- this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
26019
+ this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
25895
26020
  this.loggerService.log("partialGlobalService validate", {
25896
26021
  context,
25897
26022
  methodName,
@@ -26346,7 +26471,7 @@ class ClientBreakeven {
26346
26471
  * @param backtest - Whether running in backtest mode
26347
26472
  * @returns Unique string key for memoization
26348
26473
  */
26349
- const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26474
+ const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26350
26475
  /**
26351
26476
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26352
26477
  *
@@ -26432,7 +26557,7 @@ class BreakevenConnectionService {
26432
26557
  * Key format: "signalId:backtest" or "signalId:live"
26433
26558
  * Value: ClientBreakeven instance with logger and event emitter
26434
26559
  */
26435
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
26560
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
26436
26561
  return new ClientBreakeven({
26437
26562
  signalId,
26438
26563
  logger: this.loggerService,
@@ -26493,7 +26618,7 @@ class BreakevenConnectionService {
26493
26618
  const breakeven = this.getBreakeven(data.id, backtest);
26494
26619
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26495
26620
  await breakeven.clear(symbol, data, priceClose, backtest);
26496
- const key = CREATE_KEY_FN$e(data.id, backtest);
26621
+ const key = CREATE_KEY_FN$g(data.id, backtest);
26497
26622
  this.getBreakeven.clear(key);
26498
26623
  };
26499
26624
  }
@@ -26509,7 +26634,7 @@ class BreakevenConnectionService {
26509
26634
  * @param backtest - Whether running in backtest mode
26510
26635
  * @returns Unique string key for memoization
26511
26636
  */
26512
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
26637
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
26513
26638
  const parts = [symbol, strategyName, exchangeName];
26514
26639
  if (frameName)
26515
26640
  parts.push(frameName);
@@ -26684,7 +26809,7 @@ class BreakevenMarkdownService {
26684
26809
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26685
26810
  * Each combination gets its own isolated storage instance.
26686
26811
  */
26687
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26812
+ 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));
26688
26813
  /**
26689
26814
  * Subscribes to breakeven signal emitter to receive events.
26690
26815
  * Protected against multiple subscriptions.
@@ -26873,7 +26998,7 @@ class BreakevenMarkdownService {
26873
26998
  payload,
26874
26999
  });
26875
27000
  if (payload) {
26876
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27001
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26877
27002
  this.getStorage.clear(key);
26878
27003
  }
26879
27004
  else {
@@ -26889,7 +27014,7 @@ class BreakevenMarkdownService {
26889
27014
  * @param context - Context with strategyName, exchangeName, frameName
26890
27015
  * @returns Unique string key for memoization
26891
27016
  */
26892
- const CREATE_KEY_FN$c = (context) => {
27017
+ const CREATE_KEY_FN$e = (context) => {
26893
27018
  const parts = [context.strategyName, context.exchangeName];
26894
27019
  if (context.frameName)
26895
27020
  parts.push(context.frameName);
@@ -26963,7 +27088,7 @@ class BreakevenGlobalService {
26963
27088
  * @param context - Context with strategyName, exchangeName and frameName
26964
27089
  * @param methodName - Name of the calling method for error tracking
26965
27090
  */
26966
- this.validate = memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
27091
+ this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
26967
27092
  this.loggerService.log("breakevenGlobalService validate", {
26968
27093
  context,
26969
27094
  methodName,
@@ -27184,7 +27309,7 @@ class ConfigValidationService {
27184
27309
  * @param backtest - Whether running in backtest mode
27185
27310
  * @returns Unique string key for memoization
27186
27311
  */
27187
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
27312
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27188
27313
  const parts = [symbol, strategyName, exchangeName];
27189
27314
  if (frameName)
27190
27315
  parts.push(frameName);
@@ -27351,7 +27476,7 @@ class RiskMarkdownService {
27351
27476
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27352
27477
  * Each combination gets its own isolated storage instance.
27353
27478
  */
27354
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27479
+ 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));
27355
27480
  /**
27356
27481
  * Subscribes to risk rejection emitter to receive rejection events.
27357
27482
  * Protected against multiple subscriptions.
@@ -27540,7 +27665,7 @@ class RiskMarkdownService {
27540
27665
  payload,
27541
27666
  });
27542
27667
  if (payload) {
27543
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27668
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27544
27669
  this.getStorage.clear(key);
27545
27670
  }
27546
27671
  else {
@@ -29922,7 +30047,7 @@ class HighestProfitReportService {
29922
30047
  * @returns Colon-separated key string for memoization
29923
30048
  * @internal
29924
30049
  */
29925
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30050
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
29926
30051
  const parts = [symbol, strategyName, exchangeName];
29927
30052
  if (frameName)
29928
30053
  parts.push(frameName);
@@ -30164,7 +30289,7 @@ class StrategyMarkdownService {
30164
30289
  *
30165
30290
  * @internal
30166
30291
  */
30167
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30292
+ 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));
30168
30293
  /**
30169
30294
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30170
30295
  *
@@ -30738,7 +30863,7 @@ class StrategyMarkdownService {
30738
30863
  this.clear = async (payload) => {
30739
30864
  this.loggerService.log("strategyMarkdownService clear", { payload });
30740
30865
  if (payload) {
30741
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30866
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30742
30867
  this.getStorage.clear(key);
30743
30868
  }
30744
30869
  else {
@@ -30846,7 +30971,7 @@ class StrategyMarkdownService {
30846
30971
  * Creates a unique key for memoizing ReportStorage instances.
30847
30972
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30848
30973
  */
30849
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30974
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30850
30975
  const parts = [symbol, strategyName, exchangeName];
30851
30976
  if (frameName)
30852
30977
  parts.push(frameName);
@@ -31039,7 +31164,7 @@ let ReportStorage$2 = class ReportStorage {
31039
31164
  class SyncMarkdownService {
31040
31165
  constructor() {
31041
31166
  this.loggerService = inject(TYPES.loggerService);
31042
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31167
+ 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));
31043
31168
  /**
31044
31169
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31045
31170
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31235,7 +31360,7 @@ class SyncMarkdownService {
31235
31360
  this.clear = async (payload) => {
31236
31361
  this.loggerService.log("syncMarkdownService clear", { payload });
31237
31362
  if (payload) {
31238
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31363
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31239
31364
  this.getStorage.clear(key);
31240
31365
  }
31241
31366
  else {
@@ -31248,7 +31373,7 @@ class SyncMarkdownService {
31248
31373
  /**
31249
31374
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31250
31375
  */
31251
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31376
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31252
31377
  const parts = [symbol, strategyName, exchangeName];
31253
31378
  if (frameName)
31254
31379
  parts.push(frameName);
@@ -31424,7 +31549,7 @@ let ReportStorage$1 = class ReportStorage {
31424
31549
  class HighestProfitMarkdownService {
31425
31550
  constructor() {
31426
31551
  this.loggerService = inject(TYPES.loggerService);
31427
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31552
+ 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));
31428
31553
  /**
31429
31554
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31430
31555
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31590,7 +31715,7 @@ class HighestProfitMarkdownService {
31590
31715
  this.clear = async (payload) => {
31591
31716
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31592
31717
  if (payload) {
31593
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31718
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31594
31719
  this.getStorage.clear(key);
31595
31720
  }
31596
31721
  else {
@@ -31612,7 +31737,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31612
31737
  * @param backtest - Whether running in backtest mode
31613
31738
  * @returns Unique string key for memoization
31614
31739
  */
31615
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31740
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31616
31741
  const parts = [symbol, strategyName, exchangeName];
31617
31742
  if (frameName)
31618
31743
  parts.push(frameName);
@@ -31655,7 +31780,7 @@ class PriceMetaService {
31655
31780
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31656
31781
  * Instances are cached until clear() is called.
31657
31782
  */
31658
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31783
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31659
31784
  /**
31660
31785
  * Returns the current market price for the given symbol and context.
31661
31786
  *
@@ -31684,10 +31809,10 @@ class PriceMetaService {
31684
31809
  if (source.data) {
31685
31810
  return source.data;
31686
31811
  }
31687
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31812
+ 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...`);
31688
31813
  const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31689
31814
  if (typeof currentPrice === "symbol") {
31690
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31815
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31691
31816
  }
31692
31817
  return currentPrice;
31693
31818
  };
@@ -31729,7 +31854,7 @@ class PriceMetaService {
31729
31854
  this.getSource.clear();
31730
31855
  return;
31731
31856
  }
31732
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31857
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31733
31858
  this.getSource.clear(key);
31734
31859
  };
31735
31860
  }
@@ -31747,7 +31872,7 @@ const LISTEN_TIMEOUT = 120000;
31747
31872
  * @param backtest - Whether running in backtest mode
31748
31873
  * @returns Unique string key for memoization
31749
31874
  */
31750
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31875
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31751
31876
  const parts = [symbol, strategyName, exchangeName];
31752
31877
  if (frameName)
31753
31878
  parts.push(frameName);
@@ -31790,7 +31915,7 @@ class TimeMetaService {
31790
31915
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31791
31916
  * Instances are cached until clear() is called.
31792
31917
  */
31793
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31918
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
31794
31919
  /**
31795
31920
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31796
31921
  *
@@ -31818,10 +31943,10 @@ class TimeMetaService {
31818
31943
  if (source.data) {
31819
31944
  return source.data;
31820
31945
  }
31821
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31946
+ 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...`);
31822
31947
  const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31823
31948
  if (typeof timestamp === "symbol") {
31824
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31949
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31825
31950
  }
31826
31951
  return timestamp;
31827
31952
  };
@@ -31863,7 +31988,7 @@ class TimeMetaService {
31863
31988
  this.getSource.clear();
31864
31989
  return;
31865
31990
  }
31866
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31991
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31867
31992
  this.getSource.clear(key);
31868
31993
  };
31869
31994
  }
@@ -31959,7 +32084,7 @@ class MaxDrawdownReportService {
31959
32084
  /**
31960
32085
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31961
32086
  */
31962
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32087
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31963
32088
  const parts = [symbol, strategyName, exchangeName];
31964
32089
  if (frameName)
31965
32090
  parts.push(frameName);
@@ -32083,7 +32208,7 @@ class ReportStorage {
32083
32208
  class MaxDrawdownMarkdownService {
32084
32209
  constructor() {
32085
32210
  this.loggerService = inject(TYPES.loggerService);
32086
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32211
+ 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));
32087
32212
  /**
32088
32213
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32089
32214
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32162,7 +32287,7 @@ class MaxDrawdownMarkdownService {
32162
32287
  this.clear = async (payload) => {
32163
32288
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32164
32289
  if (payload) {
32165
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32290
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32166
32291
  this.getStorage.clear(key);
32167
32292
  }
32168
32293
  else {
@@ -32172,6 +32297,125 @@ class MaxDrawdownMarkdownService {
32172
32297
  }
32173
32298
  }
32174
32299
 
32300
+ const METHOD_NAME_COMMIT_SIGNAL_NOTIFY = "notificationHelperService.commitSignalNotify";
32301
+ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32302
+ /**
32303
+ * Creates a unique key for memoizing validate calls.
32304
+ * Key format: "strategyName:exchangeName:frameName"
32305
+ * @param context - Execution context with strategyName, exchangeName, frameName
32306
+ * @returns Unique string key for memoization
32307
+ */
32308
+ const CREATE_KEY_FN$6 = (context) => {
32309
+ const parts = [context.strategyName, context.exchangeName];
32310
+ if (context.frameName)
32311
+ parts.push(context.frameName);
32312
+ return parts.join(":");
32313
+ };
32314
+ /**
32315
+ * Helper service for emitting signal info notifications.
32316
+ *
32317
+ * Handles validation (memoized per context) and emission of `signal.info` events
32318
+ * via `signalNotifySubject`. Used internally by the framework action pipeline —
32319
+ * end users interact with this via `commitSignalNotify()` in `onActivePing` callbacks.
32320
+ */
32321
+ class NotificationHelperService {
32322
+ constructor() {
32323
+ this.loggerService = inject(TYPES.loggerService);
32324
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
32325
+ this.riskValidationService = inject(TYPES.riskValidationService);
32326
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
32327
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
32328
+ this.frameValidationService = inject(TYPES.frameValidationService);
32329
+ this.actionValidationService = inject(TYPES.actionValidationService);
32330
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
32331
+ this.timeMetaService = inject(TYPES.timeMetaService);
32332
+ /**
32333
+ * Validates strategy, exchange, frame, risk, and action schemas for the given context.
32334
+ *
32335
+ * Memoized per unique `"strategyName:exchangeName[:frameName]"` key — subsequent calls
32336
+ * with the same context are no-ops, so validation runs at most once per context.
32337
+ *
32338
+ * @param context - Routing context: strategyName, exchangeName, frameName
32339
+ * @throws {Error} If any registered schema fails validation
32340
+ */
32341
+ this.validate = memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
32342
+ this.loggerService.log(METHOD_NAME_VALIDATE, {
32343
+ context,
32344
+ });
32345
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
32346
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
32347
+ const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
32348
+ context.frameName &&
32349
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
32350
+ riskName &&
32351
+ this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
32352
+ riskList &&
32353
+ riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
32354
+ actions &&
32355
+ actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
32356
+ });
32357
+ /**
32358
+ * Emits a `signal.info` notification for the currently active pending signal.
32359
+ *
32360
+ * Validates all schemas (via memoized `validate`), resolves the pending signal
32361
+ * for the given symbol, then emits a `SignalInfoContract` via `signalNotifySubject`,
32362
+ * which is routed to all registered `listenSignalNotify` callbacks and persisted
32363
+ * by `NotificationAdapter`.
32364
+ *
32365
+ * @param payload - Optional notification fields (notificationId, notificationNote)
32366
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
32367
+ * @param currentPrice - Market price at the time of the call
32368
+ * @param context - Routing context: strategyName, exchangeName, frameName
32369
+ * @param backtest - true when called during a backtest run
32370
+ *
32371
+ * @throws {Error} If no active pending signal is found for the given symbol
32372
+ *
32373
+ * @example
32374
+ * ```typescript
32375
+ * // Inside onActivePing callback:
32376
+ * await commitSignalNotify("BTCUSDT", {
32377
+ * notificationNote: "RSI crossed 70, consider closing",
32378
+ * notificationId: "msg-123",
32379
+ * });
32380
+ * ```
32381
+ */
32382
+ this.commitSignalNotify = async (payload, symbol, currentPrice, context, backtest) => {
32383
+ this.loggerService.info(METHOD_NAME_COMMIT_SIGNAL_NOTIFY, {
32384
+ symbol,
32385
+ context,
32386
+ backtest,
32387
+ currentPrice,
32388
+ });
32389
+ this.validate(context);
32390
+ const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32391
+ strategyName: context.strategyName,
32392
+ exchangeName: context.exchangeName,
32393
+ frameName: "",
32394
+ });
32395
+ if (!pendingSignal) {
32396
+ throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
32397
+ }
32398
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, {
32399
+ strategyName: context.strategyName,
32400
+ exchangeName: context.exchangeName,
32401
+ frameName: context.frameName,
32402
+ }, backtest);
32403
+ await signalNotifySubject.next({
32404
+ backtest,
32405
+ symbol,
32406
+ currentPrice,
32407
+ data: pendingSignal,
32408
+ exchangeName: context.exchangeName,
32409
+ strategyName: context.strategyName,
32410
+ frameName: context.frameName,
32411
+ note: payload.notificationNote || pendingSignal.note,
32412
+ notificationId: payload.notificationId,
32413
+ timestamp,
32414
+ });
32415
+ };
32416
+ }
32417
+ }
32418
+
32175
32419
  {
32176
32420
  provide(TYPES.loggerService, () => new LoggerService());
32177
32421
  }
@@ -32260,6 +32504,9 @@ class MaxDrawdownMarkdownService {
32260
32504
  provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
32261
32505
  provide(TYPES.maxDrawdownReportService, () => new MaxDrawdownReportService());
32262
32506
  }
32507
+ {
32508
+ provide(TYPES.notificationHelperService, () => new NotificationHelperService());
32509
+ }
32263
32510
  {
32264
32511
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
32265
32512
  provide(TYPES.strategyValidationService, () => new StrategyValidationService());
@@ -32360,6 +32607,9 @@ const reportServices = {
32360
32607
  highestProfitReportService: inject(TYPES.highestProfitReportService),
32361
32608
  maxDrawdownReportService: inject(TYPES.maxDrawdownReportService),
32362
32609
  };
32610
+ const helperServices = {
32611
+ notificationHelperService: inject(TYPES.notificationHelperService),
32612
+ };
32363
32613
  const validationServices = {
32364
32614
  exchangeValidationService: inject(TYPES.exchangeValidationService),
32365
32615
  strategyValidationService: inject(TYPES.strategyValidationService),
@@ -32385,6 +32635,7 @@ const backtest = {
32385
32635
  ...markdownServices,
32386
32636
  ...reportServices,
32387
32637
  ...validationServices,
32638
+ ...helperServices,
32388
32639
  };
32389
32640
  init();
32390
32641
 
@@ -35301,6 +35552,7 @@ const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap
35301
35552
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35302
35553
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
35303
35554
  const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35555
+ const COMMIT_SIGNAL_NOTIFY_METHOD_NAME = "strategy.commitSignalNotify";
35304
35556
  /**
35305
35557
  * Cancels the scheduled signal without stopping the strategy.
35306
35558
  *
@@ -37265,6 +37517,50 @@ async function hasNoScheduledSignal(symbol) {
37265
37517
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37266
37518
  return await not(backtest.strategyCoreService.hasScheduledSignal(isBacktest, symbol, { exchangeName, frameName, strategyName }));
37267
37519
  }
37520
+ /**
37521
+ * Emits a `signal.info` notification for the currently active pending signal.
37522
+ *
37523
+ * Broadcasts a user-defined informational note without affecting position state.
37524
+ * Useful for annotating strategy decisions, triggering external alerts, or logging
37525
+ * mid-position events (e.g. RSI crossing a threshold, volume spike detected).
37526
+ *
37527
+ * Automatically reads backtest/live mode from execution context.
37528
+ * Automatically reads strategyName, exchangeName, frameName from method context.
37529
+ * Automatically fetches current price via getAveragePrice.
37530
+ *
37531
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
37532
+ * @param payload - Optional notification fields
37533
+ * @param payload.notificationNote - Human-readable note. Falls back to signal.note if omitted.
37534
+ * @param payload.notificationId - Optional correlation ID for external systems (e.g. Telegram message ID).
37535
+ *
37536
+ * @throws {Error} If called outside an execution context
37537
+ * @throws {Error} If called outside a method context
37538
+ * @throws {Error} If no active pending signal exists for the given symbol
37539
+ *
37540
+ * @example
37541
+ * ```typescript
37542
+ * import { commitSignalNotify } from "backtest-kit";
37543
+ *
37544
+ * // Inside onActivePing callback:
37545
+ * await commitSignalNotify("BTCUSDT", {
37546
+ * notificationNote: "RSI crossed 70, consider closing",
37547
+ * notificationId: "msg-123",
37548
+ * });
37549
+ * ```
37550
+ */
37551
+ async function commitSignalNotify(symbol, payload = {}) {
37552
+ backtest.loggerService.info(COMMIT_SIGNAL_NOTIFY_METHOD_NAME, { symbol, payload });
37553
+ if (!ExecutionContextService.hasContext()) {
37554
+ throw new Error("commitSignalNotify requires an execution context");
37555
+ }
37556
+ if (!MethodContextService.hasContext()) {
37557
+ throw new Error("commitSignalNotify requires a method context");
37558
+ }
37559
+ const currentPrice = await getAveragePrice(symbol);
37560
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37561
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37562
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, { strategyName, exchangeName, frameName }, isBacktest);
37563
+ }
37268
37564
 
37269
37565
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
37270
37566
  /**
@@ -37348,6 +37644,8 @@ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
37348
37644
  const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
37349
37645
  const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
37350
37646
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
37647
+ const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
37648
+ const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
37351
37649
  /**
37352
37650
  * Subscribes to all signal events with queued async processing.
37353
37651
  *
@@ -38739,6 +39037,46 @@ function listenMaxDrawdownOnce(filterFn, fn) {
38739
39037
  };
38740
39038
  return disposeFn = listenMaxDrawdown(wrappedFn);
38741
39039
  }
39040
+ /**
39041
+ * Subscribes to signal info events with queued async processing.
39042
+ * Emits when a strategy calls commitSignalInfo() to broadcast a user-defined note for an open position.
39043
+ * Events are processed sequentially in order received, even if callback is async.
39044
+ * Uses queued wrapper to prevent concurrent execution of the callback.
39045
+ * @param fn - Callback function to handle signal info events
39046
+ * @return Unsubscribe function to stop listening to events
39047
+ */
39048
+ function listenSignalNotify(fn) {
39049
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_METHOD_NAME);
39050
+ const wrappedFn = async (event) => {
39051
+ if (await backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39052
+ strategyName: event.strategyName,
39053
+ exchangeName: event.exchangeName,
39054
+ frameName: event.frameName,
39055
+ })) {
39056
+ await fn(event);
39057
+ }
39058
+ };
39059
+ return signalNotifySubject.subscribe(queued(wrappedFn));
39060
+ }
39061
+ /**
39062
+ * Subscribes to filtered signal info events with one-time execution.
39063
+ * Listens for events matching the filter predicate, then executes callback once
39064
+ * and automatically unsubscribes.
39065
+ * @param filterFn - Predicate to filter which events trigger the callback
39066
+ * @param fn - Callback function to handle the filtered event (called only once)
39067
+ * @return Unsubscribe function to cancel the listener before it fires
39068
+ */
39069
+ function listenSignalNotifyOnce(filterFn, fn) {
39070
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME);
39071
+ let disposeFn;
39072
+ const wrappedFn = async (event) => {
39073
+ if (filterFn(event)) {
39074
+ await fn(event);
39075
+ disposeFn && disposeFn();
39076
+ }
39077
+ };
39078
+ return disposeFn = listenSignalNotify(wrappedFn);
39079
+ }
38742
39080
 
38743
39081
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
38744
39082
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -38795,6 +39133,7 @@ const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingSto
38795
39133
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
38796
39134
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
38797
39135
  const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
39136
+ const BACKTEST_METHOD_NAME_SIGNAL_NOTIFY = "Backtest.commitSignalNotify";
38798
39137
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
38799
39138
  const BACKTEST_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "BacktestUtils.hasNoPendingSignal";
38800
39139
  const BACKTEST_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "BacktestUtils.hasNoScheduledSignal";
@@ -41150,6 +41489,33 @@ class BacktestUtils {
41150
41489
  });
41151
41490
  return await backtest.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
41152
41491
  };
41492
+ /**
41493
+ * Emits a `signal.info` notification for the currently active pending signal.
41494
+ *
41495
+ * @param symbol - Trading pair symbol
41496
+ * @param currentPrice - Market price at the time of the call
41497
+ * @param context - Execution context with strategyName, exchangeName, frameName
41498
+ * @param payload - Optional notification fields (notificationNote, notificationId)
41499
+ *
41500
+ * @throws {Error} If no active pending signal exists for the given symbol
41501
+ *
41502
+ * @example
41503
+ * ```typescript
41504
+ * await Backtest.commitSignalNotify("BTCUSDT", 42000, {
41505
+ * strategyName: "my-strategy",
41506
+ * exchangeName: "binance",
41507
+ * frameName: "1h"
41508
+ * }, { notificationNote: "RSI crossed 70" });
41509
+ * ```
41510
+ */
41511
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
41512
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_SIGNAL_NOTIFY, {
41513
+ symbol,
41514
+ currentPrice,
41515
+ context,
41516
+ });
41517
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, context, true);
41518
+ };
41153
41519
  /**
41154
41520
  * Gets statistical data from all closed signals for a symbol-strategy pair.
41155
41521
  *
@@ -41363,6 +41729,7 @@ const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
41363
41729
  const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
41364
41730
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
41365
41731
  const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
41732
+ const LIVE_METHOD_NAME_SIGNAL_NOTIFY = "Live.commitSignalNotify";
41366
41733
  const LIVE_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "LiveUtils.hasNoPendingSignal";
41367
41734
  const LIVE_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "LiveUtils.hasNoScheduledSignal";
41368
41735
  /**
@@ -44059,6 +44426,36 @@ class LiveUtils {
44059
44426
  frameName: "",
44060
44427
  }, cost);
44061
44428
  };
44429
+ /**
44430
+ * Emits a `signal.info` notification for the currently active pending signal.
44431
+ *
44432
+ * @param symbol - Trading pair symbol
44433
+ * @param currentPrice - Market price at the time of the call
44434
+ * @param context - Execution context with strategyName and exchangeName
44435
+ * @param payload - Optional notification fields (notificationNote, notificationId)
44436
+ *
44437
+ * @throws {Error} If no active pending signal exists for the given symbol
44438
+ *
44439
+ * @example
44440
+ * ```typescript
44441
+ * await Live.commitSignalNotify("BTCUSDT", 42000, {
44442
+ * strategyName: "my-strategy",
44443
+ * exchangeName: "binance",
44444
+ * }, { notificationNote: "RSI crossed 70" });
44445
+ * ```
44446
+ */
44447
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
44448
+ backtest.loggerService.info(LIVE_METHOD_NAME_SIGNAL_NOTIFY, {
44449
+ symbol,
44450
+ currentPrice,
44451
+ context,
44452
+ });
44453
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, {
44454
+ strategyName: context.strategyName,
44455
+ exchangeName: context.exchangeName,
44456
+ frameName: "",
44457
+ }, false);
44458
+ };
44062
44459
  /**
44063
44460
  * Gets statistical data from all live trading events for a symbol-strategy pair.
44064
44461
  *
@@ -45228,6 +45625,503 @@ async function listRiskSchema() {
45228
45625
  return await backtest.riskValidationService.list();
45229
45626
  }
45230
45627
 
45628
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
45629
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
45630
+ const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
45631
+ const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
45632
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
45633
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
45634
+ const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
45635
+ const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
45636
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
45637
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
45638
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
45639
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
45640
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
45641
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
45642
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
45643
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
45644
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
45645
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
45646
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
45647
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45648
+ const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45649
+ const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45650
+ const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
45651
+ /**
45652
+ * Builds a composite storage key from context parts.
45653
+ * Includes backtest flag as the last segment to prevent live/backtest collisions.
45654
+ * @param symbol - Trading pair symbol
45655
+ * @param strategyName - Strategy identifier
45656
+ * @param exchangeName - Exchange identifier
45657
+ * @param frameName - Frame identifier
45658
+ * @param backtest - Flag indicating if the context is backtest or live
45659
+ * @returns Composite key string
45660
+ */
45661
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
45662
+ const parts = [symbol, strategyName, exchangeName];
45663
+ if (frameName)
45664
+ parts.push(frameName);
45665
+ parts.push(backtest ? "backtest" : "live");
45666
+ return parts.join(":");
45667
+ };
45668
+ /**
45669
+ * Persistent storage adapter for backtest recent signals.
45670
+ *
45671
+ * Features:
45672
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45673
+ * - Handles active ping events only
45674
+ *
45675
+ * Use this adapter for backtest recent signal persistence across sessions.
45676
+ */
45677
+ class RecentPersistBacktestUtils {
45678
+ constructor() {
45679
+ /**
45680
+ * Handles active ping event.
45681
+ * Persists the latest signal to disk via PersistRecentAdapter.
45682
+ * @param event - Active ping contract with signal data and backtest flag
45683
+ */
45684
+ this.handleActivePing = async (event) => {
45685
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45686
+ signalId: event.data.id,
45687
+ });
45688
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45689
+ };
45690
+ /**
45691
+ * Retrieves the latest persisted signal for the given context.
45692
+ * @param symbol - Trading pair symbol
45693
+ * @param strategyName - Strategy identifier
45694
+ * @param exchangeName - Exchange identifier
45695
+ * @param frameName - Frame identifier
45696
+ * @param backtest - Flag indicating if the context is backtest or live
45697
+ * @returns The latest signal or null if not found
45698
+ */
45699
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45700
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
45701
+ symbol,
45702
+ strategyName,
45703
+ exchangeName,
45704
+ frameName,
45705
+ backtest: backtest$1,
45706
+ });
45707
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45708
+ };
45709
+ }
45710
+ }
45711
+ /**
45712
+ * In-memory storage adapter for backtest recent signals.
45713
+ *
45714
+ * Features:
45715
+ * - Stores the latest active signal per context key in memory only
45716
+ * - Fast read/write operations
45717
+ * - Data is lost when application restarts
45718
+ *
45719
+ * Use this adapter for testing or when persistence is not required.
45720
+ */
45721
+ class RecentMemoryBacktestUtils {
45722
+ constructor() {
45723
+ /** Map of composite context keys to the latest signal */
45724
+ this._signals = new Map();
45725
+ /**
45726
+ * Handles active ping event.
45727
+ * Stores the latest signal in memory under the composite context key.
45728
+ * @param event - Active ping contract with signal data and backtest flag
45729
+ */
45730
+ this.handleActivePing = async (event) => {
45731
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45732
+ signalId: event.data.id,
45733
+ });
45734
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45735
+ this._signals.set(key, event.data);
45736
+ };
45737
+ /**
45738
+ * Retrieves the latest in-memory signal for the given context.
45739
+ * @param symbol - Trading pair symbol
45740
+ * @param strategyName - Strategy identifier
45741
+ * @param exchangeName - Exchange identifier
45742
+ * @param frameName - Frame identifier
45743
+ * @param backtest - Flag indicating if the context is backtest or live
45744
+ * @returns The latest signal or null if not found
45745
+ */
45746
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45747
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45748
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45749
+ return this._signals.get(key) ?? null;
45750
+ };
45751
+ }
45752
+ }
45753
+ /**
45754
+ * Persistent storage adapter for live recent signals.
45755
+ *
45756
+ * Features:
45757
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45758
+ * - Handles active ping events only
45759
+ *
45760
+ * Use this adapter (default) for live recent signal persistence across sessions.
45761
+ */
45762
+ class RecentPersistLiveUtils {
45763
+ constructor() {
45764
+ /**
45765
+ * Handles active ping event.
45766
+ * Persists the latest signal to disk via PersistRecentAdapter.
45767
+ * @param event - Active ping contract with signal data and backtest flag
45768
+ */
45769
+ this.handleActivePing = async (event) => {
45770
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45771
+ signalId: event.data.id,
45772
+ });
45773
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45774
+ };
45775
+ /**
45776
+ * Retrieves the latest persisted signal for the given context.
45777
+ * @param symbol - Trading pair symbol
45778
+ * @param strategyName - Strategy identifier
45779
+ * @param exchangeName - Exchange identifier
45780
+ * @param frameName - Frame identifier
45781
+ * @param backtest - Flag indicating if the context is backtest or live
45782
+ * @returns The latest signal or null if not found
45783
+ */
45784
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45785
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
45786
+ symbol,
45787
+ strategyName,
45788
+ exchangeName,
45789
+ frameName,
45790
+ backtest: backtest$1,
45791
+ });
45792
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45793
+ };
45794
+ }
45795
+ }
45796
+ /**
45797
+ * In-memory storage adapter for live recent signals.
45798
+ *
45799
+ * Features:
45800
+ * - Stores the latest active signal per context key in memory only
45801
+ * - Fast read/write operations
45802
+ * - Data is lost when application restarts
45803
+ *
45804
+ * Use this adapter for testing or when persistence is not required.
45805
+ */
45806
+ class RecentMemoryLiveUtils {
45807
+ constructor() {
45808
+ /** Map of composite context keys to the latest signal */
45809
+ this._signals = new Map();
45810
+ /**
45811
+ * Handles active ping event.
45812
+ * Stores the latest signal in memory under the composite context key.
45813
+ * @param event - Active ping contract with signal data and backtest flag
45814
+ */
45815
+ this.handleActivePing = async (event) => {
45816
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45817
+ signalId: event.data.id,
45818
+ });
45819
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45820
+ this._signals.set(key, event.data);
45821
+ };
45822
+ /**
45823
+ * Retrieves the latest in-memory signal for the given context.
45824
+ * @param symbol - Trading pair symbol
45825
+ * @param strategyName - Strategy identifier
45826
+ * @param exchangeName - Exchange identifier
45827
+ * @param frameName - Frame identifier
45828
+ * @param backtest - Flag indicating if the context is backtest or live
45829
+ * @returns The latest signal or null if not found
45830
+ */
45831
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45832
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45833
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45834
+ return this._signals.get(key) ?? null;
45835
+ };
45836
+ }
45837
+ }
45838
+ /**
45839
+ * Backtest recent signal adapter with pluggable storage backend.
45840
+ *
45841
+ * Features:
45842
+ * - Adapter pattern for swappable storage implementations
45843
+ * - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
45844
+ * - Alternative adapter: RecentPersistBacktestUtils
45845
+ * - Convenience methods: usePersist(), useMemory()
45846
+ */
45847
+ class RecentBacktestAdapter {
45848
+ constructor() {
45849
+ /** Internal storage utils instance */
45850
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45851
+ /**
45852
+ * Handles active ping event.
45853
+ * Proxies call to the underlying storage adapter.
45854
+ * @param event - Active ping contract with signal data
45855
+ */
45856
+ this.handleActivePing = async (event) => {
45857
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45858
+ signalId: event.data.id,
45859
+ });
45860
+ return await this._recentBacktestUtils.handleActivePing(event);
45861
+ };
45862
+ /**
45863
+ * Retrieves the latest signal for the given context.
45864
+ * Proxies call to the underlying storage adapter.
45865
+ * @param symbol - Trading pair symbol
45866
+ * @param strategyName - Strategy identifier
45867
+ * @param exchangeName - Exchange identifier
45868
+ * @param frameName - Frame identifier
45869
+ * @param backtest - Flag indicating if the context is backtest or live
45870
+ * @returns The latest signal or null if not found
45871
+ */
45872
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45873
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45874
+ symbol,
45875
+ strategyName,
45876
+ exchangeName,
45877
+ frameName,
45878
+ backtest: backtest$1,
45879
+ });
45880
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45881
+ };
45882
+ /**
45883
+ * Sets the storage adapter constructor.
45884
+ * All future storage operations will use this adapter.
45885
+ * @param Ctor - Constructor for recent adapter
45886
+ */
45887
+ this.useRecentAdapter = (Ctor) => {
45888
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
45889
+ this._recentBacktestUtils = Reflect.construct(Ctor, []);
45890
+ };
45891
+ /**
45892
+ * Switches to persistent storage adapter.
45893
+ * Signals will be persisted to disk.
45894
+ */
45895
+ this.usePersist = () => {
45896
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
45897
+ this._recentBacktestUtils = new RecentPersistBacktestUtils();
45898
+ };
45899
+ /**
45900
+ * Switches to in-memory storage adapter (default).
45901
+ * Signals will be stored in memory only.
45902
+ */
45903
+ this.useMemory = () => {
45904
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
45905
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45906
+ };
45907
+ /**
45908
+ * Clears the cached utils instance by resetting to the default in-memory adapter.
45909
+ */
45910
+ this.clear = () => {
45911
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
45912
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45913
+ };
45914
+ }
45915
+ }
45916
+ /**
45917
+ * Live recent signal adapter with pluggable storage backend.
45918
+ *
45919
+ * Features:
45920
+ * - Adapter pattern for swappable storage implementations
45921
+ * - Default adapter: RecentPersistLiveUtils (persistent storage)
45922
+ * - Alternative adapter: RecentMemoryLiveUtils
45923
+ * - Convenience methods: usePersist(), useMemory()
45924
+ */
45925
+ class RecentLiveAdapter {
45926
+ constructor() {
45927
+ /** Internal storage utils instance */
45928
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45929
+ /**
45930
+ * Handles active ping event.
45931
+ * Proxies call to the underlying storage adapter.
45932
+ * @param event - Active ping contract with signal data
45933
+ */
45934
+ this.handleActivePing = async (event) => {
45935
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45936
+ signalId: event.data.id,
45937
+ });
45938
+ return await this._recentLiveUtils.handleActivePing(event);
45939
+ };
45940
+ /**
45941
+ * Retrieves the latest signal for the given context.
45942
+ * Proxies call to the underlying storage adapter.
45943
+ * @param symbol - Trading pair symbol
45944
+ * @param strategyName - Strategy identifier
45945
+ * @param exchangeName - Exchange identifier
45946
+ * @param frameName - Frame identifier
45947
+ * @param backtest - Flag indicating if the context is backtest or live
45948
+ * @returns The latest signal or null if not found
45949
+ */
45950
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45951
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45952
+ symbol,
45953
+ strategyName,
45954
+ exchangeName,
45955
+ frameName,
45956
+ backtest: backtest$1,
45957
+ });
45958
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45959
+ };
45960
+ /**
45961
+ * Sets the storage adapter constructor.
45962
+ * All future storage operations will use this adapter.
45963
+ * @param Ctor - Constructor for recent adapter
45964
+ */
45965
+ this.useRecentAdapter = (Ctor) => {
45966
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
45967
+ this._recentLiveUtils = Reflect.construct(Ctor, []);
45968
+ };
45969
+ /**
45970
+ * Switches to persistent storage adapter (default).
45971
+ * Signals will be persisted to disk.
45972
+ */
45973
+ this.usePersist = () => {
45974
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
45975
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45976
+ };
45977
+ /**
45978
+ * Switches to in-memory storage adapter.
45979
+ * Signals will be stored in memory only.
45980
+ */
45981
+ this.useMemory = () => {
45982
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
45983
+ this._recentLiveUtils = new RecentMemoryLiveUtils();
45984
+ };
45985
+ /**
45986
+ * Clears the cached utils instance by resetting to the default persistent adapter.
45987
+ */
45988
+ this.clear = () => {
45989
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
45990
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45991
+ };
45992
+ }
45993
+ }
45994
+ /**
45995
+ * Main recent signal adapter that manages both backtest and live recent signal storage.
45996
+ *
45997
+ * Features:
45998
+ * - Subscribes to activePingSubject for automatic storage updates
45999
+ * - Provides unified access to the latest signal for any context
46000
+ * - Singleshot enable pattern prevents duplicate subscriptions
46001
+ * - Cleanup function for proper unsubscription
46002
+ */
46003
+ class RecentAdapter {
46004
+ constructor() {
46005
+ /**
46006
+ * Enables recent signal storage by subscribing to activePingSubject.
46007
+ * Uses singleshot to ensure one-time subscription.
46008
+ *
46009
+ * @returns Cleanup function that unsubscribes from all emitters
46010
+ */
46011
+ this.enable = singleshot(() => {
46012
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
46013
+ let unBacktest;
46014
+ let unLive;
46015
+ {
46016
+ const unBacktestPingActive = activePingSubject
46017
+ .filter(({ backtest }) => backtest)
46018
+ .connect((event) => RecentBacktest.handleActivePing(event));
46019
+ unBacktest = compose(() => unBacktestPingActive());
46020
+ }
46021
+ {
46022
+ const unLivePingActive = activePingSubject
46023
+ .filter(({ backtest }) => !backtest)
46024
+ .connect((event) => RecentLive.handleActivePing(event));
46025
+ unLive = compose(() => unLivePingActive());
46026
+ }
46027
+ const unEnable = () => this.enable.clear();
46028
+ return compose(() => unBacktest(), () => unLive(), () => unEnable());
46029
+ });
46030
+ /**
46031
+ * Disables recent signal storage by unsubscribing from all emitters.
46032
+ * Safe to call multiple times.
46033
+ */
46034
+ this.disable = () => {
46035
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
46036
+ if (this.enable.hasValue()) {
46037
+ const lastSubscription = this.enable();
46038
+ lastSubscription();
46039
+ }
46040
+ };
46041
+ /**
46042
+ * Retrieves the latest active signal for the given symbol and context.
46043
+ * Searches backtest storage first, then live storage.
46044
+ *
46045
+ * @param symbol - Trading pair symbol
46046
+ * @param context - Execution context with strategyName, exchangeName, and frameName
46047
+ * @param backtest - Flag indicating if the context is backtest or live
46048
+ * @returns The latest signal or null if not found
46049
+ * @throws Error if RecentAdapter is not enabled
46050
+ */
46051
+ this.getLatestSignal = async (symbol, context) => {
46052
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46053
+ symbol,
46054
+ context,
46055
+ });
46056
+ if (!this.enable.hasValue()) {
46057
+ throw new Error("RecentAdapter is not enabled. Call enable() first.");
46058
+ }
46059
+ let result = null;
46060
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
46061
+ return result;
46062
+ }
46063
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
46064
+ return result;
46065
+ }
46066
+ return null;
46067
+ };
46068
+ }
46069
+ }
46070
+ /**
46071
+ * Global singleton instance of RecentAdapter.
46072
+ * Provides unified recent signal management for backtest and live trading.
46073
+ */
46074
+ const Recent = new RecentAdapter();
46075
+ /**
46076
+ * Global singleton instance of RecentLiveAdapter.
46077
+ * Provides live trading recent signal storage with pluggable backends.
46078
+ */
46079
+ const RecentLive = new RecentLiveAdapter();
46080
+ /**
46081
+ * Global singleton instance of RecentBacktestAdapter.
46082
+ * Provides backtest recent signal storage with pluggable backends.
46083
+ */
46084
+ const RecentBacktest = new RecentBacktestAdapter();
46085
+
46086
+ const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46087
+ /**
46088
+ * Returns the latest signal (pending or closed) for the current strategy context.
46089
+ *
46090
+ * Does not distinguish between active and closed signals — returns whichever
46091
+ * was recorded last. Useful for cooldown logic: e.g. skip opening a new position
46092
+ * for 4 hours after a stop-loss by checking the timestamp of the latest signal
46093
+ * regardless of its outcome.
46094
+ *
46095
+ * Searches backtest storage first, then live storage.
46096
+ * Returns null if no signal exists at all.
46097
+ *
46098
+ * Automatically detects backtest/live mode from execution context.
46099
+ *
46100
+ * @param symbol - Trading pair symbol
46101
+ * @returns Promise resolving to the latest signal or null
46102
+ *
46103
+ * @example
46104
+ * ```typescript
46105
+ * import { getLatestSignal } from "backtest-kit";
46106
+ *
46107
+ * const latest = await getLatestSignal("BTCUSDT");
46108
+ * if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
46109
+ * return; // cooldown after SL — skip new signal for 4 hours
46110
+ * }
46111
+ * ```
46112
+ */
46113
+ async function getLatestSignal(symbol) {
46114
+ backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
46115
+ if (!ExecutionContextService.hasContext()) {
46116
+ throw new Error("getLatestSignal requires an execution context");
46117
+ }
46118
+ if (!MethodContextService.hasContext()) {
46119
+ throw new Error("getLatestSignal requires a method context");
46120
+ }
46121
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46122
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46123
+ }
46124
+
45231
46125
  const DEFAULT_BM25_K1 = 1.5;
45232
46126
  const DEFAULT_BM25_B = 0.75;
45233
46127
  const DEFAULT_BM25_SCORE = 0.5;
@@ -47756,6 +48650,87 @@ class MarkdownUtils {
47756
48650
  backtest.maxDrawdownMarkdownService.unsubscribe();
47757
48651
  }
47758
48652
  };
48653
+ /**
48654
+ * Clears markdown report data selectively.
48655
+ *
48656
+ * Clears accumulated data for specified markdown services without unsubscribing.
48657
+ * Use this method to reset report data for specific services while keeping them active.
48658
+ *
48659
+ * Each cleared service will:
48660
+ * - Clear accumulated data for all reports
48661
+ * - Start fresh with new data for future events
48662
+ * - Not affect event subscriptions or report generation status
48663
+ *
48664
+ * @param config - Service configuration object specifying which services to clear. Defaults to clearing all services.
48665
+ * @param config.backtest - Clear backtest result report data
48666
+ * @param config.breakeven - Clear breakeven event tracking data
48667
+ * @param config.partial - Clear partial profit/loss event tracking data
48668
+ * @param config.heat - Clear portfolio heatmap analysis data
48669
+ * @param config.walker - Clear walker strategy comparison report data
48670
+ * @param config.performance - Clear performance bottleneck analysis data
48671
+ * @param config.risk - Clear risk rejection tracking data
48672
+ * @param config.schedule - Clear scheduled signal tracking data
48673
+ * @param config.live - Clear live trading event report data
48674
+ * @param config.strategy - Clear strategy report data
48675
+ * @param config.sync - Clear sync report data
48676
+ * @param config.highest_profit - Clear highest profit report data
48677
+ * @param config.max_drawdown - Clear max drawdown report data
48678
+ */
48679
+ 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) => {
48680
+ LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48681
+ backtest: bt,
48682
+ breakeven,
48683
+ heat,
48684
+ live,
48685
+ partial,
48686
+ performance,
48687
+ risk,
48688
+ strategy,
48689
+ schedule,
48690
+ walker,
48691
+ sync,
48692
+ highest_profit,
48693
+ });
48694
+ if (bt) {
48695
+ backtest.backtestMarkdownService.clear();
48696
+ }
48697
+ if (breakeven) {
48698
+ backtest.breakevenMarkdownService.clear();
48699
+ }
48700
+ if (heat) {
48701
+ backtest.heatMarkdownService.clear();
48702
+ }
48703
+ if (live) {
48704
+ backtest.liveMarkdownService.clear();
48705
+ }
48706
+ if (partial) {
48707
+ backtest.partialMarkdownService.clear();
48708
+ }
48709
+ if (performance) {
48710
+ backtest.performanceMarkdownService.clear();
48711
+ }
48712
+ if (risk) {
48713
+ backtest.riskMarkdownService.clear();
48714
+ }
48715
+ if (strategy) {
48716
+ backtest.strategyMarkdownService.clear();
48717
+ }
48718
+ if (schedule) {
48719
+ backtest.scheduleMarkdownService.clear();
48720
+ }
48721
+ if (walker) {
48722
+ backtest.walkerMarkdownService.clear();
48723
+ }
48724
+ if (sync) {
48725
+ backtest.syncMarkdownService.clear();
48726
+ }
48727
+ if (highest_profit) {
48728
+ backtest.highestProfitMarkdownService.clear();
48729
+ }
48730
+ if (max_drawdown) {
48731
+ backtest.maxDrawdownMarkdownService.clear();
48732
+ }
48733
+ };
47759
48734
  }
47760
48735
  }
47761
48736
  /**
@@ -47798,15 +48773,6 @@ class MarkdownAdapter extends MarkdownUtils {
47798
48773
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
47799
48774
  MarkdownWriter.useJsonl();
47800
48775
  }
47801
- /**
47802
- * Clears the memoized storage cache.
47803
- * Call this when process.cwd() changes between strategy iterations
47804
- * so new storage instances are created with the updated base path.
47805
- */
47806
- clear() {
47807
- LOGGER_SERVICE$1.log(MARKDOWN_METHOD_NAME_CLEAR);
47808
- MarkdownWriter.clear();
47809
- }
47810
48776
  /**
47811
48777
  * Switches to a dummy markdown adapter that discards all writes.
47812
48778
  * All future markdown writes will be no-ops.
@@ -48478,6 +49444,68 @@ class LogAdapter {
48478
49444
  */
48479
49445
  const Log = new LogAdapter();
48480
49446
 
49447
+ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49448
+ /** List of all global subjects whose listeners should be snapshotted for session isolation */
49449
+ const SUBJECT_ISOLATION_LIST = [
49450
+ activePingSubject,
49451
+ backtestScheduleOpenSubject,
49452
+ breakevenSubject,
49453
+ doneBacktestSubject,
49454
+ doneLiveSubject,
49455
+ errorEmitter,
49456
+ exitEmitter,
49457
+ highestProfitSubject,
49458
+ maxDrawdownSubject,
49459
+ partialLossSubject,
49460
+ partialProfitSubject,
49461
+ performanceEmitter,
49462
+ progressBacktestEmitter,
49463
+ riskSubject,
49464
+ schedulePingSubject,
49465
+ shutdownEmitter,
49466
+ signalBacktestEmitter,
49467
+ signalEmitter,
49468
+ signalLiveEmitter,
49469
+ strategyCommitSubject,
49470
+ syncSubject,
49471
+ validationSubject,
49472
+ signalNotifySubject,
49473
+ ];
49474
+ /**
49475
+ * Creates a snapshot function for a given subject by clearing its internal
49476
+ * events map and returning a restore function that can put the original listeners back.
49477
+ * @param subject The subject to snapshot
49478
+ * @returns A function that restores the subject's original listeners when called
49479
+ */
49480
+ const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
49481
+ const emitter = subject["_emitter"];
49482
+ const events = emitter["_events"];
49483
+ emitter["_events"] = {};
49484
+ return () => {
49485
+ emitter["_events"] = events;
49486
+ };
49487
+ };
49488
+ /**
49489
+ * Manages isolation of global event-bus state between backtest sessions.
49490
+ * Allows temporarily detaching all subject subscriptions so that one session
49491
+ * does not interfere with another, then restoring them afterwards.
49492
+ */
49493
+ class SessionUtils {
49494
+ constructor() {
49495
+ /**
49496
+ * Snapshots the current listener state of every global subject by replacing
49497
+ * their internal `_events` map with an empty object.
49498
+ * @returns A restore function that, when called, puts all original listeners back.
49499
+ */
49500
+ this.createSnapshot = () => {
49501
+ backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
49502
+ const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
49503
+ return compose(...snapshotList);
49504
+ };
49505
+ }
49506
+ }
49507
+ const Session = new SessionUtils();
49508
+
48481
49509
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
48482
49510
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
48483
49511
  const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
@@ -53287,7 +54315,44 @@ const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
53287
54315
  message: getErrorMessage(error),
53288
54316
  backtest: false,
53289
54317
  });
54318
+ /**
54319
+ * Creates a notification model for signal info events.
54320
+ * @param data - The signal info contract data
54321
+ * @returns NotificationModel for signal info event
54322
+ */
54323
+ const CREATE_SIGNAL_INFO_NOTIFICATION_FN = (data) => ({
54324
+ type: "signal.info",
54325
+ id: CREATE_KEY_FN$2(),
54326
+ timestamp: data.timestamp,
54327
+ backtest: data.backtest,
54328
+ symbol: data.symbol,
54329
+ strategyName: data.strategyName,
54330
+ exchangeName: data.exchangeName,
54331
+ signalId: data.data.id,
54332
+ currentPrice: data.currentPrice,
54333
+ position: data.data.position,
54334
+ priceOpen: data.data.priceOpen,
54335
+ priceTakeProfit: data.data.priceTakeProfit,
54336
+ priceStopLoss: data.data.priceStopLoss,
54337
+ originalPriceTakeProfit: data.data.originalPriceTakeProfit,
54338
+ originalPriceStopLoss: data.data.originalPriceStopLoss,
54339
+ originalPriceOpen: data.data.originalPriceOpen,
54340
+ totalEntries: data.data.totalEntries,
54341
+ totalPartials: data.data.totalPartials,
54342
+ pnl: data.data.pnl,
54343
+ pnlPercentage: data.data.pnl.pnlPercentage,
54344
+ pnlPriceOpen: data.data.pnl.priceOpen,
54345
+ pnlPriceClose: data.data.pnl.priceClose,
54346
+ pnlCost: data.data.pnl.pnlCost,
54347
+ pnlEntries: data.data.pnl.pnlEntries,
54348
+ note: data.note,
54349
+ notificationId: data.notificationId,
54350
+ scheduledAt: data.data.scheduledAt,
54351
+ pendingAt: data.data.pendingAt,
54352
+ createdAt: data.timestamp,
54353
+ });
53290
54354
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryBacktestUtils.handleSignal";
54355
+ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryBacktestUtils.handleSignalNotify";
53291
54356
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryBacktestUtils.handlePartialProfit";
53292
54357
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryBacktestUtils.handlePartialLoss";
53293
54358
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryBacktestUtils.handleBreakeven";
@@ -53300,6 +54365,7 @@ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "Notifi
53300
54365
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA = "NotificationMemoryBacktestUtils.getData";
53301
54366
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_DISPOSE = "NotificationMemoryBacktestUtils.dispose";
53302
54367
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryLiveUtils.handleSignal";
54368
+ const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryLiveUtils.handleSignalNotify";
53303
54369
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryLiveUtils.handlePartialProfit";
53304
54370
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryLiveUtils.handlePartialLoss";
53305
54371
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryLiveUtils.handleBreakeven";
@@ -53328,6 +54394,7 @@ const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_CLEAR = "NotificationLiveAdapter.cle
53328
54394
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistBacktestUtils.waitForInit";
53329
54395
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistBacktestUtils._updateNotifications";
53330
54396
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistBacktestUtils.handleSignal";
54397
+ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistBacktestUtils.handleSignalNotify";
53331
54398
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistBacktestUtils.handlePartialProfit";
53332
54399
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistBacktestUtils.handlePartialLoss";
53333
54400
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistBacktestUtils.handleBreakeven";
@@ -53342,6 +54409,7 @@ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_DISPOSE = "NotificationPersistBa
53342
54409
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistLiveUtils.waitForInit";
53343
54410
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistLiveUtils._updateNotifications";
53344
54411
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistLiveUtils.handleSignal";
54412
+ const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistLiveUtils.handleSignalNotify";
53345
54413
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistLiveUtils.handlePartialProfit";
53346
54414
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistLiveUtils.handlePartialLoss";
53347
54415
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistLiveUtils.handleBreakeven";
@@ -53384,6 +54452,12 @@ class NotificationMemoryBacktestUtils {
53384
54452
  this._addNotification(notification);
53385
54453
  }
53386
54454
  };
54455
+ this.handleSignalNotify = async (data) => {
54456
+ backtest.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54457
+ signalId: data.data.id,
54458
+ });
54459
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54460
+ };
53387
54461
  /**
53388
54462
  * Handles partial profit availability event.
53389
54463
  * @param data - The partial profit contract data
@@ -53528,6 +54602,8 @@ class NotificationDummyBacktestUtils {
53528
54602
  */
53529
54603
  this.handleSignal = async () => {
53530
54604
  };
54605
+ this.handleSignalNotify = async () => {
54606
+ };
53531
54607
  /**
53532
54608
  * No-op handler for partial profit event.
53533
54609
  */
@@ -53635,6 +54711,14 @@ class NotificationPersistBacktestUtils {
53635
54711
  await this._updateNotifications();
53636
54712
  }
53637
54713
  };
54714
+ this.handleSignalNotify = async (data) => {
54715
+ backtest.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54716
+ signalId: data.data.id,
54717
+ });
54718
+ await this.waitForInit();
54719
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54720
+ await this._updateNotifications();
54721
+ };
53638
54722
  /**
53639
54723
  * Handles partial profit availability event.
53640
54724
  * @param data - The partial profit contract data
@@ -53836,6 +54920,12 @@ class NotificationMemoryLiveUtils {
53836
54920
  this._addNotification(notification);
53837
54921
  }
53838
54922
  };
54923
+ this.handleSignalNotify = async (data) => {
54924
+ backtest.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54925
+ signalId: data.data.id,
54926
+ });
54927
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54928
+ };
53839
54929
  /**
53840
54930
  * Handles partial profit availability event.
53841
54931
  * @param data - The partial profit contract data
@@ -53980,6 +55070,8 @@ class NotificationDummyLiveUtils {
53980
55070
  */
53981
55071
  this.handleSignal = async () => {
53982
55072
  };
55073
+ this.handleSignalNotify = async () => {
55074
+ };
53983
55075
  /**
53984
55076
  * No-op handler for partial profit event.
53985
55077
  */
@@ -54088,6 +55180,14 @@ class NotificationPersistLiveUtils {
54088
55180
  await this._updateNotifications();
54089
55181
  }
54090
55182
  };
55183
+ this.handleSignalNotify = async (data) => {
55184
+ backtest.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55185
+ signalId: data.data.id,
55186
+ });
55187
+ await this.waitForInit();
55188
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55189
+ await this._updateNotifications();
55190
+ };
54091
55191
  /**
54092
55192
  * Handles partial profit availability event.
54093
55193
  * @param data - The partial profit contract data
@@ -54281,6 +55381,9 @@ class NotificationBacktestAdapter {
54281
55381
  this.handleSignal = async (data) => {
54282
55382
  return await this._notificationBacktestUtils.handleSignal(data);
54283
55383
  };
55384
+ this.handleSignalNotify = async (data) => {
55385
+ return await this._notificationBacktestUtils.handleSignalNotify(data);
55386
+ };
54284
55387
  /**
54285
55388
  * Handles partial profit availability event.
54286
55389
  * Proxies call to the underlying notification adapter.
@@ -54436,6 +55539,9 @@ class NotificationLiveAdapter {
54436
55539
  this.handleSignal = async (data) => {
54437
55540
  return await this._notificationLiveUtils.handleSignal(data);
54438
55541
  };
55542
+ this.handleSignalNotify = async (data) => {
55543
+ return await this._notificationLiveUtils.handleSignalNotify(data);
55544
+ };
54439
55545
  /**
54440
55546
  * Handles partial profit availability event.
54441
55547
  * Proxies call to the underlying notification adapter.
@@ -54614,7 +55720,10 @@ class NotificationAdapter {
54614
55720
  const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
54615
55721
  const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
54616
55722
  const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
54617
- unBacktest = compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation());
55723
+ const unBacktestSignalNotify = signalNotifySubject
55724
+ .filter(({ backtest }) => backtest)
55725
+ .connect((data) => NotificationBacktest.handleSignalNotify(data));
55726
+ unBacktest = compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
54618
55727
  }
54619
55728
  {
54620
55729
  const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
@@ -54639,7 +55748,10 @@ class NotificationAdapter {
54639
55748
  const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
54640
55749
  const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
54641
55750
  const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
54642
- unLive = compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation());
55751
+ const unLiveSignalNotify = signalNotifySubject
55752
+ .filter(({ backtest }) => !backtest)
55753
+ .connect((data) => NotificationLive.handleSignalNotify(data));
55754
+ unLive = compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
54643
55755
  }
54644
55756
  return () => {
54645
55757
  unLive();
@@ -54715,12 +55827,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
54715
55827
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
54716
55828
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
54717
55829
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
55830
+ const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
54718
55831
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
54719
55832
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
55833
+ const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
54720
55834
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
54721
55835
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
54722
55836
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
54723
55837
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
55838
+ const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
54724
55839
  const MS_PER_MINUTE$1 = 60000;
54725
55840
  const INTERVAL_MINUTES$1 = {
54726
55841
  "1m": 1,
@@ -54874,11 +55989,17 @@ class CacheFnInstance {
54874
55989
  return cached;
54875
55990
  }
54876
55991
  }
55992
+ const value = this.fn(...args);
54877
55993
  const newCache = {
54878
55994
  when: currentWhen,
54879
- value: this.fn(...args),
55995
+ value,
54880
55996
  };
54881
55997
  this._cacheMap.set(key, newCache);
55998
+ if (value && value instanceof Promise) {
55999
+ value.catch(() => {
56000
+ this._cacheMap.delete(key);
56001
+ });
56002
+ }
54882
56003
  return newCache;
54883
56004
  };
54884
56005
  /**
@@ -54910,6 +56031,36 @@ class CacheFnInstance {
54910
56031
  }
54911
56032
  }
54912
56033
  };
56034
+ /**
56035
+ * Check whether a valid (non-expired) cache entry exists for the current context and arguments.
56036
+ *
56037
+ * Returns `true` if a cached value exists and its interval is still current.
56038
+ * Returns `false` if there is no entry or the cached entry has expired.
56039
+ *
56040
+ * Requires active execution context and method context.
56041
+ *
56042
+ * @param args - Arguments to look up in the cache
56043
+ * @returns `true` if a fresh cached value exists, `false` otherwise
56044
+ */
56045
+ this.hasValue = (...args) => {
56046
+ if (!MethodContextService.hasContext()) {
56047
+ throw new Error("CacheFnInstance hasValue requires method context");
56048
+ }
56049
+ if (!ExecutionContextService.hasContext()) {
56050
+ throw new Error("CacheFnInstance hasValue requires execution context");
56051
+ }
56052
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56053
+ const argKey = String(this.key(args));
56054
+ const key = `${contextKey}:${argKey}`;
56055
+ const cached = this._cacheMap.get(key);
56056
+ if (!cached) {
56057
+ return false;
56058
+ }
56059
+ const currentWhen = backtest.executionContextService.context.when;
56060
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
56061
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
56062
+ return currentAligned === cachedAligned;
56063
+ };
54913
56064
  /**
54914
56065
  * Garbage collect expired cache entries.
54915
56066
  *
@@ -55034,6 +56185,33 @@ class CacheFileInstance {
55034
56185
  await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
55035
56186
  return result;
55036
56187
  };
56188
+ /**
56189
+ * Check whether a cached value exists on disk for the given arguments and current interval.
56190
+ *
56191
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56192
+ * Returns `false` if no record is found.
56193
+ *
56194
+ * Requires active execution context and method context.
56195
+ *
56196
+ * @param args - Arguments forwarded to the key generator
56197
+ * @returns `true` if a cached record exists, `false` otherwise
56198
+ */
56199
+ this.hasValue = async (...args) => {
56200
+ backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56201
+ if (!MethodContextService.hasContext()) {
56202
+ throw new Error("CacheFileInstance hasValue requires method context");
56203
+ }
56204
+ if (!ExecutionContextService.hasContext()) {
56205
+ throw new Error("CacheFileInstance hasValue requires execution context");
56206
+ }
56207
+ const [symbol, ...rest] = args;
56208
+ const { when } = backtest.executionContextService.context;
56209
+ const alignedTs = align$1(when.getTime(), this.interval);
56210
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56211
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
56212
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
56213
+ return cached !== null;
56214
+ };
55037
56215
  /**
55038
56216
  * Soft-delete all persisted records for this instance's bucket.
55039
56217
  * After this call the next `run()` will recompute and re-cache the value.
@@ -55120,23 +56298,30 @@ class CacheUtils {
55120
56298
  wrappedFn.clear = () => {
55121
56299
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
55122
56300
  if (!MethodContextService.hasContext()) {
55123
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55124
- return;
56301
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
55125
56302
  }
55126
56303
  if (!ExecutionContextService.hasContext()) {
55127
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55128
- return;
56304
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
55129
56305
  }
55130
56306
  this._getFnInstance.get(run)?.clear();
55131
56307
  };
55132
56308
  wrappedFn.gc = () => {
55133
56309
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
55134
56310
  if (!ExecutionContextService.hasContext()) {
55135
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_GC} called without execution context, skipping`);
55136
- return;
56311
+ throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
55137
56312
  }
55138
56313
  return this._getFnInstance.get(run)?.gc();
55139
56314
  };
56315
+ wrappedFn.hasValue = (...args) => {
56316
+ backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
56317
+ if (!MethodContextService.hasContext()) {
56318
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56319
+ }
56320
+ if (!ExecutionContextService.hasContext()) {
56321
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56322
+ }
56323
+ return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
56324
+ };
55140
56325
  return wrappedFn;
55141
56326
  };
55142
56327
  /**
@@ -55188,8 +56373,25 @@ class CacheUtils {
55188
56373
  };
55189
56374
  wrappedFn.clear = async () => {
55190
56375
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
56376
+ if (!MethodContextService.hasContext()) {
56377
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
56378
+ }
56379
+ if (!ExecutionContextService.hasContext()) {
56380
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
56381
+ }
55191
56382
  await this._getFileInstance.get(run)?.clear();
55192
56383
  };
56384
+ wrappedFn.hasValue = async (...args) => {
56385
+ backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
56386
+ if (!MethodContextService.hasContext()) {
56387
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56388
+ }
56389
+ if (!ExecutionContextService.hasContext()) {
56390
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56391
+ }
56392
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56393
+ return await instance.hasValue(...args);
56394
+ };
55193
56395
  return wrappedFn;
55194
56396
  };
55195
56397
  /**
@@ -55258,11 +56460,14 @@ const Cache = new CacheUtils();
55258
56460
 
55259
56461
  const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
55260
56462
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
56463
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
55261
56464
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
55262
56465
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
55263
56466
  const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
56467
+ const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
55264
56468
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
55265
56469
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
56470
+ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
55266
56471
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
55267
56472
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
55268
56473
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
@@ -55375,7 +56580,7 @@ class IntervalFnInstance {
55375
56580
  * within the same interval or when `fn` itself returned `null`
55376
56581
  * @throws Error if method context, execution context, or interval is missing
55377
56582
  */
55378
- this.run = async (...args) => {
56583
+ this.run = (...args) => {
55379
56584
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
55380
56585
  const step = INTERVAL_MINUTES[this.interval];
55381
56586
  {
@@ -55397,10 +56602,15 @@ class IntervalFnInstance {
55397
56602
  if (this._stateMap.get(stateKey) === currentAligned) {
55398
56603
  return null;
55399
56604
  }
55400
- const result = await this.fn.apply(null, args);
56605
+ const result = this.fn.apply(null, args);
55401
56606
  if (result !== null) {
55402
56607
  this._stateMap.set(stateKey, currentAligned);
55403
56608
  }
56609
+ if (result && result instanceof Promise) {
56610
+ result.catch(() => {
56611
+ this._stateMap.delete(stateKey);
56612
+ });
56613
+ }
55404
56614
  return result;
55405
56615
  };
55406
56616
  /**
@@ -55420,6 +56630,31 @@ class IntervalFnInstance {
55420
56630
  }
55421
56631
  }
55422
56632
  };
56633
+ /**
56634
+ * Check whether the function has already fired for the current interval and context.
56635
+ *
56636
+ * Returns `true` if the function fired (non-null result) within the current interval boundary.
56637
+ * Returns `false` if there is no recorded firing for this interval.
56638
+ *
56639
+ * Requires active method context and execution context.
56640
+ *
56641
+ * @param args - Arguments to look up in the state map
56642
+ * @returns `true` if the function has already fired this interval, `false` otherwise
56643
+ */
56644
+ this.hasValue = (...args) => {
56645
+ if (!MethodContextService.hasContext()) {
56646
+ throw new Error("IntervalFnInstance hasValue requires method context");
56647
+ }
56648
+ if (!ExecutionContextService.hasContext()) {
56649
+ throw new Error("IntervalFnInstance hasValue requires execution context");
56650
+ }
56651
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56652
+ const currentWhen = backtest.executionContextService.context.when;
56653
+ const currentAligned = align(currentWhen.getTime(), this.interval);
56654
+ const argKey = this.key(args);
56655
+ const stateKey = `${contextKey}:${argKey}`;
56656
+ return this._stateMap.get(stateKey) === currentAligned;
56657
+ };
55423
56658
  /**
55424
56659
  * Garbage collect expired state entries.
55425
56660
  *
@@ -55541,6 +56776,33 @@ class IntervalFileInstance {
55541
56776
  }
55542
56777
  return result;
55543
56778
  };
56779
+ /**
56780
+ * Check whether the function has already fired for the current interval on disk.
56781
+ *
56782
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56783
+ * Returns `false` if no record is found.
56784
+ *
56785
+ * Requires active execution context and method context.
56786
+ *
56787
+ * @param args - Arguments forwarded to the key generator
56788
+ * @returns `true` if a fired record exists, `false` otherwise
56789
+ */
56790
+ this.hasValue = async (...args) => {
56791
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56792
+ if (!MethodContextService.hasContext()) {
56793
+ throw new Error("IntervalFileInstance hasValue requires method context");
56794
+ }
56795
+ if (!ExecutionContextService.hasContext()) {
56796
+ throw new Error("IntervalFileInstance hasValue requires execution context");
56797
+ }
56798
+ const [symbol, ...rest] = args;
56799
+ const { when } = backtest.executionContextService.context;
56800
+ const alignedMs = align(when.getTime(), this.interval);
56801
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56802
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
56803
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
56804
+ return cached !== null;
56805
+ };
55544
56806
  /**
55545
56807
  * Soft-delete all persisted records for this instance's bucket.
55546
56808
  * After this call the function will fire again on the next `run()`.
@@ -55621,23 +56883,30 @@ class IntervalUtils {
55621
56883
  wrappedFn.clear = () => {
55622
56884
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
55623
56885
  if (!MethodContextService.hasContext()) {
55624
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55625
- return;
56886
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
55626
56887
  }
55627
56888
  if (!ExecutionContextService.hasContext()) {
55628
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55629
- return;
56889
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
55630
56890
  }
55631
56891
  this._getInstance.get(run)?.clear();
55632
56892
  };
55633
56893
  wrappedFn.gc = () => {
55634
56894
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
55635
56895
  if (!ExecutionContextService.hasContext()) {
55636
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
55637
- return;
56896
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
55638
56897
  }
55639
56898
  return this._getInstance.get(run)?.gc();
55640
56899
  };
56900
+ wrappedFn.hasValue = (...args) => {
56901
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
56902
+ if (!MethodContextService.hasContext()) {
56903
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56904
+ }
56905
+ if (!ExecutionContextService.hasContext()) {
56906
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56907
+ }
56908
+ return this._getInstance.get(run)?.hasValue(...args) ?? false;
56909
+ };
55641
56910
  return wrappedFn;
55642
56911
  };
55643
56912
  /**
@@ -55680,8 +56949,25 @@ class IntervalUtils {
55680
56949
  };
55681
56950
  wrappedFn.clear = async () => {
55682
56951
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
56952
+ if (!MethodContextService.hasContext()) {
56953
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
56954
+ }
56955
+ if (!ExecutionContextService.hasContext()) {
56956
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
56957
+ }
55683
56958
  await this._getFileInstance.get(run)?.clear();
55684
56959
  };
56960
+ wrappedFn.hasValue = async (...args) => {
56961
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
56962
+ if (!MethodContextService.hasContext()) {
56963
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56964
+ }
56965
+ if (!ExecutionContextService.hasContext()) {
56966
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56967
+ }
56968
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56969
+ return await instance.hasValue(...args);
56970
+ };
55685
56971
  return wrappedFn;
55686
56972
  };
55687
56973
  /**
@@ -56855,4 +58141,4 @@ const validateSignal = (signal, currentPrice) => {
56855
58141
  return !errors.length;
56856
58142
  };
56857
58143
 
56858
- 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, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, 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, 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 };
58144
+ 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, 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, 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 };