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.cjs CHANGED
@@ -147,6 +147,9 @@ const reportServices$1 = {
147
147
  highestProfitReportService: Symbol('highestProfitReportService'),
148
148
  maxDrawdownReportService: Symbol('maxDrawdownReportService'),
149
149
  };
150
+ const helperServices$1 = {
151
+ notificationHelperService: Symbol('notificationHelperService'),
152
+ };
150
153
  const validationServices$1 = {
151
154
  exchangeValidationService: Symbol('exchangeValidationService'),
152
155
  strategyValidationService: Symbol('strategyValidationService'),
@@ -172,6 +175,7 @@ const TYPES = {
172
175
  ...markdownServices$1,
173
176
  ...reportServices$1,
174
177
  ...validationServices$1,
178
+ ...helperServices$1,
175
179
  };
176
180
 
177
181
  /**
@@ -764,6 +768,11 @@ const highestProfitSubject = new functoolsKit.Subject();
764
768
  * Allows users to track drawdown levels and implement custom risk management logic based on drawdown thresholds.
765
769
  */
766
770
  const maxDrawdownSubject = new functoolsKit.Subject();
771
+ /**
772
+ * Signal info emitter for user-defined informational notes on open positions.
773
+ * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
774
+ */
775
+ const signalNotifySubject = new functoolsKit.Subject();
767
776
 
768
777
  var emitters = /*#__PURE__*/Object.freeze({
769
778
  __proto__: null,
@@ -788,6 +797,7 @@ var emitters = /*#__PURE__*/Object.freeze({
788
797
  signalBacktestEmitter: signalBacktestEmitter,
789
798
  signalEmitter: signalEmitter,
790
799
  signalLiveEmitter: signalLiveEmitter,
800
+ signalNotifySubject: signalNotifySubject,
791
801
  strategyCommitSubject: strategyCommitSubject,
792
802
  syncSubject: syncSubject,
793
803
  validationSubject: validationSubject,
@@ -1002,6 +1012,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
1002
1012
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
1003
1013
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
1004
1014
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1015
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1016
+ const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1017
+ const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
1018
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
1019
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
1020
+ const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
1005
1021
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
1006
1022
  const BASE_UNLINK_RETRY_COUNT = 5;
1007
1023
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2848,6 +2864,115 @@ class PersistMemoryUtils {
2848
2864
  * ```
2849
2865
  */
2850
2866
  const PersistMemoryAdapter = new PersistMemoryUtils();
2867
+ /**
2868
+ * Utility class for managing recent signal persistence.
2869
+ *
2870
+ * Features:
2871
+ * - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
2872
+ * - Custom adapter support
2873
+ * - Atomic read/write operations
2874
+ * - Crash-safe recent signal state management
2875
+ *
2876
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2877
+ */
2878
+ class PersistRecentUtils {
2879
+ constructor() {
2880
+ this.PersistRecentFactory = PersistBase;
2881
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
2882
+ this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
2883
+ `./dump/data/recent/`,
2884
+ ]));
2885
+ /**
2886
+ * Reads the latest persisted recent signal for a given context.
2887
+ *
2888
+ * Returns null if no recent signal exists.
2889
+ *
2890
+ * @param symbol - Trading pair symbol
2891
+ * @param strategyName - Strategy identifier
2892
+ * @param exchangeName - Exchange identifier
2893
+ * @param frameName - Frame identifier
2894
+ * @returns Promise resolving to recent signal or null
2895
+ */
2896
+ this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
2897
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
2898
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2899
+ const isInitial = !this.getStorage.has(key);
2900
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2901
+ await stateStorage.waitForInit(isInitial);
2902
+ if (await stateStorage.hasValue(symbol)) {
2903
+ return await stateStorage.readValue(symbol);
2904
+ }
2905
+ return null;
2906
+ };
2907
+ /**
2908
+ * Writes the latest recent signal to disk with atomic file writes.
2909
+ *
2910
+ * Uses symbol as the entity ID within the per-context storage instance.
2911
+ * Uses atomic writes to prevent corruption on crashes.
2912
+ *
2913
+ * @param signalRow - Recent signal data to persist
2914
+ * @param symbol - Trading pair symbol
2915
+ * @param strategyName - Strategy identifier
2916
+ * @param exchangeName - Exchange identifier
2917
+ * @param frameName - Frame identifier
2918
+ * @returns Promise that resolves when write is complete
2919
+ */
2920
+ this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
2921
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
2922
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2923
+ const isInitial = !this.getStorage.has(key);
2924
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2925
+ await stateStorage.waitForInit(isInitial);
2926
+ await stateStorage.writeValue(symbol, signalRow);
2927
+ };
2928
+ }
2929
+ createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
2930
+ const parts = [symbol, strategyName, exchangeName];
2931
+ if (frameName)
2932
+ parts.push(frameName);
2933
+ parts.push(backtest ? "backtest" : "live");
2934
+ return parts;
2935
+ }
2936
+ /**
2937
+ * Registers a custom persistence adapter.
2938
+ *
2939
+ * @param Ctor - Custom PersistBase constructor
2940
+ */
2941
+ usePersistRecentAdapter(Ctor) {
2942
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
2943
+ this.PersistRecentFactory = Ctor;
2944
+ }
2945
+ /**
2946
+ * Clears the memoized storage cache.
2947
+ * Call this when process.cwd() changes between strategy iterations
2948
+ * so new storage instances are created with the updated base path.
2949
+ */
2950
+ clear() {
2951
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
2952
+ this.getStorage.clear();
2953
+ }
2954
+ /**
2955
+ * Switches to the default JSON persist adapter.
2956
+ * All future persistence writes will use JSON storage.
2957
+ */
2958
+ useJson() {
2959
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
2960
+ this.usePersistRecentAdapter(PersistBase);
2961
+ }
2962
+ /**
2963
+ * Switches to a dummy persist adapter that discards all writes.
2964
+ * All future persistence writes will be no-ops.
2965
+ */
2966
+ useDummy() {
2967
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
2968
+ this.usePersistRecentAdapter(PersistDummy);
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Global singleton instance of PersistRecentUtils.
2973
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2974
+ */
2975
+ const PersistRecentAdapter = new PersistRecentUtils();
2851
2976
 
2852
2977
  var _a$2, _b$2;
2853
2978
  const BUSY_DELAY = 100;
@@ -10175,7 +10300,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10175
10300
  * @param backtest - Whether running in backtest mode
10176
10301
  * @returns Unique string key for memoization
10177
10302
  */
10178
- const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
10303
+ const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10179
10304
  const parts = [symbol, strategyName, exchangeName];
10180
10305
  if (frameName)
10181
10306
  parts.push(frameName);
@@ -10442,7 +10567,7 @@ class StrategyConnectionService {
10442
10567
  * @param backtest - Whether running in backtest mode
10443
10568
  * @returns Configured ClientStrategy instance
10444
10569
  */
10445
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10570
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10446
10571
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10447
10572
  return new ClientStrategy({
10448
10573
  symbol,
@@ -11363,7 +11488,7 @@ class StrategyConnectionService {
11363
11488
  }
11364
11489
  return;
11365
11490
  }
11366
- const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11491
+ const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11367
11492
  if (!this.getStrategy.has(key)) {
11368
11493
  return;
11369
11494
  }
@@ -12532,7 +12657,7 @@ class ClientRisk {
12532
12657
  * @param backtest - Whether running in backtest mode
12533
12658
  * @returns Unique string key for memoization
12534
12659
  */
12535
- const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12660
+ const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
12536
12661
  const parts = [riskName, exchangeName];
12537
12662
  if (frameName)
12538
12663
  parts.push(frameName);
@@ -12632,7 +12757,7 @@ class RiskConnectionService {
12632
12757
  * @param backtest - True if backtest mode, false if live mode
12633
12758
  * @returns Configured ClientRisk instance
12634
12759
  */
12635
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12760
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12636
12761
  const schema = this.riskSchemaService.get(riskName);
12637
12762
  return new ClientRisk({
12638
12763
  ...schema,
@@ -12701,7 +12826,7 @@ class RiskConnectionService {
12701
12826
  payload,
12702
12827
  });
12703
12828
  if (payload) {
12704
- const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12829
+ const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12705
12830
  this.getRisk.clear(key);
12706
12831
  }
12707
12832
  else {
@@ -13745,7 +13870,7 @@ class ClientAction {
13745
13870
  * @param backtest - Whether running in backtest mode
13746
13871
  * @returns Unique string key for memoization
13747
13872
  */
13748
- const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13873
+ const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
13749
13874
  const parts = [actionName, strategyName, exchangeName];
13750
13875
  if (frameName)
13751
13876
  parts.push(frameName);
@@ -13797,7 +13922,7 @@ class ActionConnectionService {
13797
13922
  * @param backtest - True if backtest mode, false if live mode
13798
13923
  * @returns Configured ClientAction instance
13799
13924
  */
13800
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13925
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13801
13926
  const schema = this.actionSchemaService.get(actionName);
13802
13927
  return new ClientAction({
13803
13928
  ...schema,
@@ -14008,7 +14133,7 @@ class ActionConnectionService {
14008
14133
  await Promise.all(actions.map(async (action) => await action.dispose()));
14009
14134
  return;
14010
14135
  }
14011
- const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14136
+ const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14012
14137
  if (!this.getAction.has(key)) {
14013
14138
  return;
14014
14139
  }
@@ -14019,14 +14144,14 @@ class ActionConnectionService {
14019
14144
  }
14020
14145
  }
14021
14146
 
14022
- const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
14147
+ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14023
14148
  /**
14024
14149
  * Creates a unique key for memoizing validate calls.
14025
14150
  * Key format: "exchangeName"
14026
14151
  * @param exchangeName - Exchange name
14027
14152
  * @returns Unique string key for memoization
14028
14153
  */
14029
- const CREATE_KEY_FN$q = (exchangeName) => {
14154
+ const CREATE_KEY_FN$s = (exchangeName) => {
14030
14155
  return exchangeName;
14031
14156
  };
14032
14157
  /**
@@ -14050,11 +14175,11 @@ class ExchangeCoreService {
14050
14175
  * @param exchangeName - Name of the exchange to validate
14051
14176
  * @returns Promise that resolves when validation is complete
14052
14177
  */
14053
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
14054
- this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14178
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14179
+ this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14055
14180
  exchangeName,
14056
14181
  });
14057
- this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$2);
14182
+ this.exchangeValidationService.validate(exchangeName, METHOD_NAME_VALIDATE$3);
14058
14183
  });
14059
14184
  /**
14060
14185
  * Fetches historical candles with execution context.
@@ -14295,14 +14420,14 @@ class ExchangeCoreService {
14295
14420
  }
14296
14421
  }
14297
14422
 
14298
- const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14423
+ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14299
14424
  /**
14300
14425
  * Creates a unique key for memoizing validate calls.
14301
14426
  * Key format: "strategyName:exchangeName:frameName"
14302
14427
  * @param context - Execution context with strategyName, exchangeName, frameName
14303
14428
  * @returns Unique string key for memoization
14304
14429
  */
14305
- const CREATE_KEY_FN$p = (context) => {
14430
+ const CREATE_KEY_FN$r = (context) => {
14306
14431
  const parts = [context.strategyName, context.exchangeName];
14307
14432
  if (context.frameName)
14308
14433
  parts.push(context.frameName);
@@ -14334,16 +14459,16 @@ class StrategyCoreService {
14334
14459
  * @param context - Execution context with strategyName, exchangeName, frameName
14335
14460
  * @returns Promise that resolves when validation is complete
14336
14461
  */
14337
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
14338
- this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14462
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14463
+ this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14339
14464
  context,
14340
14465
  });
14341
14466
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
14342
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
14343
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
14344
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
14345
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
14346
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
14467
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$2);
14468
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$2);
14469
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$2);
14470
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2);
14471
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$2));
14347
14472
  });
14348
14473
  /**
14349
14474
  * Retrieves the currently active pending signal for the symbol.
@@ -15672,7 +15797,7 @@ class SizingGlobalService {
15672
15797
  * @param context - Context with riskName, exchangeName, frameName
15673
15798
  * @returns Unique string key for memoization
15674
15799
  */
15675
- const CREATE_KEY_FN$o = (context) => {
15800
+ const CREATE_KEY_FN$q = (context) => {
15676
15801
  const parts = [context.riskName, context.exchangeName];
15677
15802
  if (context.frameName)
15678
15803
  parts.push(context.frameName);
@@ -15698,7 +15823,7 @@ class RiskGlobalService {
15698
15823
  * @param payload - Payload with riskName, exchangeName and frameName
15699
15824
  * @returns Promise that resolves when validation is complete
15700
15825
  */
15701
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15826
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
15702
15827
  this.loggerService.log("riskGlobalService validate", {
15703
15828
  context,
15704
15829
  });
@@ -15769,14 +15894,14 @@ class RiskGlobalService {
15769
15894
  }
15770
15895
  }
15771
15896
 
15772
- const METHOD_NAME_VALIDATE = "actionCoreService validate";
15897
+ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
15773
15898
  /**
15774
15899
  * Creates a unique key for memoizing validate calls.
15775
15900
  * Key format: "strategyName:exchangeName:frameName"
15776
15901
  * @param context - Execution context with strategyName, exchangeName, frameName
15777
15902
  * @returns Unique string key for memoization
15778
15903
  */
15779
- const CREATE_KEY_FN$n = (context) => {
15904
+ const CREATE_KEY_FN$p = (context) => {
15780
15905
  const parts = [context.strategyName, context.exchangeName];
15781
15906
  if (context.frameName)
15782
15907
  parts.push(context.frameName);
@@ -15820,17 +15945,17 @@ class ActionCoreService {
15820
15945
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15821
15946
  * @returns Promise that resolves when all validations complete
15822
15947
  */
15823
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15824
- this.loggerService.log(METHOD_NAME_VALIDATE, {
15948
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15949
+ this.loggerService.log(METHOD_NAME_VALIDATE$1, {
15825
15950
  context,
15826
15951
  });
15827
15952
  const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
15828
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
15829
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
15830
- context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
15831
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
15832
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
15833
- actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
15953
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE$1);
15954
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE$1);
15955
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE$1);
15956
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1);
15957
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE$1));
15958
+ actions && actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE$1));
15834
15959
  });
15835
15960
  /**
15836
15961
  * Initializes all ClientAction instances for the strategy.
@@ -20863,7 +20988,7 @@ const ReportWriter = new ReportWriterAdapter();
20863
20988
  * @param backtest - Whether running in backtest mode
20864
20989
  * @returns Unique string key for memoization
20865
20990
  */
20866
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20991
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
20867
20992
  const parts = [symbol, strategyName, exchangeName];
20868
20993
  if (frameName)
20869
20994
  parts.push(frameName);
@@ -21109,7 +21234,7 @@ class BacktestMarkdownService {
21109
21234
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21110
21235
  * Each combination gets its own isolated storage instance.
21111
21236
  */
21112
- this.getStorage = functoolsKit.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));
21237
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21113
21238
  /**
21114
21239
  * Processes tick events and accumulates closed signals.
21115
21240
  * Should be called from IStrategyCallbacks.onTick.
@@ -21266,7 +21391,7 @@ class BacktestMarkdownService {
21266
21391
  payload,
21267
21392
  });
21268
21393
  if (payload) {
21269
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21394
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21270
21395
  this.getStorage.clear(key);
21271
21396
  }
21272
21397
  else {
@@ -21328,7 +21453,7 @@ class BacktestMarkdownService {
21328
21453
  * @param backtest - Whether running in backtest mode
21329
21454
  * @returns Unique string key for memoization
21330
21455
  */
21331
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
21456
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21332
21457
  const parts = [symbol, strategyName, exchangeName];
21333
21458
  if (frameName)
21334
21459
  parts.push(frameName);
@@ -21823,7 +21948,7 @@ class LiveMarkdownService {
21823
21948
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21824
21949
  * Each combination gets its own isolated storage instance.
21825
21950
  */
21826
- this.getStorage = functoolsKit.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));
21951
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21827
21952
  /**
21828
21953
  * Subscribes to live signal emitter to receive tick events.
21829
21954
  * Protected against multiple subscriptions.
@@ -22041,7 +22166,7 @@ class LiveMarkdownService {
22041
22166
  payload,
22042
22167
  });
22043
22168
  if (payload) {
22044
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22169
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22045
22170
  this.getStorage.clear(key);
22046
22171
  }
22047
22172
  else {
@@ -22061,7 +22186,7 @@ class LiveMarkdownService {
22061
22186
  * @param backtest - Whether running in backtest mode
22062
22187
  * @returns Unique string key for memoization
22063
22188
  */
22064
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22189
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22065
22190
  const parts = [symbol, strategyName, exchangeName];
22066
22191
  if (frameName)
22067
22192
  parts.push(frameName);
@@ -22350,7 +22475,7 @@ class ScheduleMarkdownService {
22350
22475
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22351
22476
  * Each combination gets its own isolated storage instance.
22352
22477
  */
22353
- this.getStorage = functoolsKit.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));
22478
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22354
22479
  /**
22355
22480
  * Subscribes to signal emitter to receive scheduled signal events.
22356
22481
  * Protected against multiple subscriptions.
@@ -22553,7 +22678,7 @@ class ScheduleMarkdownService {
22553
22678
  payload,
22554
22679
  });
22555
22680
  if (payload) {
22556
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22681
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22557
22682
  this.getStorage.clear(key);
22558
22683
  }
22559
22684
  else {
@@ -22573,7 +22698,7 @@ class ScheduleMarkdownService {
22573
22698
  * @param backtest - Whether running in backtest mode
22574
22699
  * @returns Unique string key for memoization
22575
22700
  */
22576
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
22701
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22577
22702
  const parts = [symbol, strategyName, exchangeName];
22578
22703
  if (frameName)
22579
22704
  parts.push(frameName);
@@ -22818,7 +22943,7 @@ class PerformanceMarkdownService {
22818
22943
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22819
22944
  * Each combination gets its own isolated storage instance.
22820
22945
  */
22821
- this.getStorage = functoolsKit.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));
22946
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22822
22947
  /**
22823
22948
  * Subscribes to performance emitter to receive performance events.
22824
22949
  * Protected against multiple subscriptions.
@@ -22985,7 +23110,7 @@ class PerformanceMarkdownService {
22985
23110
  payload,
22986
23111
  });
22987
23112
  if (payload) {
22988
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23113
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22989
23114
  this.getStorage.clear(key);
22990
23115
  }
22991
23116
  else {
@@ -23464,7 +23589,7 @@ class WalkerMarkdownService {
23464
23589
  * @param backtest - Whether running in backtest mode
23465
23590
  * @returns Unique string key for memoization
23466
23591
  */
23467
- const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
23592
+ const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
23468
23593
  const parts = [exchangeName];
23469
23594
  if (frameName)
23470
23595
  parts.push(frameName);
@@ -23911,7 +24036,7 @@ class HeatMarkdownService {
23911
24036
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23912
24037
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23913
24038
  */
23914
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24039
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23915
24040
  /**
23916
24041
  * Subscribes to signal emitter to receive tick events.
23917
24042
  * Protected against multiple subscriptions.
@@ -24129,7 +24254,7 @@ class HeatMarkdownService {
24129
24254
  payload,
24130
24255
  });
24131
24256
  if (payload) {
24132
- const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
24257
+ const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24133
24258
  this.getStorage.clear(key);
24134
24259
  }
24135
24260
  else {
@@ -25160,7 +25285,7 @@ class ClientPartial {
25160
25285
  * @param backtest - Whether running in backtest mode
25161
25286
  * @returns Unique string key for memoization
25162
25287
  */
25163
- const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25288
+ const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25164
25289
  /**
25165
25290
  * Creates a callback function for emitting profit events to partialProfitSubject.
25166
25291
  *
@@ -25282,7 +25407,7 @@ class PartialConnectionService {
25282
25407
  * Key format: "signalId:backtest" or "signalId:live"
25283
25408
  * Value: ClientPartial instance with logger and event emitters
25284
25409
  */
25285
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
25410
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25286
25411
  return new ClientPartial({
25287
25412
  signalId,
25288
25413
  logger: this.loggerService,
@@ -25372,7 +25497,7 @@ class PartialConnectionService {
25372
25497
  const partial = this.getPartial(data.id, backtest);
25373
25498
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25374
25499
  await partial.clear(symbol, data, priceClose, backtest);
25375
- const key = CREATE_KEY_FN$h(data.id, backtest);
25500
+ const key = CREATE_KEY_FN$j(data.id, backtest);
25376
25501
  this.getPartial.clear(key);
25377
25502
  };
25378
25503
  }
@@ -25388,7 +25513,7 @@ class PartialConnectionService {
25388
25513
  * @param backtest - Whether running in backtest mode
25389
25514
  * @returns Unique string key for memoization
25390
25515
  */
25391
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
25516
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
25392
25517
  const parts = [symbol, strategyName, exchangeName];
25393
25518
  if (frameName)
25394
25519
  parts.push(frameName);
@@ -25611,7 +25736,7 @@ class PartialMarkdownService {
25611
25736
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25612
25737
  * Each combination gets its own isolated storage instance.
25613
25738
  */
25614
- this.getStorage = functoolsKit.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));
25739
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25615
25740
  /**
25616
25741
  * Subscribes to partial profit/loss signal emitters to receive events.
25617
25742
  * Protected against multiple subscriptions.
@@ -25821,7 +25946,7 @@ class PartialMarkdownService {
25821
25946
  payload,
25822
25947
  });
25823
25948
  if (payload) {
25824
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25949
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25825
25950
  this.getStorage.clear(key);
25826
25951
  }
25827
25952
  else {
@@ -25837,7 +25962,7 @@ class PartialMarkdownService {
25837
25962
  * @param context - Context with strategyName, exchangeName, frameName
25838
25963
  * @returns Unique string key for memoization
25839
25964
  */
25840
- const CREATE_KEY_FN$f = (context) => {
25965
+ const CREATE_KEY_FN$h = (context) => {
25841
25966
  const parts = [context.strategyName, context.exchangeName];
25842
25967
  if (context.frameName)
25843
25968
  parts.push(context.frameName);
@@ -25911,7 +26036,7 @@ class PartialGlobalService {
25911
26036
  * @param context - Context with strategyName, exchangeName and frameName
25912
26037
  * @param methodName - Name of the calling method for error tracking
25913
26038
  */
25914
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
26039
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
25915
26040
  this.loggerService.log("partialGlobalService validate", {
25916
26041
  context,
25917
26042
  methodName,
@@ -26366,7 +26491,7 @@ class ClientBreakeven {
26366
26491
  * @param backtest - Whether running in backtest mode
26367
26492
  * @returns Unique string key for memoization
26368
26493
  */
26369
- const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26494
+ const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26370
26495
  /**
26371
26496
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26372
26497
  *
@@ -26452,7 +26577,7 @@ class BreakevenConnectionService {
26452
26577
  * Key format: "signalId:backtest" or "signalId:live"
26453
26578
  * Value: ClientBreakeven instance with logger and event emitter
26454
26579
  */
26455
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
26580
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
26456
26581
  return new ClientBreakeven({
26457
26582
  signalId,
26458
26583
  logger: this.loggerService,
@@ -26513,7 +26638,7 @@ class BreakevenConnectionService {
26513
26638
  const breakeven = this.getBreakeven(data.id, backtest);
26514
26639
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26515
26640
  await breakeven.clear(symbol, data, priceClose, backtest);
26516
- const key = CREATE_KEY_FN$e(data.id, backtest);
26641
+ const key = CREATE_KEY_FN$g(data.id, backtest);
26517
26642
  this.getBreakeven.clear(key);
26518
26643
  };
26519
26644
  }
@@ -26529,7 +26654,7 @@ class BreakevenConnectionService {
26529
26654
  * @param backtest - Whether running in backtest mode
26530
26655
  * @returns Unique string key for memoization
26531
26656
  */
26532
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
26657
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
26533
26658
  const parts = [symbol, strategyName, exchangeName];
26534
26659
  if (frameName)
26535
26660
  parts.push(frameName);
@@ -26704,7 +26829,7 @@ class BreakevenMarkdownService {
26704
26829
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26705
26830
  * Each combination gets its own isolated storage instance.
26706
26831
  */
26707
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26832
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26708
26833
  /**
26709
26834
  * Subscribes to breakeven signal emitter to receive events.
26710
26835
  * Protected against multiple subscriptions.
@@ -26893,7 +27018,7 @@ class BreakevenMarkdownService {
26893
27018
  payload,
26894
27019
  });
26895
27020
  if (payload) {
26896
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27021
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26897
27022
  this.getStorage.clear(key);
26898
27023
  }
26899
27024
  else {
@@ -26909,7 +27034,7 @@ class BreakevenMarkdownService {
26909
27034
  * @param context - Context with strategyName, exchangeName, frameName
26910
27035
  * @returns Unique string key for memoization
26911
27036
  */
26912
- const CREATE_KEY_FN$c = (context) => {
27037
+ const CREATE_KEY_FN$e = (context) => {
26913
27038
  const parts = [context.strategyName, context.exchangeName];
26914
27039
  if (context.frameName)
26915
27040
  parts.push(context.frameName);
@@ -26983,7 +27108,7 @@ class BreakevenGlobalService {
26983
27108
  * @param context - Context with strategyName, exchangeName and frameName
26984
27109
  * @param methodName - Name of the calling method for error tracking
26985
27110
  */
26986
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
27111
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
26987
27112
  this.loggerService.log("breakevenGlobalService validate", {
26988
27113
  context,
26989
27114
  methodName,
@@ -27204,7 +27329,7 @@ class ConfigValidationService {
27204
27329
  * @param backtest - Whether running in backtest mode
27205
27330
  * @returns Unique string key for memoization
27206
27331
  */
27207
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
27332
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27208
27333
  const parts = [symbol, strategyName, exchangeName];
27209
27334
  if (frameName)
27210
27335
  parts.push(frameName);
@@ -27371,7 +27496,7 @@ class RiskMarkdownService {
27371
27496
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27372
27497
  * Each combination gets its own isolated storage instance.
27373
27498
  */
27374
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27499
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27375
27500
  /**
27376
27501
  * Subscribes to risk rejection emitter to receive rejection events.
27377
27502
  * Protected against multiple subscriptions.
@@ -27560,7 +27685,7 @@ class RiskMarkdownService {
27560
27685
  payload,
27561
27686
  });
27562
27687
  if (payload) {
27563
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27688
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27564
27689
  this.getStorage.clear(key);
27565
27690
  }
27566
27691
  else {
@@ -29942,7 +30067,7 @@ class HighestProfitReportService {
29942
30067
  * @returns Colon-separated key string for memoization
29943
30068
  * @internal
29944
30069
  */
29945
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30070
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
29946
30071
  const parts = [symbol, strategyName, exchangeName];
29947
30072
  if (frameName)
29948
30073
  parts.push(frameName);
@@ -30184,7 +30309,7 @@ class StrategyMarkdownService {
30184
30309
  *
30185
30310
  * @internal
30186
30311
  */
30187
- this.getStorage = functoolsKit.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));
30312
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30188
30313
  /**
30189
30314
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30190
30315
  *
@@ -30758,7 +30883,7 @@ class StrategyMarkdownService {
30758
30883
  this.clear = async (payload) => {
30759
30884
  this.loggerService.log("strategyMarkdownService clear", { payload });
30760
30885
  if (payload) {
30761
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30886
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30762
30887
  this.getStorage.clear(key);
30763
30888
  }
30764
30889
  else {
@@ -30866,7 +30991,7 @@ class StrategyMarkdownService {
30866
30991
  * Creates a unique key for memoizing ReportStorage instances.
30867
30992
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30868
30993
  */
30869
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30994
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
30870
30995
  const parts = [symbol, strategyName, exchangeName];
30871
30996
  if (frameName)
30872
30997
  parts.push(frameName);
@@ -31059,7 +31184,7 @@ let ReportStorage$2 = class ReportStorage {
31059
31184
  class SyncMarkdownService {
31060
31185
  constructor() {
31061
31186
  this.loggerService = inject(TYPES.loggerService);
31062
- this.getStorage = functoolsKit.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));
31187
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31063
31188
  /**
31064
31189
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31065
31190
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31255,7 +31380,7 @@ class SyncMarkdownService {
31255
31380
  this.clear = async (payload) => {
31256
31381
  this.loggerService.log("syncMarkdownService clear", { payload });
31257
31382
  if (payload) {
31258
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31383
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31259
31384
  this.getStorage.clear(key);
31260
31385
  }
31261
31386
  else {
@@ -31268,7 +31393,7 @@ class SyncMarkdownService {
31268
31393
  /**
31269
31394
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31270
31395
  */
31271
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31396
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
31272
31397
  const parts = [symbol, strategyName, exchangeName];
31273
31398
  if (frameName)
31274
31399
  parts.push(frameName);
@@ -31444,7 +31569,7 @@ let ReportStorage$1 = class ReportStorage {
31444
31569
  class HighestProfitMarkdownService {
31445
31570
  constructor() {
31446
31571
  this.loggerService = inject(TYPES.loggerService);
31447
- this.getStorage = functoolsKit.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));
31572
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31448
31573
  /**
31449
31574
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31450
31575
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31610,7 +31735,7 @@ class HighestProfitMarkdownService {
31610
31735
  this.clear = async (payload) => {
31611
31736
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31612
31737
  if (payload) {
31613
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31738
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31614
31739
  this.getStorage.clear(key);
31615
31740
  }
31616
31741
  else {
@@ -31632,7 +31757,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31632
31757
  * @param backtest - Whether running in backtest mode
31633
31758
  * @returns Unique string key for memoization
31634
31759
  */
31635
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31760
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31636
31761
  const parts = [symbol, strategyName, exchangeName];
31637
31762
  if (frameName)
31638
31763
  parts.push(frameName);
@@ -31675,7 +31800,7 @@ class PriceMetaService {
31675
31800
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31676
31801
  * Instances are cached until clear() is called.
31677
31802
  */
31678
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31803
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31679
31804
  /**
31680
31805
  * Returns the current market price for the given symbol and context.
31681
31806
  *
@@ -31704,10 +31829,10 @@ class PriceMetaService {
31704
31829
  if (source.data) {
31705
31830
  return source.data;
31706
31831
  }
31707
- 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...`);
31832
+ 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...`);
31708
31833
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31709
31834
  if (typeof currentPrice === "symbol") {
31710
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31835
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31711
31836
  }
31712
31837
  return currentPrice;
31713
31838
  };
@@ -31749,7 +31874,7 @@ class PriceMetaService {
31749
31874
  this.getSource.clear();
31750
31875
  return;
31751
31876
  }
31752
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31877
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31753
31878
  this.getSource.clear(key);
31754
31879
  };
31755
31880
  }
@@ -31767,7 +31892,7 @@ const LISTEN_TIMEOUT = 120000;
31767
31892
  * @param backtest - Whether running in backtest mode
31768
31893
  * @returns Unique string key for memoization
31769
31894
  */
31770
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31895
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31771
31896
  const parts = [symbol, strategyName, exchangeName];
31772
31897
  if (frameName)
31773
31898
  parts.push(frameName);
@@ -31810,7 +31935,7 @@ class TimeMetaService {
31810
31935
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31811
31936
  * Instances are cached until clear() is called.
31812
31937
  */
31813
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31938
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31814
31939
  /**
31815
31940
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31816
31941
  *
@@ -31838,10 +31963,10 @@ class TimeMetaService {
31838
31963
  if (source.data) {
31839
31964
  return source.data;
31840
31965
  }
31841
- 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...`);
31966
+ 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...`);
31842
31967
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31843
31968
  if (typeof timestamp === "symbol") {
31844
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31969
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31845
31970
  }
31846
31971
  return timestamp;
31847
31972
  };
@@ -31883,7 +32008,7 @@ class TimeMetaService {
31883
32008
  this.getSource.clear();
31884
32009
  return;
31885
32010
  }
31886
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32011
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31887
32012
  this.getSource.clear(key);
31888
32013
  };
31889
32014
  }
@@ -31979,7 +32104,7 @@ class MaxDrawdownReportService {
31979
32104
  /**
31980
32105
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31981
32106
  */
31982
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32107
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31983
32108
  const parts = [symbol, strategyName, exchangeName];
31984
32109
  if (frameName)
31985
32110
  parts.push(frameName);
@@ -32103,7 +32228,7 @@ class ReportStorage {
32103
32228
  class MaxDrawdownMarkdownService {
32104
32229
  constructor() {
32105
32230
  this.loggerService = inject(TYPES.loggerService);
32106
- this.getStorage = functoolsKit.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));
32231
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32107
32232
  /**
32108
32233
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32109
32234
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32182,7 +32307,7 @@ class MaxDrawdownMarkdownService {
32182
32307
  this.clear = async (payload) => {
32183
32308
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32184
32309
  if (payload) {
32185
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32310
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32186
32311
  this.getStorage.clear(key);
32187
32312
  }
32188
32313
  else {
@@ -32192,6 +32317,125 @@ class MaxDrawdownMarkdownService {
32192
32317
  }
32193
32318
  }
32194
32319
 
32320
+ const METHOD_NAME_COMMIT_SIGNAL_NOTIFY = "notificationHelperService.commitSignalNotify";
32321
+ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32322
+ /**
32323
+ * Creates a unique key for memoizing validate calls.
32324
+ * Key format: "strategyName:exchangeName:frameName"
32325
+ * @param context - Execution context with strategyName, exchangeName, frameName
32326
+ * @returns Unique string key for memoization
32327
+ */
32328
+ const CREATE_KEY_FN$6 = (context) => {
32329
+ const parts = [context.strategyName, context.exchangeName];
32330
+ if (context.frameName)
32331
+ parts.push(context.frameName);
32332
+ return parts.join(":");
32333
+ };
32334
+ /**
32335
+ * Helper service for emitting signal info notifications.
32336
+ *
32337
+ * Handles validation (memoized per context) and emission of `signal.info` events
32338
+ * via `signalNotifySubject`. Used internally by the framework action pipeline —
32339
+ * end users interact with this via `commitSignalNotify()` in `onActivePing` callbacks.
32340
+ */
32341
+ class NotificationHelperService {
32342
+ constructor() {
32343
+ this.loggerService = inject(TYPES.loggerService);
32344
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
32345
+ this.riskValidationService = inject(TYPES.riskValidationService);
32346
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
32347
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
32348
+ this.frameValidationService = inject(TYPES.frameValidationService);
32349
+ this.actionValidationService = inject(TYPES.actionValidationService);
32350
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
32351
+ this.timeMetaService = inject(TYPES.timeMetaService);
32352
+ /**
32353
+ * Validates strategy, exchange, frame, risk, and action schemas for the given context.
32354
+ *
32355
+ * Memoized per unique `"strategyName:exchangeName[:frameName]"` key — subsequent calls
32356
+ * with the same context are no-ops, so validation runs at most once per context.
32357
+ *
32358
+ * @param context - Routing context: strategyName, exchangeName, frameName
32359
+ * @throws {Error} If any registered schema fails validation
32360
+ */
32361
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
32362
+ this.loggerService.log(METHOD_NAME_VALIDATE, {
32363
+ context,
32364
+ });
32365
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
32366
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
32367
+ const { riskName, riskList, actions } = this.strategySchemaService.get(context.strategyName);
32368
+ context.frameName &&
32369
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
32370
+ riskName &&
32371
+ this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
32372
+ riskList &&
32373
+ riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
32374
+ actions &&
32375
+ actions.forEach((actionName) => this.actionValidationService.validate(actionName, METHOD_NAME_VALIDATE));
32376
+ });
32377
+ /**
32378
+ * Emits a `signal.info` notification for the currently active pending signal.
32379
+ *
32380
+ * Validates all schemas (via memoized `validate`), resolves the pending signal
32381
+ * for the given symbol, then emits a `SignalInfoContract` via `signalNotifySubject`,
32382
+ * which is routed to all registered `listenSignalNotify` callbacks and persisted
32383
+ * by `NotificationAdapter`.
32384
+ *
32385
+ * @param payload - Optional notification fields (notificationId, notificationNote)
32386
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
32387
+ * @param currentPrice - Market price at the time of the call
32388
+ * @param context - Routing context: strategyName, exchangeName, frameName
32389
+ * @param backtest - true when called during a backtest run
32390
+ *
32391
+ * @throws {Error} If no active pending signal is found for the given symbol
32392
+ *
32393
+ * @example
32394
+ * ```typescript
32395
+ * // Inside onActivePing callback:
32396
+ * await commitSignalNotify("BTCUSDT", {
32397
+ * notificationNote: "RSI crossed 70, consider closing",
32398
+ * notificationId: "msg-123",
32399
+ * });
32400
+ * ```
32401
+ */
32402
+ this.commitSignalNotify = async (payload, symbol, currentPrice, context, backtest) => {
32403
+ this.loggerService.info(METHOD_NAME_COMMIT_SIGNAL_NOTIFY, {
32404
+ symbol,
32405
+ context,
32406
+ backtest,
32407
+ currentPrice,
32408
+ });
32409
+ this.validate(context);
32410
+ const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32411
+ strategyName: context.strategyName,
32412
+ exchangeName: context.exchangeName,
32413
+ frameName: "",
32414
+ });
32415
+ if (!pendingSignal) {
32416
+ throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
32417
+ }
32418
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, {
32419
+ strategyName: context.strategyName,
32420
+ exchangeName: context.exchangeName,
32421
+ frameName: context.frameName,
32422
+ }, backtest);
32423
+ await signalNotifySubject.next({
32424
+ backtest,
32425
+ symbol,
32426
+ currentPrice,
32427
+ data: pendingSignal,
32428
+ exchangeName: context.exchangeName,
32429
+ strategyName: context.strategyName,
32430
+ frameName: context.frameName,
32431
+ note: payload.notificationNote || pendingSignal.note,
32432
+ notificationId: payload.notificationId,
32433
+ timestamp,
32434
+ });
32435
+ };
32436
+ }
32437
+ }
32438
+
32195
32439
  {
32196
32440
  provide(TYPES.loggerService, () => new LoggerService());
32197
32441
  }
@@ -32280,6 +32524,9 @@ class MaxDrawdownMarkdownService {
32280
32524
  provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
32281
32525
  provide(TYPES.maxDrawdownReportService, () => new MaxDrawdownReportService());
32282
32526
  }
32527
+ {
32528
+ provide(TYPES.notificationHelperService, () => new NotificationHelperService());
32529
+ }
32283
32530
  {
32284
32531
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
32285
32532
  provide(TYPES.strategyValidationService, () => new StrategyValidationService());
@@ -32380,6 +32627,9 @@ const reportServices = {
32380
32627
  highestProfitReportService: inject(TYPES.highestProfitReportService),
32381
32628
  maxDrawdownReportService: inject(TYPES.maxDrawdownReportService),
32382
32629
  };
32630
+ const helperServices = {
32631
+ notificationHelperService: inject(TYPES.notificationHelperService),
32632
+ };
32383
32633
  const validationServices = {
32384
32634
  exchangeValidationService: inject(TYPES.exchangeValidationService),
32385
32635
  strategyValidationService: inject(TYPES.strategyValidationService),
@@ -32405,6 +32655,7 @@ const backtest = {
32405
32655
  ...markdownServices,
32406
32656
  ...reportServices,
32407
32657
  ...validationServices,
32658
+ ...helperServices,
32408
32659
  };
32409
32660
  init();
32410
32661
 
@@ -35321,6 +35572,7 @@ const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap
35321
35572
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35322
35573
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
35323
35574
  const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35575
+ const COMMIT_SIGNAL_NOTIFY_METHOD_NAME = "strategy.commitSignalNotify";
35324
35576
  /**
35325
35577
  * Cancels the scheduled signal without stopping the strategy.
35326
35578
  *
@@ -37285,6 +37537,50 @@ async function hasNoScheduledSignal(symbol) {
37285
37537
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37286
37538
  return await functoolsKit.not(backtest.strategyCoreService.hasScheduledSignal(isBacktest, symbol, { exchangeName, frameName, strategyName }));
37287
37539
  }
37540
+ /**
37541
+ * Emits a `signal.info` notification for the currently active pending signal.
37542
+ *
37543
+ * Broadcasts a user-defined informational note without affecting position state.
37544
+ * Useful for annotating strategy decisions, triggering external alerts, or logging
37545
+ * mid-position events (e.g. RSI crossing a threshold, volume spike detected).
37546
+ *
37547
+ * Automatically reads backtest/live mode from execution context.
37548
+ * Automatically reads strategyName, exchangeName, frameName from method context.
37549
+ * Automatically fetches current price via getAveragePrice.
37550
+ *
37551
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
37552
+ * @param payload - Optional notification fields
37553
+ * @param payload.notificationNote - Human-readable note. Falls back to signal.note if omitted.
37554
+ * @param payload.notificationId - Optional correlation ID for external systems (e.g. Telegram message ID).
37555
+ *
37556
+ * @throws {Error} If called outside an execution context
37557
+ * @throws {Error} If called outside a method context
37558
+ * @throws {Error} If no active pending signal exists for the given symbol
37559
+ *
37560
+ * @example
37561
+ * ```typescript
37562
+ * import { commitSignalNotify } from "backtest-kit";
37563
+ *
37564
+ * // Inside onActivePing callback:
37565
+ * await commitSignalNotify("BTCUSDT", {
37566
+ * notificationNote: "RSI crossed 70, consider closing",
37567
+ * notificationId: "msg-123",
37568
+ * });
37569
+ * ```
37570
+ */
37571
+ async function commitSignalNotify(symbol, payload = {}) {
37572
+ backtest.loggerService.info(COMMIT_SIGNAL_NOTIFY_METHOD_NAME, { symbol, payload });
37573
+ if (!ExecutionContextService.hasContext()) {
37574
+ throw new Error("commitSignalNotify requires an execution context");
37575
+ }
37576
+ if (!MethodContextService.hasContext()) {
37577
+ throw new Error("commitSignalNotify requires a method context");
37578
+ }
37579
+ const currentPrice = await getAveragePrice(symbol);
37580
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37581
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37582
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, { strategyName, exchangeName, frameName }, isBacktest);
37583
+ }
37288
37584
 
37289
37585
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
37290
37586
  /**
@@ -37368,6 +37664,8 @@ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
37368
37664
  const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
37369
37665
  const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
37370
37666
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
37667
+ const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
37668
+ const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
37371
37669
  /**
37372
37670
  * Subscribes to all signal events with queued async processing.
37373
37671
  *
@@ -38759,6 +39057,46 @@ function listenMaxDrawdownOnce(filterFn, fn) {
38759
39057
  };
38760
39058
  return disposeFn = listenMaxDrawdown(wrappedFn);
38761
39059
  }
39060
+ /**
39061
+ * Subscribes to signal info events with queued async processing.
39062
+ * Emits when a strategy calls commitSignalInfo() to broadcast a user-defined note for an open position.
39063
+ * Events are processed sequentially in order received, even if callback is async.
39064
+ * Uses queued wrapper to prevent concurrent execution of the callback.
39065
+ * @param fn - Callback function to handle signal info events
39066
+ * @return Unsubscribe function to stop listening to events
39067
+ */
39068
+ function listenSignalNotify(fn) {
39069
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_METHOD_NAME);
39070
+ const wrappedFn = async (event) => {
39071
+ if (await backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39072
+ strategyName: event.strategyName,
39073
+ exchangeName: event.exchangeName,
39074
+ frameName: event.frameName,
39075
+ })) {
39076
+ await fn(event);
39077
+ }
39078
+ };
39079
+ return signalNotifySubject.subscribe(functoolsKit.queued(wrappedFn));
39080
+ }
39081
+ /**
39082
+ * Subscribes to filtered signal info events with one-time execution.
39083
+ * Listens for events matching the filter predicate, then executes callback once
39084
+ * and automatically unsubscribes.
39085
+ * @param filterFn - Predicate to filter which events trigger the callback
39086
+ * @param fn - Callback function to handle the filtered event (called only once)
39087
+ * @return Unsubscribe function to cancel the listener before it fires
39088
+ */
39089
+ function listenSignalNotifyOnce(filterFn, fn) {
39090
+ backtest.loggerService.log(LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME);
39091
+ let disposeFn;
39092
+ const wrappedFn = async (event) => {
39093
+ if (filterFn(event)) {
39094
+ await fn(event);
39095
+ disposeFn && disposeFn();
39096
+ }
39097
+ };
39098
+ return disposeFn = listenSignalNotify(wrappedFn);
39099
+ }
38762
39100
 
38763
39101
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
38764
39102
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -38815,6 +39153,7 @@ const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingSto
38815
39153
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
38816
39154
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
38817
39155
  const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
39156
+ const BACKTEST_METHOD_NAME_SIGNAL_NOTIFY = "Backtest.commitSignalNotify";
38818
39157
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
38819
39158
  const BACKTEST_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "BacktestUtils.hasNoPendingSignal";
38820
39159
  const BACKTEST_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "BacktestUtils.hasNoScheduledSignal";
@@ -41170,6 +41509,33 @@ class BacktestUtils {
41170
41509
  });
41171
41510
  return await backtest.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
41172
41511
  };
41512
+ /**
41513
+ * Emits a `signal.info` notification for the currently active pending signal.
41514
+ *
41515
+ * @param symbol - Trading pair symbol
41516
+ * @param currentPrice - Market price at the time of the call
41517
+ * @param context - Execution context with strategyName, exchangeName, frameName
41518
+ * @param payload - Optional notification fields (notificationNote, notificationId)
41519
+ *
41520
+ * @throws {Error} If no active pending signal exists for the given symbol
41521
+ *
41522
+ * @example
41523
+ * ```typescript
41524
+ * await Backtest.commitSignalNotify("BTCUSDT", 42000, {
41525
+ * strategyName: "my-strategy",
41526
+ * exchangeName: "binance",
41527
+ * frameName: "1h"
41528
+ * }, { notificationNote: "RSI crossed 70" });
41529
+ * ```
41530
+ */
41531
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
41532
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_SIGNAL_NOTIFY, {
41533
+ symbol,
41534
+ currentPrice,
41535
+ context,
41536
+ });
41537
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, context, true);
41538
+ };
41173
41539
  /**
41174
41540
  * Gets statistical data from all closed signals for a symbol-strategy pair.
41175
41541
  *
@@ -41383,6 +41749,7 @@ const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
41383
41749
  const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
41384
41750
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
41385
41751
  const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
41752
+ const LIVE_METHOD_NAME_SIGNAL_NOTIFY = "Live.commitSignalNotify";
41386
41753
  const LIVE_METHOD_NAME_HAS_NO_PENDING_SIGNAL = "LiveUtils.hasNoPendingSignal";
41387
41754
  const LIVE_METHOD_NAME_HAS_NO_SCHEDULED_SIGNAL = "LiveUtils.hasNoScheduledSignal";
41388
41755
  /**
@@ -44079,6 +44446,36 @@ class LiveUtils {
44079
44446
  frameName: "",
44080
44447
  }, cost);
44081
44448
  };
44449
+ /**
44450
+ * Emits a `signal.info` notification for the currently active pending signal.
44451
+ *
44452
+ * @param symbol - Trading pair symbol
44453
+ * @param currentPrice - Market price at the time of the call
44454
+ * @param context - Execution context with strategyName and exchangeName
44455
+ * @param payload - Optional notification fields (notificationNote, notificationId)
44456
+ *
44457
+ * @throws {Error} If no active pending signal exists for the given symbol
44458
+ *
44459
+ * @example
44460
+ * ```typescript
44461
+ * await Live.commitSignalNotify("BTCUSDT", 42000, {
44462
+ * strategyName: "my-strategy",
44463
+ * exchangeName: "binance",
44464
+ * }, { notificationNote: "RSI crossed 70" });
44465
+ * ```
44466
+ */
44467
+ this.commitSignalNotify = async (symbol, currentPrice, context, payload = {}) => {
44468
+ backtest.loggerService.info(LIVE_METHOD_NAME_SIGNAL_NOTIFY, {
44469
+ symbol,
44470
+ currentPrice,
44471
+ context,
44472
+ });
44473
+ await backtest.notificationHelperService.commitSignalNotify(payload, symbol, currentPrice, {
44474
+ strategyName: context.strategyName,
44475
+ exchangeName: context.exchangeName,
44476
+ frameName: "",
44477
+ }, false);
44478
+ };
44082
44479
  /**
44083
44480
  * Gets statistical data from all live trading events for a symbol-strategy pair.
44084
44481
  *
@@ -45248,6 +45645,503 @@ async function listRiskSchema() {
45248
45645
  return await backtest.riskValidationService.list();
45249
45646
  }
45250
45647
 
45648
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
45649
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
45650
+ const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
45651
+ const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
45652
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
45653
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
45654
+ const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
45655
+ const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
45656
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
45657
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
45658
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
45659
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
45660
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
45661
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
45662
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
45663
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
45664
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
45665
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
45666
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
45667
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45668
+ const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45669
+ const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45670
+ const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
45671
+ /**
45672
+ * Builds a composite storage key from context parts.
45673
+ * Includes backtest flag as the last segment to prevent live/backtest collisions.
45674
+ * @param symbol - Trading pair symbol
45675
+ * @param strategyName - Strategy identifier
45676
+ * @param exchangeName - Exchange identifier
45677
+ * @param frameName - Frame identifier
45678
+ * @param backtest - Flag indicating if the context is backtest or live
45679
+ * @returns Composite key string
45680
+ */
45681
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
45682
+ const parts = [symbol, strategyName, exchangeName];
45683
+ if (frameName)
45684
+ parts.push(frameName);
45685
+ parts.push(backtest ? "backtest" : "live");
45686
+ return parts.join(":");
45687
+ };
45688
+ /**
45689
+ * Persistent storage adapter for backtest recent signals.
45690
+ *
45691
+ * Features:
45692
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45693
+ * - Handles active ping events only
45694
+ *
45695
+ * Use this adapter for backtest recent signal persistence across sessions.
45696
+ */
45697
+ class RecentPersistBacktestUtils {
45698
+ constructor() {
45699
+ /**
45700
+ * Handles active ping event.
45701
+ * Persists the latest signal to disk via PersistRecentAdapter.
45702
+ * @param event - Active ping contract with signal data and backtest flag
45703
+ */
45704
+ this.handleActivePing = async (event) => {
45705
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45706
+ signalId: event.data.id,
45707
+ });
45708
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45709
+ };
45710
+ /**
45711
+ * Retrieves the latest persisted signal for the given context.
45712
+ * @param symbol - Trading pair symbol
45713
+ * @param strategyName - Strategy identifier
45714
+ * @param exchangeName - Exchange identifier
45715
+ * @param frameName - Frame identifier
45716
+ * @param backtest - Flag indicating if the context is backtest or live
45717
+ * @returns The latest signal or null if not found
45718
+ */
45719
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45720
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
45721
+ symbol,
45722
+ strategyName,
45723
+ exchangeName,
45724
+ frameName,
45725
+ backtest: backtest$1,
45726
+ });
45727
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45728
+ };
45729
+ }
45730
+ }
45731
+ /**
45732
+ * In-memory storage adapter for backtest recent signals.
45733
+ *
45734
+ * Features:
45735
+ * - Stores the latest active signal per context key in memory only
45736
+ * - Fast read/write operations
45737
+ * - Data is lost when application restarts
45738
+ *
45739
+ * Use this adapter for testing or when persistence is not required.
45740
+ */
45741
+ class RecentMemoryBacktestUtils {
45742
+ constructor() {
45743
+ /** Map of composite context keys to the latest signal */
45744
+ this._signals = new Map();
45745
+ /**
45746
+ * Handles active ping event.
45747
+ * Stores the latest signal in memory under the composite context key.
45748
+ * @param event - Active ping contract with signal data and backtest flag
45749
+ */
45750
+ this.handleActivePing = async (event) => {
45751
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45752
+ signalId: event.data.id,
45753
+ });
45754
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45755
+ this._signals.set(key, event.data);
45756
+ };
45757
+ /**
45758
+ * Retrieves the latest in-memory signal for the given context.
45759
+ * @param symbol - Trading pair symbol
45760
+ * @param strategyName - Strategy identifier
45761
+ * @param exchangeName - Exchange identifier
45762
+ * @param frameName - Frame identifier
45763
+ * @param backtest - Flag indicating if the context is backtest or live
45764
+ * @returns The latest signal or null if not found
45765
+ */
45766
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45767
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45768
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45769
+ return this._signals.get(key) ?? null;
45770
+ };
45771
+ }
45772
+ }
45773
+ /**
45774
+ * Persistent storage adapter for live recent signals.
45775
+ *
45776
+ * Features:
45777
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45778
+ * - Handles active ping events only
45779
+ *
45780
+ * Use this adapter (default) for live recent signal persistence across sessions.
45781
+ */
45782
+ class RecentPersistLiveUtils {
45783
+ constructor() {
45784
+ /**
45785
+ * Handles active ping event.
45786
+ * Persists the latest signal to disk via PersistRecentAdapter.
45787
+ * @param event - Active ping contract with signal data and backtest flag
45788
+ */
45789
+ this.handleActivePing = async (event) => {
45790
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45791
+ signalId: event.data.id,
45792
+ });
45793
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45794
+ };
45795
+ /**
45796
+ * Retrieves the latest persisted signal for the given context.
45797
+ * @param symbol - Trading pair symbol
45798
+ * @param strategyName - Strategy identifier
45799
+ * @param exchangeName - Exchange identifier
45800
+ * @param frameName - Frame identifier
45801
+ * @param backtest - Flag indicating if the context is backtest or live
45802
+ * @returns The latest signal or null if not found
45803
+ */
45804
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45805
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
45806
+ symbol,
45807
+ strategyName,
45808
+ exchangeName,
45809
+ frameName,
45810
+ backtest: backtest$1,
45811
+ });
45812
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45813
+ };
45814
+ }
45815
+ }
45816
+ /**
45817
+ * In-memory storage adapter for live recent signals.
45818
+ *
45819
+ * Features:
45820
+ * - Stores the latest active signal per context key in memory only
45821
+ * - Fast read/write operations
45822
+ * - Data is lost when application restarts
45823
+ *
45824
+ * Use this adapter for testing or when persistence is not required.
45825
+ */
45826
+ class RecentMemoryLiveUtils {
45827
+ constructor() {
45828
+ /** Map of composite context keys to the latest signal */
45829
+ this._signals = new Map();
45830
+ /**
45831
+ * Handles active ping event.
45832
+ * Stores the latest signal in memory under the composite context key.
45833
+ * @param event - Active ping contract with signal data and backtest flag
45834
+ */
45835
+ this.handleActivePing = async (event) => {
45836
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45837
+ signalId: event.data.id,
45838
+ });
45839
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45840
+ this._signals.set(key, event.data);
45841
+ };
45842
+ /**
45843
+ * Retrieves the latest in-memory signal for the given context.
45844
+ * @param symbol - Trading pair symbol
45845
+ * @param strategyName - Strategy identifier
45846
+ * @param exchangeName - Exchange identifier
45847
+ * @param frameName - Frame identifier
45848
+ * @param backtest - Flag indicating if the context is backtest or live
45849
+ * @returns The latest signal or null if not found
45850
+ */
45851
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45852
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45853
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45854
+ return this._signals.get(key) ?? null;
45855
+ };
45856
+ }
45857
+ }
45858
+ /**
45859
+ * Backtest recent signal adapter with pluggable storage backend.
45860
+ *
45861
+ * Features:
45862
+ * - Adapter pattern for swappable storage implementations
45863
+ * - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
45864
+ * - Alternative adapter: RecentPersistBacktestUtils
45865
+ * - Convenience methods: usePersist(), useMemory()
45866
+ */
45867
+ class RecentBacktestAdapter {
45868
+ constructor() {
45869
+ /** Internal storage utils instance */
45870
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45871
+ /**
45872
+ * Handles active ping event.
45873
+ * Proxies call to the underlying storage adapter.
45874
+ * @param event - Active ping contract with signal data
45875
+ */
45876
+ this.handleActivePing = async (event) => {
45877
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45878
+ signalId: event.data.id,
45879
+ });
45880
+ return await this._recentBacktestUtils.handleActivePing(event);
45881
+ };
45882
+ /**
45883
+ * Retrieves the latest signal for the given context.
45884
+ * Proxies call to the underlying storage adapter.
45885
+ * @param symbol - Trading pair symbol
45886
+ * @param strategyName - Strategy identifier
45887
+ * @param exchangeName - Exchange identifier
45888
+ * @param frameName - Frame identifier
45889
+ * @param backtest - Flag indicating if the context is backtest or live
45890
+ * @returns The latest signal or null if not found
45891
+ */
45892
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45893
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45894
+ symbol,
45895
+ strategyName,
45896
+ exchangeName,
45897
+ frameName,
45898
+ backtest: backtest$1,
45899
+ });
45900
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45901
+ };
45902
+ /**
45903
+ * Sets the storage adapter constructor.
45904
+ * All future storage operations will use this adapter.
45905
+ * @param Ctor - Constructor for recent adapter
45906
+ */
45907
+ this.useRecentAdapter = (Ctor) => {
45908
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
45909
+ this._recentBacktestUtils = Reflect.construct(Ctor, []);
45910
+ };
45911
+ /**
45912
+ * Switches to persistent storage adapter.
45913
+ * Signals will be persisted to disk.
45914
+ */
45915
+ this.usePersist = () => {
45916
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
45917
+ this._recentBacktestUtils = new RecentPersistBacktestUtils();
45918
+ };
45919
+ /**
45920
+ * Switches to in-memory storage adapter (default).
45921
+ * Signals will be stored in memory only.
45922
+ */
45923
+ this.useMemory = () => {
45924
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
45925
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45926
+ };
45927
+ /**
45928
+ * Clears the cached utils instance by resetting to the default in-memory adapter.
45929
+ */
45930
+ this.clear = () => {
45931
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
45932
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45933
+ };
45934
+ }
45935
+ }
45936
+ /**
45937
+ * Live recent signal adapter with pluggable storage backend.
45938
+ *
45939
+ * Features:
45940
+ * - Adapter pattern for swappable storage implementations
45941
+ * - Default adapter: RecentPersistLiveUtils (persistent storage)
45942
+ * - Alternative adapter: RecentMemoryLiveUtils
45943
+ * - Convenience methods: usePersist(), useMemory()
45944
+ */
45945
+ class RecentLiveAdapter {
45946
+ constructor() {
45947
+ /** Internal storage utils instance */
45948
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45949
+ /**
45950
+ * Handles active ping event.
45951
+ * Proxies call to the underlying storage adapter.
45952
+ * @param event - Active ping contract with signal data
45953
+ */
45954
+ this.handleActivePing = async (event) => {
45955
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45956
+ signalId: event.data.id,
45957
+ });
45958
+ return await this._recentLiveUtils.handleActivePing(event);
45959
+ };
45960
+ /**
45961
+ * Retrieves the latest signal for the given context.
45962
+ * Proxies call to the underlying storage adapter.
45963
+ * @param symbol - Trading pair symbol
45964
+ * @param strategyName - Strategy identifier
45965
+ * @param exchangeName - Exchange identifier
45966
+ * @param frameName - Frame identifier
45967
+ * @param backtest - Flag indicating if the context is backtest or live
45968
+ * @returns The latest signal or null if not found
45969
+ */
45970
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45971
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45972
+ symbol,
45973
+ strategyName,
45974
+ exchangeName,
45975
+ frameName,
45976
+ backtest: backtest$1,
45977
+ });
45978
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45979
+ };
45980
+ /**
45981
+ * Sets the storage adapter constructor.
45982
+ * All future storage operations will use this adapter.
45983
+ * @param Ctor - Constructor for recent adapter
45984
+ */
45985
+ this.useRecentAdapter = (Ctor) => {
45986
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
45987
+ this._recentLiveUtils = Reflect.construct(Ctor, []);
45988
+ };
45989
+ /**
45990
+ * Switches to persistent storage adapter (default).
45991
+ * Signals will be persisted to disk.
45992
+ */
45993
+ this.usePersist = () => {
45994
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
45995
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45996
+ };
45997
+ /**
45998
+ * Switches to in-memory storage adapter.
45999
+ * Signals will be stored in memory only.
46000
+ */
46001
+ this.useMemory = () => {
46002
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
46003
+ this._recentLiveUtils = new RecentMemoryLiveUtils();
46004
+ };
46005
+ /**
46006
+ * Clears the cached utils instance by resetting to the default persistent adapter.
46007
+ */
46008
+ this.clear = () => {
46009
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
46010
+ this._recentLiveUtils = new RecentPersistLiveUtils();
46011
+ };
46012
+ }
46013
+ }
46014
+ /**
46015
+ * Main recent signal adapter that manages both backtest and live recent signal storage.
46016
+ *
46017
+ * Features:
46018
+ * - Subscribes to activePingSubject for automatic storage updates
46019
+ * - Provides unified access to the latest signal for any context
46020
+ * - Singleshot enable pattern prevents duplicate subscriptions
46021
+ * - Cleanup function for proper unsubscription
46022
+ */
46023
+ class RecentAdapter {
46024
+ constructor() {
46025
+ /**
46026
+ * Enables recent signal storage by subscribing to activePingSubject.
46027
+ * Uses singleshot to ensure one-time subscription.
46028
+ *
46029
+ * @returns Cleanup function that unsubscribes from all emitters
46030
+ */
46031
+ this.enable = functoolsKit.singleshot(() => {
46032
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
46033
+ let unBacktest;
46034
+ let unLive;
46035
+ {
46036
+ const unBacktestPingActive = activePingSubject
46037
+ .filter(({ backtest }) => backtest)
46038
+ .connect((event) => RecentBacktest.handleActivePing(event));
46039
+ unBacktest = functoolsKit.compose(() => unBacktestPingActive());
46040
+ }
46041
+ {
46042
+ const unLivePingActive = activePingSubject
46043
+ .filter(({ backtest }) => !backtest)
46044
+ .connect((event) => RecentLive.handleActivePing(event));
46045
+ unLive = functoolsKit.compose(() => unLivePingActive());
46046
+ }
46047
+ const unEnable = () => this.enable.clear();
46048
+ return functoolsKit.compose(() => unBacktest(), () => unLive(), () => unEnable());
46049
+ });
46050
+ /**
46051
+ * Disables recent signal storage by unsubscribing from all emitters.
46052
+ * Safe to call multiple times.
46053
+ */
46054
+ this.disable = () => {
46055
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
46056
+ if (this.enable.hasValue()) {
46057
+ const lastSubscription = this.enable();
46058
+ lastSubscription();
46059
+ }
46060
+ };
46061
+ /**
46062
+ * Retrieves the latest active signal for the given symbol and context.
46063
+ * Searches backtest storage first, then live storage.
46064
+ *
46065
+ * @param symbol - Trading pair symbol
46066
+ * @param context - Execution context with strategyName, exchangeName, and frameName
46067
+ * @param backtest - Flag indicating if the context is backtest or live
46068
+ * @returns The latest signal or null if not found
46069
+ * @throws Error if RecentAdapter is not enabled
46070
+ */
46071
+ this.getLatestSignal = async (symbol, context) => {
46072
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46073
+ symbol,
46074
+ context,
46075
+ });
46076
+ if (!this.enable.hasValue()) {
46077
+ throw new Error("RecentAdapter is not enabled. Call enable() first.");
46078
+ }
46079
+ let result = null;
46080
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
46081
+ return result;
46082
+ }
46083
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
46084
+ return result;
46085
+ }
46086
+ return null;
46087
+ };
46088
+ }
46089
+ }
46090
+ /**
46091
+ * Global singleton instance of RecentAdapter.
46092
+ * Provides unified recent signal management for backtest and live trading.
46093
+ */
46094
+ const Recent = new RecentAdapter();
46095
+ /**
46096
+ * Global singleton instance of RecentLiveAdapter.
46097
+ * Provides live trading recent signal storage with pluggable backends.
46098
+ */
46099
+ const RecentLive = new RecentLiveAdapter();
46100
+ /**
46101
+ * Global singleton instance of RecentBacktestAdapter.
46102
+ * Provides backtest recent signal storage with pluggable backends.
46103
+ */
46104
+ const RecentBacktest = new RecentBacktestAdapter();
46105
+
46106
+ const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46107
+ /**
46108
+ * Returns the latest signal (pending or closed) for the current strategy context.
46109
+ *
46110
+ * Does not distinguish between active and closed signals — returns whichever
46111
+ * was recorded last. Useful for cooldown logic: e.g. skip opening a new position
46112
+ * for 4 hours after a stop-loss by checking the timestamp of the latest signal
46113
+ * regardless of its outcome.
46114
+ *
46115
+ * Searches backtest storage first, then live storage.
46116
+ * Returns null if no signal exists at all.
46117
+ *
46118
+ * Automatically detects backtest/live mode from execution context.
46119
+ *
46120
+ * @param symbol - Trading pair symbol
46121
+ * @returns Promise resolving to the latest signal or null
46122
+ *
46123
+ * @example
46124
+ * ```typescript
46125
+ * import { getLatestSignal } from "backtest-kit";
46126
+ *
46127
+ * const latest = await getLatestSignal("BTCUSDT");
46128
+ * if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
46129
+ * return; // cooldown after SL — skip new signal for 4 hours
46130
+ * }
46131
+ * ```
46132
+ */
46133
+ async function getLatestSignal(symbol) {
46134
+ backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
46135
+ if (!ExecutionContextService.hasContext()) {
46136
+ throw new Error("getLatestSignal requires an execution context");
46137
+ }
46138
+ if (!MethodContextService.hasContext()) {
46139
+ throw new Error("getLatestSignal requires a method context");
46140
+ }
46141
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46142
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46143
+ }
46144
+
45251
46145
  const DEFAULT_BM25_K1 = 1.5;
45252
46146
  const DEFAULT_BM25_B = 0.75;
45253
46147
  const DEFAULT_BM25_SCORE = 0.5;
@@ -47776,6 +48670,87 @@ class MarkdownUtils {
47776
48670
  backtest.maxDrawdownMarkdownService.unsubscribe();
47777
48671
  }
47778
48672
  };
48673
+ /**
48674
+ * Clears markdown report data selectively.
48675
+ *
48676
+ * Clears accumulated data for specified markdown services without unsubscribing.
48677
+ * Use this method to reset report data for specific services while keeping them active.
48678
+ *
48679
+ * Each cleared service will:
48680
+ * - Clear accumulated data for all reports
48681
+ * - Start fresh with new data for future events
48682
+ * - Not affect event subscriptions or report generation status
48683
+ *
48684
+ * @param config - Service configuration object specifying which services to clear. Defaults to clearing all services.
48685
+ * @param config.backtest - Clear backtest result report data
48686
+ * @param config.breakeven - Clear breakeven event tracking data
48687
+ * @param config.partial - Clear partial profit/loss event tracking data
48688
+ * @param config.heat - Clear portfolio heatmap analysis data
48689
+ * @param config.walker - Clear walker strategy comparison report data
48690
+ * @param config.performance - Clear performance bottleneck analysis data
48691
+ * @param config.risk - Clear risk rejection tracking data
48692
+ * @param config.schedule - Clear scheduled signal tracking data
48693
+ * @param config.live - Clear live trading event report data
48694
+ * @param config.strategy - Clear strategy report data
48695
+ * @param config.sync - Clear sync report data
48696
+ * @param config.highest_profit - Clear highest profit report data
48697
+ * @param config.max_drawdown - Clear max drawdown report data
48698
+ */
48699
+ 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) => {
48700
+ LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48701
+ backtest: bt,
48702
+ breakeven,
48703
+ heat,
48704
+ live,
48705
+ partial,
48706
+ performance,
48707
+ risk,
48708
+ strategy,
48709
+ schedule,
48710
+ walker,
48711
+ sync,
48712
+ highest_profit,
48713
+ });
48714
+ if (bt) {
48715
+ backtest.backtestMarkdownService.clear();
48716
+ }
48717
+ if (breakeven) {
48718
+ backtest.breakevenMarkdownService.clear();
48719
+ }
48720
+ if (heat) {
48721
+ backtest.heatMarkdownService.clear();
48722
+ }
48723
+ if (live) {
48724
+ backtest.liveMarkdownService.clear();
48725
+ }
48726
+ if (partial) {
48727
+ backtest.partialMarkdownService.clear();
48728
+ }
48729
+ if (performance) {
48730
+ backtest.performanceMarkdownService.clear();
48731
+ }
48732
+ if (risk) {
48733
+ backtest.riskMarkdownService.clear();
48734
+ }
48735
+ if (strategy) {
48736
+ backtest.strategyMarkdownService.clear();
48737
+ }
48738
+ if (schedule) {
48739
+ backtest.scheduleMarkdownService.clear();
48740
+ }
48741
+ if (walker) {
48742
+ backtest.walkerMarkdownService.clear();
48743
+ }
48744
+ if (sync) {
48745
+ backtest.syncMarkdownService.clear();
48746
+ }
48747
+ if (highest_profit) {
48748
+ backtest.highestProfitMarkdownService.clear();
48749
+ }
48750
+ if (max_drawdown) {
48751
+ backtest.maxDrawdownMarkdownService.clear();
48752
+ }
48753
+ };
47779
48754
  }
47780
48755
  }
47781
48756
  /**
@@ -47818,15 +48793,6 @@ class MarkdownAdapter extends MarkdownUtils {
47818
48793
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_USE_JSONL);
47819
48794
  MarkdownWriter.useJsonl();
47820
48795
  }
47821
- /**
47822
- * Clears the memoized storage cache.
47823
- * Call this when process.cwd() changes between strategy iterations
47824
- * so new storage instances are created with the updated base path.
47825
- */
47826
- clear() {
47827
- LOGGER_SERVICE$1.log(MARKDOWN_METHOD_NAME_CLEAR);
47828
- MarkdownWriter.clear();
47829
- }
47830
48796
  /**
47831
48797
  * Switches to a dummy markdown adapter that discards all writes.
47832
48798
  * All future markdown writes will be no-ops.
@@ -48498,6 +49464,68 @@ class LogAdapter {
48498
49464
  */
48499
49465
  const Log = new LogAdapter();
48500
49466
 
49467
+ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49468
+ /** List of all global subjects whose listeners should be snapshotted for session isolation */
49469
+ const SUBJECT_ISOLATION_LIST = [
49470
+ activePingSubject,
49471
+ backtestScheduleOpenSubject,
49472
+ breakevenSubject,
49473
+ doneBacktestSubject,
49474
+ doneLiveSubject,
49475
+ errorEmitter,
49476
+ exitEmitter,
49477
+ highestProfitSubject,
49478
+ maxDrawdownSubject,
49479
+ partialLossSubject,
49480
+ partialProfitSubject,
49481
+ performanceEmitter,
49482
+ progressBacktestEmitter,
49483
+ riskSubject,
49484
+ schedulePingSubject,
49485
+ shutdownEmitter,
49486
+ signalBacktestEmitter,
49487
+ signalEmitter,
49488
+ signalLiveEmitter,
49489
+ strategyCommitSubject,
49490
+ syncSubject,
49491
+ validationSubject,
49492
+ signalNotifySubject,
49493
+ ];
49494
+ /**
49495
+ * Creates a snapshot function for a given subject by clearing its internal
49496
+ * events map and returning a restore function that can put the original listeners back.
49497
+ * @param subject The subject to snapshot
49498
+ * @returns A function that restores the subject's original listeners when called
49499
+ */
49500
+ const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
49501
+ const emitter = subject["_emitter"];
49502
+ const events = emitter["_events"];
49503
+ emitter["_events"] = {};
49504
+ return () => {
49505
+ emitter["_events"] = events;
49506
+ };
49507
+ };
49508
+ /**
49509
+ * Manages isolation of global event-bus state between backtest sessions.
49510
+ * Allows temporarily detaching all subject subscriptions so that one session
49511
+ * does not interfere with another, then restoring them afterwards.
49512
+ */
49513
+ class SessionUtils {
49514
+ constructor() {
49515
+ /**
49516
+ * Snapshots the current listener state of every global subject by replacing
49517
+ * their internal `_events` map with an empty object.
49518
+ * @returns A restore function that, when called, puts all original listeners back.
49519
+ */
49520
+ this.createSnapshot = () => {
49521
+ backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
49522
+ const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
49523
+ return functoolsKit.compose(...snapshotList);
49524
+ };
49525
+ }
49526
+ }
49527
+ const Session = new SessionUtils();
49528
+
48501
49529
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
48502
49530
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
48503
49531
  const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
@@ -53307,7 +54335,44 @@ const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
53307
54335
  message: functoolsKit.getErrorMessage(error),
53308
54336
  backtest: false,
53309
54337
  });
54338
+ /**
54339
+ * Creates a notification model for signal info events.
54340
+ * @param data - The signal info contract data
54341
+ * @returns NotificationModel for signal info event
54342
+ */
54343
+ const CREATE_SIGNAL_INFO_NOTIFICATION_FN = (data) => ({
54344
+ type: "signal.info",
54345
+ id: CREATE_KEY_FN$2(),
54346
+ timestamp: data.timestamp,
54347
+ backtest: data.backtest,
54348
+ symbol: data.symbol,
54349
+ strategyName: data.strategyName,
54350
+ exchangeName: data.exchangeName,
54351
+ signalId: data.data.id,
54352
+ currentPrice: data.currentPrice,
54353
+ position: data.data.position,
54354
+ priceOpen: data.data.priceOpen,
54355
+ priceTakeProfit: data.data.priceTakeProfit,
54356
+ priceStopLoss: data.data.priceStopLoss,
54357
+ originalPriceTakeProfit: data.data.originalPriceTakeProfit,
54358
+ originalPriceStopLoss: data.data.originalPriceStopLoss,
54359
+ originalPriceOpen: data.data.originalPriceOpen,
54360
+ totalEntries: data.data.totalEntries,
54361
+ totalPartials: data.data.totalPartials,
54362
+ pnl: data.data.pnl,
54363
+ pnlPercentage: data.data.pnl.pnlPercentage,
54364
+ pnlPriceOpen: data.data.pnl.priceOpen,
54365
+ pnlPriceClose: data.data.pnl.priceClose,
54366
+ pnlCost: data.data.pnl.pnlCost,
54367
+ pnlEntries: data.data.pnl.pnlEntries,
54368
+ note: data.note,
54369
+ notificationId: data.notificationId,
54370
+ scheduledAt: data.data.scheduledAt,
54371
+ pendingAt: data.data.pendingAt,
54372
+ createdAt: data.timestamp,
54373
+ });
53310
54374
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryBacktestUtils.handleSignal";
54375
+ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryBacktestUtils.handleSignalNotify";
53311
54376
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryBacktestUtils.handlePartialProfit";
53312
54377
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryBacktestUtils.handlePartialLoss";
53313
54378
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryBacktestUtils.handleBreakeven";
@@ -53320,6 +54385,7 @@ const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "Notifi
53320
54385
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA = "NotificationMemoryBacktestUtils.getData";
53321
54386
  const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_DISPOSE = "NotificationMemoryBacktestUtils.dispose";
53322
54387
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryLiveUtils.handleSignal";
54388
+ const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationMemoryLiveUtils.handleSignalNotify";
53323
54389
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryLiveUtils.handlePartialProfit";
53324
54390
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryLiveUtils.handlePartialLoss";
53325
54391
  const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryLiveUtils.handleBreakeven";
@@ -53348,6 +54414,7 @@ const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_CLEAR = "NotificationLiveAdapter.cle
53348
54414
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistBacktestUtils.waitForInit";
53349
54415
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistBacktestUtils._updateNotifications";
53350
54416
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistBacktestUtils.handleSignal";
54417
+ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistBacktestUtils.handleSignalNotify";
53351
54418
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistBacktestUtils.handlePartialProfit";
53352
54419
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistBacktestUtils.handlePartialLoss";
53353
54420
  const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistBacktestUtils.handleBreakeven";
@@ -53362,6 +54429,7 @@ const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_DISPOSE = "NotificationPersistBa
53362
54429
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistLiveUtils.waitForInit";
53363
54430
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistLiveUtils._updateNotifications";
53364
54431
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistLiveUtils.handleSignal";
54432
+ const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY = "NotificationPersistLiveUtils.handleSignalNotify";
53365
54433
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistLiveUtils.handlePartialProfit";
53366
54434
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistLiveUtils.handlePartialLoss";
53367
54435
  const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistLiveUtils.handleBreakeven";
@@ -53404,6 +54472,12 @@ class NotificationMemoryBacktestUtils {
53404
54472
  this._addNotification(notification);
53405
54473
  }
53406
54474
  };
54475
+ this.handleSignalNotify = async (data) => {
54476
+ backtest.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54477
+ signalId: data.data.id,
54478
+ });
54479
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54480
+ };
53407
54481
  /**
53408
54482
  * Handles partial profit availability event.
53409
54483
  * @param data - The partial profit contract data
@@ -53548,6 +54622,8 @@ class NotificationDummyBacktestUtils {
53548
54622
  */
53549
54623
  this.handleSignal = async () => {
53550
54624
  };
54625
+ this.handleSignalNotify = async () => {
54626
+ };
53551
54627
  /**
53552
54628
  * No-op handler for partial profit event.
53553
54629
  */
@@ -53655,6 +54731,14 @@ class NotificationPersistBacktestUtils {
53655
54731
  await this._updateNotifications();
53656
54732
  }
53657
54733
  };
54734
+ this.handleSignalNotify = async (data) => {
54735
+ backtest.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54736
+ signalId: data.data.id,
54737
+ });
54738
+ await this.waitForInit();
54739
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54740
+ await this._updateNotifications();
54741
+ };
53658
54742
  /**
53659
54743
  * Handles partial profit availability event.
53660
54744
  * @param data - The partial profit contract data
@@ -53856,6 +54940,12 @@ class NotificationMemoryLiveUtils {
53856
54940
  this._addNotification(notification);
53857
54941
  }
53858
54942
  };
54943
+ this.handleSignalNotify = async (data) => {
54944
+ backtest.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
54945
+ signalId: data.data.id,
54946
+ });
54947
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
54948
+ };
53859
54949
  /**
53860
54950
  * Handles partial profit availability event.
53861
54951
  * @param data - The partial profit contract data
@@ -54000,6 +55090,8 @@ class NotificationDummyLiveUtils {
54000
55090
  */
54001
55091
  this.handleSignal = async () => {
54002
55092
  };
55093
+ this.handleSignalNotify = async () => {
55094
+ };
54003
55095
  /**
54004
55096
  * No-op handler for partial profit event.
54005
55097
  */
@@ -54108,6 +55200,14 @@ class NotificationPersistLiveUtils {
54108
55200
  await this._updateNotifications();
54109
55201
  }
54110
55202
  };
55203
+ this.handleSignalNotify = async (data) => {
55204
+ backtest.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL_NOTIFY, {
55205
+ signalId: data.data.id,
55206
+ });
55207
+ await this.waitForInit();
55208
+ this._addNotification(CREATE_SIGNAL_INFO_NOTIFICATION_FN(data));
55209
+ await this._updateNotifications();
55210
+ };
54111
55211
  /**
54112
55212
  * Handles partial profit availability event.
54113
55213
  * @param data - The partial profit contract data
@@ -54301,6 +55401,9 @@ class NotificationBacktestAdapter {
54301
55401
  this.handleSignal = async (data) => {
54302
55402
  return await this._notificationBacktestUtils.handleSignal(data);
54303
55403
  };
55404
+ this.handleSignalNotify = async (data) => {
55405
+ return await this._notificationBacktestUtils.handleSignalNotify(data);
55406
+ };
54304
55407
  /**
54305
55408
  * Handles partial profit availability event.
54306
55409
  * Proxies call to the underlying notification adapter.
@@ -54456,6 +55559,9 @@ class NotificationLiveAdapter {
54456
55559
  this.handleSignal = async (data) => {
54457
55560
  return await this._notificationLiveUtils.handleSignal(data);
54458
55561
  };
55562
+ this.handleSignalNotify = async (data) => {
55563
+ return await this._notificationLiveUtils.handleSignalNotify(data);
55564
+ };
54459
55565
  /**
54460
55566
  * Handles partial profit availability event.
54461
55567
  * Proxies call to the underlying notification adapter.
@@ -54634,7 +55740,10 @@ class NotificationAdapter {
54634
55740
  const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
54635
55741
  const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
54636
55742
  const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
54637
- unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation());
55743
+ const unBacktestSignalNotify = signalNotifySubject
55744
+ .filter(({ backtest }) => backtest)
55745
+ .connect((data) => NotificationBacktest.handleSignalNotify(data));
55746
+ unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
54638
55747
  }
54639
55748
  {
54640
55749
  const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
@@ -54659,7 +55768,10 @@ class NotificationAdapter {
54659
55768
  const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
54660
55769
  const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
54661
55770
  const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
54662
- unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation());
55771
+ const unLiveSignalNotify = signalNotifySubject
55772
+ .filter(({ backtest }) => !backtest)
55773
+ .connect((data) => NotificationLive.handleSignalNotify(data));
55774
+ unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
54663
55775
  }
54664
55776
  return () => {
54665
55777
  unLive();
@@ -54735,12 +55847,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
54735
55847
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
54736
55848
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
54737
55849
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
55850
+ const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
54738
55851
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
54739
55852
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
55853
+ const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
54740
55854
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
54741
55855
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
54742
55856
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
54743
55857
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
55858
+ const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
54744
55859
  const MS_PER_MINUTE$1 = 60000;
54745
55860
  const INTERVAL_MINUTES$1 = {
54746
55861
  "1m": 1,
@@ -54894,11 +56009,17 @@ class CacheFnInstance {
54894
56009
  return cached;
54895
56010
  }
54896
56011
  }
56012
+ const value = this.fn(...args);
54897
56013
  const newCache = {
54898
56014
  when: currentWhen,
54899
- value: this.fn(...args),
56015
+ value,
54900
56016
  };
54901
56017
  this._cacheMap.set(key, newCache);
56018
+ if (value && value instanceof Promise) {
56019
+ value.catch(() => {
56020
+ this._cacheMap.delete(key);
56021
+ });
56022
+ }
54902
56023
  return newCache;
54903
56024
  };
54904
56025
  /**
@@ -54930,6 +56051,36 @@ class CacheFnInstance {
54930
56051
  }
54931
56052
  }
54932
56053
  };
56054
+ /**
56055
+ * Check whether a valid (non-expired) cache entry exists for the current context and arguments.
56056
+ *
56057
+ * Returns `true` if a cached value exists and its interval is still current.
56058
+ * Returns `false` if there is no entry or the cached entry has expired.
56059
+ *
56060
+ * Requires active execution context and method context.
56061
+ *
56062
+ * @param args - Arguments to look up in the cache
56063
+ * @returns `true` if a fresh cached value exists, `false` otherwise
56064
+ */
56065
+ this.hasValue = (...args) => {
56066
+ if (!MethodContextService.hasContext()) {
56067
+ throw new Error("CacheFnInstance hasValue requires method context");
56068
+ }
56069
+ if (!ExecutionContextService.hasContext()) {
56070
+ throw new Error("CacheFnInstance hasValue requires execution context");
56071
+ }
56072
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56073
+ const argKey = String(this.key(args));
56074
+ const key = `${contextKey}:${argKey}`;
56075
+ const cached = this._cacheMap.get(key);
56076
+ if (!cached) {
56077
+ return false;
56078
+ }
56079
+ const currentWhen = backtest.executionContextService.context.when;
56080
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
56081
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
56082
+ return currentAligned === cachedAligned;
56083
+ };
54933
56084
  /**
54934
56085
  * Garbage collect expired cache entries.
54935
56086
  *
@@ -55054,6 +56205,33 @@ class CacheFileInstance {
55054
56205
  await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
55055
56206
  return result;
55056
56207
  };
56208
+ /**
56209
+ * Check whether a cached value exists on disk for the given arguments and current interval.
56210
+ *
56211
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56212
+ * Returns `false` if no record is found.
56213
+ *
56214
+ * Requires active execution context and method context.
56215
+ *
56216
+ * @param args - Arguments forwarded to the key generator
56217
+ * @returns `true` if a cached record exists, `false` otherwise
56218
+ */
56219
+ this.hasValue = async (...args) => {
56220
+ backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56221
+ if (!MethodContextService.hasContext()) {
56222
+ throw new Error("CacheFileInstance hasValue requires method context");
56223
+ }
56224
+ if (!ExecutionContextService.hasContext()) {
56225
+ throw new Error("CacheFileInstance hasValue requires execution context");
56226
+ }
56227
+ const [symbol, ...rest] = args;
56228
+ const { when } = backtest.executionContextService.context;
56229
+ const alignedTs = align$1(when.getTime(), this.interval);
56230
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56231
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
56232
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
56233
+ return cached !== null;
56234
+ };
55057
56235
  /**
55058
56236
  * Soft-delete all persisted records for this instance's bucket.
55059
56237
  * After this call the next `run()` will recompute and re-cache the value.
@@ -55140,23 +56318,30 @@ class CacheUtils {
55140
56318
  wrappedFn.clear = () => {
55141
56319
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
55142
56320
  if (!MethodContextService.hasContext()) {
55143
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55144
- return;
56321
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
55145
56322
  }
55146
56323
  if (!ExecutionContextService.hasContext()) {
55147
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55148
- return;
56324
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
55149
56325
  }
55150
56326
  this._getFnInstance.get(run)?.clear();
55151
56327
  };
55152
56328
  wrappedFn.gc = () => {
55153
56329
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
55154
56330
  if (!ExecutionContextService.hasContext()) {
55155
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_GC} called without execution context, skipping`);
55156
- return;
56331
+ throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
55157
56332
  }
55158
56333
  return this._getFnInstance.get(run)?.gc();
55159
56334
  };
56335
+ wrappedFn.hasValue = (...args) => {
56336
+ backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
56337
+ if (!MethodContextService.hasContext()) {
56338
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56339
+ }
56340
+ if (!ExecutionContextService.hasContext()) {
56341
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56342
+ }
56343
+ return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
56344
+ };
55160
56345
  return wrappedFn;
55161
56346
  };
55162
56347
  /**
@@ -55208,8 +56393,25 @@ class CacheUtils {
55208
56393
  };
55209
56394
  wrappedFn.clear = async () => {
55210
56395
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
56396
+ if (!MethodContextService.hasContext()) {
56397
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
56398
+ }
56399
+ if (!ExecutionContextService.hasContext()) {
56400
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
56401
+ }
55211
56402
  await this._getFileInstance.get(run)?.clear();
55212
56403
  };
56404
+ wrappedFn.hasValue = async (...args) => {
56405
+ backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
56406
+ if (!MethodContextService.hasContext()) {
56407
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56408
+ }
56409
+ if (!ExecutionContextService.hasContext()) {
56410
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56411
+ }
56412
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56413
+ return await instance.hasValue(...args);
56414
+ };
55213
56415
  return wrappedFn;
55214
56416
  };
55215
56417
  /**
@@ -55278,11 +56480,14 @@ const Cache = new CacheUtils();
55278
56480
 
55279
56481
  const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
55280
56482
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
56483
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
55281
56484
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
55282
56485
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
55283
56486
  const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
56487
+ const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
55284
56488
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
55285
56489
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
56490
+ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
55286
56491
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
55287
56492
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
55288
56493
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
@@ -55395,7 +56600,7 @@ class IntervalFnInstance {
55395
56600
  * within the same interval or when `fn` itself returned `null`
55396
56601
  * @throws Error if method context, execution context, or interval is missing
55397
56602
  */
55398
- this.run = async (...args) => {
56603
+ this.run = (...args) => {
55399
56604
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
55400
56605
  const step = INTERVAL_MINUTES[this.interval];
55401
56606
  {
@@ -55417,10 +56622,15 @@ class IntervalFnInstance {
55417
56622
  if (this._stateMap.get(stateKey) === currentAligned) {
55418
56623
  return null;
55419
56624
  }
55420
- const result = await this.fn.apply(null, args);
56625
+ const result = this.fn.apply(null, args);
55421
56626
  if (result !== null) {
55422
56627
  this._stateMap.set(stateKey, currentAligned);
55423
56628
  }
56629
+ if (result && result instanceof Promise) {
56630
+ result.catch(() => {
56631
+ this._stateMap.delete(stateKey);
56632
+ });
56633
+ }
55424
56634
  return result;
55425
56635
  };
55426
56636
  /**
@@ -55440,6 +56650,31 @@ class IntervalFnInstance {
55440
56650
  }
55441
56651
  }
55442
56652
  };
56653
+ /**
56654
+ * Check whether the function has already fired for the current interval and context.
56655
+ *
56656
+ * Returns `true` if the function fired (non-null result) within the current interval boundary.
56657
+ * Returns `false` if there is no recorded firing for this interval.
56658
+ *
56659
+ * Requires active method context and execution context.
56660
+ *
56661
+ * @param args - Arguments to look up in the state map
56662
+ * @returns `true` if the function has already fired this interval, `false` otherwise
56663
+ */
56664
+ this.hasValue = (...args) => {
56665
+ if (!MethodContextService.hasContext()) {
56666
+ throw new Error("IntervalFnInstance hasValue requires method context");
56667
+ }
56668
+ if (!ExecutionContextService.hasContext()) {
56669
+ throw new Error("IntervalFnInstance hasValue requires execution context");
56670
+ }
56671
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56672
+ const currentWhen = backtest.executionContextService.context.when;
56673
+ const currentAligned = align(currentWhen.getTime(), this.interval);
56674
+ const argKey = this.key(args);
56675
+ const stateKey = `${contextKey}:${argKey}`;
56676
+ return this._stateMap.get(stateKey) === currentAligned;
56677
+ };
55443
56678
  /**
55444
56679
  * Garbage collect expired state entries.
55445
56680
  *
@@ -55561,6 +56796,33 @@ class IntervalFileInstance {
55561
56796
  }
55562
56797
  return result;
55563
56798
  };
56799
+ /**
56800
+ * Check whether the function has already fired for the current interval on disk.
56801
+ *
56802
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56803
+ * Returns `false` if no record is found.
56804
+ *
56805
+ * Requires active execution context and method context.
56806
+ *
56807
+ * @param args - Arguments forwarded to the key generator
56808
+ * @returns `true` if a fired record exists, `false` otherwise
56809
+ */
56810
+ this.hasValue = async (...args) => {
56811
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56812
+ if (!MethodContextService.hasContext()) {
56813
+ throw new Error("IntervalFileInstance hasValue requires method context");
56814
+ }
56815
+ if (!ExecutionContextService.hasContext()) {
56816
+ throw new Error("IntervalFileInstance hasValue requires execution context");
56817
+ }
56818
+ const [symbol, ...rest] = args;
56819
+ const { when } = backtest.executionContextService.context;
56820
+ const alignedMs = align(when.getTime(), this.interval);
56821
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56822
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
56823
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
56824
+ return cached !== null;
56825
+ };
55564
56826
  /**
55565
56827
  * Soft-delete all persisted records for this instance's bucket.
55566
56828
  * After this call the function will fire again on the next `run()`.
@@ -55641,23 +56903,30 @@ class IntervalUtils {
55641
56903
  wrappedFn.clear = () => {
55642
56904
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
55643
56905
  if (!MethodContextService.hasContext()) {
55644
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55645
- return;
56906
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
55646
56907
  }
55647
56908
  if (!ExecutionContextService.hasContext()) {
55648
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55649
- return;
56909
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
55650
56910
  }
55651
56911
  this._getInstance.get(run)?.clear();
55652
56912
  };
55653
56913
  wrappedFn.gc = () => {
55654
56914
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
55655
56915
  if (!ExecutionContextService.hasContext()) {
55656
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
55657
- return;
56916
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
55658
56917
  }
55659
56918
  return this._getInstance.get(run)?.gc();
55660
56919
  };
56920
+ wrappedFn.hasValue = (...args) => {
56921
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
56922
+ if (!MethodContextService.hasContext()) {
56923
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56924
+ }
56925
+ if (!ExecutionContextService.hasContext()) {
56926
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56927
+ }
56928
+ return this._getInstance.get(run)?.hasValue(...args) ?? false;
56929
+ };
55661
56930
  return wrappedFn;
55662
56931
  };
55663
56932
  /**
@@ -55700,8 +56969,25 @@ class IntervalUtils {
55700
56969
  };
55701
56970
  wrappedFn.clear = async () => {
55702
56971
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
56972
+ if (!MethodContextService.hasContext()) {
56973
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
56974
+ }
56975
+ if (!ExecutionContextService.hasContext()) {
56976
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
56977
+ }
55703
56978
  await this._getFileInstance.get(run)?.clear();
55704
56979
  };
56980
+ wrappedFn.hasValue = async (...args) => {
56981
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
56982
+ if (!MethodContextService.hasContext()) {
56983
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56984
+ }
56985
+ if (!ExecutionContextService.hasContext()) {
56986
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56987
+ }
56988
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56989
+ return await instance.hasValue(...args);
56990
+ };
55705
56991
  return wrappedFn;
55706
56992
  };
55707
56993
  /**
@@ -56911,18 +58197,23 @@ exports.PersistMeasureAdapter = PersistMeasureAdapter;
56911
58197
  exports.PersistMemoryAdapter = PersistMemoryAdapter;
56912
58198
  exports.PersistNotificationAdapter = PersistNotificationAdapter;
56913
58199
  exports.PersistPartialAdapter = PersistPartialAdapter;
58200
+ exports.PersistRecentAdapter = PersistRecentAdapter;
56914
58201
  exports.PersistRiskAdapter = PersistRiskAdapter;
56915
58202
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
56916
58203
  exports.PersistSignalAdapter = PersistSignalAdapter;
56917
58204
  exports.PersistStorageAdapter = PersistStorageAdapter;
56918
58205
  exports.Position = Position;
56919
58206
  exports.PositionSize = PositionSize;
58207
+ exports.Recent = Recent;
58208
+ exports.RecentBacktest = RecentBacktest;
58209
+ exports.RecentLive = RecentLive;
56920
58210
  exports.Reflect = Reflect$1;
56921
58211
  exports.Report = Report;
56922
58212
  exports.ReportBase = ReportBase;
56923
58213
  exports.ReportWriter = ReportWriter;
56924
58214
  exports.Risk = Risk;
56925
58215
  exports.Schedule = Schedule;
58216
+ exports.Session = Session;
56926
58217
  exports.Storage = Storage;
56927
58218
  exports.StorageBacktest = StorageBacktest;
56928
58219
  exports.StorageLive = StorageLive;
@@ -56947,6 +58238,7 @@ exports.commitPartialLoss = commitPartialLoss;
56947
58238
  exports.commitPartialLossCost = commitPartialLossCost;
56948
58239
  exports.commitPartialProfit = commitPartialProfit;
56949
58240
  exports.commitPartialProfitCost = commitPartialProfitCost;
58241
+ exports.commitSignalNotify = commitSignalNotify;
56950
58242
  exports.commitTrailingStop = commitTrailingStop;
56951
58243
  exports.commitTrailingStopCost = commitTrailingStopCost;
56952
58244
  exports.commitTrailingTake = commitTrailingTake;
@@ -56976,6 +58268,7 @@ exports.getDefaultConfig = getDefaultConfig;
56976
58268
  exports.getEffectivePriceOpen = getEffectivePriceOpen;
56977
58269
  exports.getExchangeSchema = getExchangeSchema;
56978
58270
  exports.getFrameSchema = getFrameSchema;
58271
+ exports.getLatestSignal = getLatestSignal;
56979
58272
  exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
56980
58273
  exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
56981
58274
  exports.getMode = getMode;
@@ -57064,6 +58357,8 @@ exports.listenSignalBacktest = listenSignalBacktest;
57064
58357
  exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
57065
58358
  exports.listenSignalLive = listenSignalLive;
57066
58359
  exports.listenSignalLiveOnce = listenSignalLiveOnce;
58360
+ exports.listenSignalNotify = listenSignalNotify;
58361
+ exports.listenSignalNotifyOnce = listenSignalNotifyOnce;
57067
58362
  exports.listenSignalOnce = listenSignalOnce;
57068
58363
  exports.listenStrategyCommit = listenStrategyCommit;
57069
58364
  exports.listenStrategyCommitOnce = listenStrategyCommitOnce;