backtest-kit 6.12.0 → 6.13.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
@@ -1002,6 +1002,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
1002
1002
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
1003
1003
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
1004
1004
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1005
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1006
+ const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1007
+ const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
1008
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
1009
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
1010
+ const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
1005
1011
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
1006
1012
  const BASE_UNLINK_RETRY_COUNT = 5;
1007
1013
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2848,6 +2854,115 @@ class PersistMemoryUtils {
2848
2854
  * ```
2849
2855
  */
2850
2856
  const PersistMemoryAdapter = new PersistMemoryUtils();
2857
+ /**
2858
+ * Utility class for managing recent signal persistence.
2859
+ *
2860
+ * Features:
2861
+ * - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
2862
+ * - Custom adapter support
2863
+ * - Atomic read/write operations
2864
+ * - Crash-safe recent signal state management
2865
+ *
2866
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2867
+ */
2868
+ class PersistRecentUtils {
2869
+ constructor() {
2870
+ this.PersistRecentFactory = PersistBase;
2871
+ 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, [
2872
+ this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
2873
+ `./dump/data/recent/`,
2874
+ ]));
2875
+ /**
2876
+ * Reads the latest persisted recent signal for a given context.
2877
+ *
2878
+ * Returns null if no recent signal exists.
2879
+ *
2880
+ * @param symbol - Trading pair symbol
2881
+ * @param strategyName - Strategy identifier
2882
+ * @param exchangeName - Exchange identifier
2883
+ * @param frameName - Frame identifier
2884
+ * @returns Promise resolving to recent signal or null
2885
+ */
2886
+ this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
2887
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
2888
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2889
+ const isInitial = !this.getStorage.has(key);
2890
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2891
+ await stateStorage.waitForInit(isInitial);
2892
+ if (await stateStorage.hasValue(symbol)) {
2893
+ return await stateStorage.readValue(symbol);
2894
+ }
2895
+ return null;
2896
+ };
2897
+ /**
2898
+ * Writes the latest recent signal to disk with atomic file writes.
2899
+ *
2900
+ * Uses symbol as the entity ID within the per-context storage instance.
2901
+ * Uses atomic writes to prevent corruption on crashes.
2902
+ *
2903
+ * @param signalRow - Recent signal data to persist
2904
+ * @param symbol - Trading pair symbol
2905
+ * @param strategyName - Strategy identifier
2906
+ * @param exchangeName - Exchange identifier
2907
+ * @param frameName - Frame identifier
2908
+ * @returns Promise that resolves when write is complete
2909
+ */
2910
+ this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
2911
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
2912
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2913
+ const isInitial = !this.getStorage.has(key);
2914
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2915
+ await stateStorage.waitForInit(isInitial);
2916
+ await stateStorage.writeValue(symbol, signalRow);
2917
+ };
2918
+ }
2919
+ createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
2920
+ const parts = [symbol, strategyName, exchangeName];
2921
+ if (frameName)
2922
+ parts.push(frameName);
2923
+ parts.push(backtest ? "backtest" : "live");
2924
+ return parts;
2925
+ }
2926
+ /**
2927
+ * Registers a custom persistence adapter.
2928
+ *
2929
+ * @param Ctor - Custom PersistBase constructor
2930
+ */
2931
+ usePersistRecentAdapter(Ctor) {
2932
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
2933
+ this.PersistRecentFactory = Ctor;
2934
+ }
2935
+ /**
2936
+ * Clears the memoized storage cache.
2937
+ * Call this when process.cwd() changes between strategy iterations
2938
+ * so new storage instances are created with the updated base path.
2939
+ */
2940
+ clear() {
2941
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
2942
+ this.getStorage.clear();
2943
+ }
2944
+ /**
2945
+ * Switches to the default JSON persist adapter.
2946
+ * All future persistence writes will use JSON storage.
2947
+ */
2948
+ useJson() {
2949
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
2950
+ this.usePersistRecentAdapter(PersistBase);
2951
+ }
2952
+ /**
2953
+ * Switches to a dummy persist adapter that discards all writes.
2954
+ * All future persistence writes will be no-ops.
2955
+ */
2956
+ useDummy() {
2957
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
2958
+ this.usePersistRecentAdapter(PersistDummy);
2959
+ }
2960
+ }
2961
+ /**
2962
+ * Global singleton instance of PersistRecentUtils.
2963
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2964
+ */
2965
+ const PersistRecentAdapter = new PersistRecentUtils();
2851
2966
 
2852
2967
  var _a$2, _b$2;
2853
2968
  const BUSY_DELAY = 100;
@@ -10175,7 +10290,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10175
10290
  * @param backtest - Whether running in backtest mode
10176
10291
  * @returns Unique string key for memoization
10177
10292
  */
10178
- const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
10293
+ const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
10179
10294
  const parts = [symbol, strategyName, exchangeName];
10180
10295
  if (frameName)
10181
10296
  parts.push(frameName);
@@ -10442,7 +10557,7 @@ class StrategyConnectionService {
10442
10557
  * @param backtest - Whether running in backtest mode
10443
10558
  * @returns Configured ClientStrategy instance
10444
10559
  */
10445
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10560
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10446
10561
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10447
10562
  return new ClientStrategy({
10448
10563
  symbol,
@@ -11363,7 +11478,7 @@ class StrategyConnectionService {
11363
11478
  }
11364
11479
  return;
11365
11480
  }
11366
- const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11481
+ const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11367
11482
  if (!this.getStrategy.has(key)) {
11368
11483
  return;
11369
11484
  }
@@ -12532,7 +12647,7 @@ class ClientRisk {
12532
12647
  * @param backtest - Whether running in backtest mode
12533
12648
  * @returns Unique string key for memoization
12534
12649
  */
12535
- const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12650
+ const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
12536
12651
  const parts = [riskName, exchangeName];
12537
12652
  if (frameName)
12538
12653
  parts.push(frameName);
@@ -12632,7 +12747,7 @@ class RiskConnectionService {
12632
12747
  * @param backtest - True if backtest mode, false if live mode
12633
12748
  * @returns Configured ClientRisk instance
12634
12749
  */
12635
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12750
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12636
12751
  const schema = this.riskSchemaService.get(riskName);
12637
12752
  return new ClientRisk({
12638
12753
  ...schema,
@@ -12701,7 +12816,7 @@ class RiskConnectionService {
12701
12816
  payload,
12702
12817
  });
12703
12818
  if (payload) {
12704
- const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12819
+ const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12705
12820
  this.getRisk.clear(key);
12706
12821
  }
12707
12822
  else {
@@ -13745,7 +13860,7 @@ class ClientAction {
13745
13860
  * @param backtest - Whether running in backtest mode
13746
13861
  * @returns Unique string key for memoization
13747
13862
  */
13748
- const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13863
+ const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
13749
13864
  const parts = [actionName, strategyName, exchangeName];
13750
13865
  if (frameName)
13751
13866
  parts.push(frameName);
@@ -13797,7 +13912,7 @@ class ActionConnectionService {
13797
13912
  * @param backtest - True if backtest mode, false if live mode
13798
13913
  * @returns Configured ClientAction instance
13799
13914
  */
13800
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13915
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13801
13916
  const schema = this.actionSchemaService.get(actionName);
13802
13917
  return new ClientAction({
13803
13918
  ...schema,
@@ -14008,7 +14123,7 @@ class ActionConnectionService {
14008
14123
  await Promise.all(actions.map(async (action) => await action.dispose()));
14009
14124
  return;
14010
14125
  }
14011
- const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14126
+ const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14012
14127
  if (!this.getAction.has(key)) {
14013
14128
  return;
14014
14129
  }
@@ -14026,7 +14141,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
14026
14141
  * @param exchangeName - Exchange name
14027
14142
  * @returns Unique string key for memoization
14028
14143
  */
14029
- const CREATE_KEY_FN$q = (exchangeName) => {
14144
+ const CREATE_KEY_FN$r = (exchangeName) => {
14030
14145
  return exchangeName;
14031
14146
  };
14032
14147
  /**
@@ -14050,7 +14165,7 @@ class ExchangeCoreService {
14050
14165
  * @param exchangeName - Name of the exchange to validate
14051
14166
  * @returns Promise that resolves when validation is complete
14052
14167
  */
14053
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
14168
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
14054
14169
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14055
14170
  exchangeName,
14056
14171
  });
@@ -14302,7 +14417,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14302
14417
  * @param context - Execution context with strategyName, exchangeName, frameName
14303
14418
  * @returns Unique string key for memoization
14304
14419
  */
14305
- const CREATE_KEY_FN$p = (context) => {
14420
+ const CREATE_KEY_FN$q = (context) => {
14306
14421
  const parts = [context.strategyName, context.exchangeName];
14307
14422
  if (context.frameName)
14308
14423
  parts.push(context.frameName);
@@ -14334,7 +14449,7 @@ class StrategyCoreService {
14334
14449
  * @param context - Execution context with strategyName, exchangeName, frameName
14335
14450
  * @returns Promise that resolves when validation is complete
14336
14451
  */
14337
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
14452
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
14338
14453
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14339
14454
  context,
14340
14455
  });
@@ -15672,7 +15787,7 @@ class SizingGlobalService {
15672
15787
  * @param context - Context with riskName, exchangeName, frameName
15673
15788
  * @returns Unique string key for memoization
15674
15789
  */
15675
- const CREATE_KEY_FN$o = (context) => {
15790
+ const CREATE_KEY_FN$p = (context) => {
15676
15791
  const parts = [context.riskName, context.exchangeName];
15677
15792
  if (context.frameName)
15678
15793
  parts.push(context.frameName);
@@ -15698,7 +15813,7 @@ class RiskGlobalService {
15698
15813
  * @param payload - Payload with riskName, exchangeName and frameName
15699
15814
  * @returns Promise that resolves when validation is complete
15700
15815
  */
15701
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15816
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15702
15817
  this.loggerService.log("riskGlobalService validate", {
15703
15818
  context,
15704
15819
  });
@@ -15776,7 +15891,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
15776
15891
  * @param context - Execution context with strategyName, exchangeName, frameName
15777
15892
  * @returns Unique string key for memoization
15778
15893
  */
15779
- const CREATE_KEY_FN$n = (context) => {
15894
+ const CREATE_KEY_FN$o = (context) => {
15780
15895
  const parts = [context.strategyName, context.exchangeName];
15781
15896
  if (context.frameName)
15782
15897
  parts.push(context.frameName);
@@ -15820,7 +15935,7 @@ class ActionCoreService {
15820
15935
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15821
15936
  * @returns Promise that resolves when all validations complete
15822
15937
  */
15823
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15938
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15824
15939
  this.loggerService.log(METHOD_NAME_VALIDATE, {
15825
15940
  context,
15826
15941
  });
@@ -20863,7 +20978,7 @@ const ReportWriter = new ReportWriterAdapter();
20863
20978
  * @param backtest - Whether running in backtest mode
20864
20979
  * @returns Unique string key for memoization
20865
20980
  */
20866
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20981
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
20867
20982
  const parts = [symbol, strategyName, exchangeName];
20868
20983
  if (frameName)
20869
20984
  parts.push(frameName);
@@ -21109,7 +21224,7 @@ class BacktestMarkdownService {
21109
21224
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21110
21225
  * Each combination gets its own isolated storage instance.
21111
21226
  */
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));
21227
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21113
21228
  /**
21114
21229
  * Processes tick events and accumulates closed signals.
21115
21230
  * Should be called from IStrategyCallbacks.onTick.
@@ -21266,7 +21381,7 @@ class BacktestMarkdownService {
21266
21381
  payload,
21267
21382
  });
21268
21383
  if (payload) {
21269
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21384
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21270
21385
  this.getStorage.clear(key);
21271
21386
  }
21272
21387
  else {
@@ -21328,7 +21443,7 @@ class BacktestMarkdownService {
21328
21443
  * @param backtest - Whether running in backtest mode
21329
21444
  * @returns Unique string key for memoization
21330
21445
  */
21331
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
21446
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
21332
21447
  const parts = [symbol, strategyName, exchangeName];
21333
21448
  if (frameName)
21334
21449
  parts.push(frameName);
@@ -21823,7 +21938,7 @@ class LiveMarkdownService {
21823
21938
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21824
21939
  * Each combination gets its own isolated storage instance.
21825
21940
  */
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));
21941
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21827
21942
  /**
21828
21943
  * Subscribes to live signal emitter to receive tick events.
21829
21944
  * Protected against multiple subscriptions.
@@ -22041,7 +22156,7 @@ class LiveMarkdownService {
22041
22156
  payload,
22042
22157
  });
22043
22158
  if (payload) {
22044
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22159
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22045
22160
  this.getStorage.clear(key);
22046
22161
  }
22047
22162
  else {
@@ -22061,7 +22176,7 @@ class LiveMarkdownService {
22061
22176
  * @param backtest - Whether running in backtest mode
22062
22177
  * @returns Unique string key for memoization
22063
22178
  */
22064
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22179
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
22065
22180
  const parts = [symbol, strategyName, exchangeName];
22066
22181
  if (frameName)
22067
22182
  parts.push(frameName);
@@ -22350,7 +22465,7 @@ class ScheduleMarkdownService {
22350
22465
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22351
22466
  * Each combination gets its own isolated storage instance.
22352
22467
  */
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));
22468
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22354
22469
  /**
22355
22470
  * Subscribes to signal emitter to receive scheduled signal events.
22356
22471
  * Protected against multiple subscriptions.
@@ -22553,7 +22668,7 @@ class ScheduleMarkdownService {
22553
22668
  payload,
22554
22669
  });
22555
22670
  if (payload) {
22556
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22671
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22557
22672
  this.getStorage.clear(key);
22558
22673
  }
22559
22674
  else {
@@ -22573,7 +22688,7 @@ class ScheduleMarkdownService {
22573
22688
  * @param backtest - Whether running in backtest mode
22574
22689
  * @returns Unique string key for memoization
22575
22690
  */
22576
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
22691
+ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22577
22692
  const parts = [symbol, strategyName, exchangeName];
22578
22693
  if (frameName)
22579
22694
  parts.push(frameName);
@@ -22818,7 +22933,7 @@ class PerformanceMarkdownService {
22818
22933
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22819
22934
  * Each combination gets its own isolated storage instance.
22820
22935
  */
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));
22936
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22822
22937
  /**
22823
22938
  * Subscribes to performance emitter to receive performance events.
22824
22939
  * Protected against multiple subscriptions.
@@ -22985,7 +23100,7 @@ class PerformanceMarkdownService {
22985
23100
  payload,
22986
23101
  });
22987
23102
  if (payload) {
22988
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23103
+ const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22989
23104
  this.getStorage.clear(key);
22990
23105
  }
22991
23106
  else {
@@ -23464,7 +23579,7 @@ class WalkerMarkdownService {
23464
23579
  * @param backtest - Whether running in backtest mode
23465
23580
  * @returns Unique string key for memoization
23466
23581
  */
23467
- const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
23582
+ const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
23468
23583
  const parts = [exchangeName];
23469
23584
  if (frameName)
23470
23585
  parts.push(frameName);
@@ -23911,7 +24026,7 @@ class HeatMarkdownService {
23911
24026
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23912
24027
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23913
24028
  */
23914
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24029
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23915
24030
  /**
23916
24031
  * Subscribes to signal emitter to receive tick events.
23917
24032
  * Protected against multiple subscriptions.
@@ -24129,7 +24244,7 @@ class HeatMarkdownService {
24129
24244
  payload,
24130
24245
  });
24131
24246
  if (payload) {
24132
- const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
24247
+ const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
24133
24248
  this.getStorage.clear(key);
24134
24249
  }
24135
24250
  else {
@@ -25160,7 +25275,7 @@ class ClientPartial {
25160
25275
  * @param backtest - Whether running in backtest mode
25161
25276
  * @returns Unique string key for memoization
25162
25277
  */
25163
- const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25278
+ const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25164
25279
  /**
25165
25280
  * Creates a callback function for emitting profit events to partialProfitSubject.
25166
25281
  *
@@ -25282,7 +25397,7 @@ class PartialConnectionService {
25282
25397
  * Key format: "signalId:backtest" or "signalId:live"
25283
25398
  * Value: ClientPartial instance with logger and event emitters
25284
25399
  */
25285
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
25400
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
25286
25401
  return new ClientPartial({
25287
25402
  signalId,
25288
25403
  logger: this.loggerService,
@@ -25372,7 +25487,7 @@ class PartialConnectionService {
25372
25487
  const partial = this.getPartial(data.id, backtest);
25373
25488
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25374
25489
  await partial.clear(symbol, data, priceClose, backtest);
25375
- const key = CREATE_KEY_FN$h(data.id, backtest);
25490
+ const key = CREATE_KEY_FN$i(data.id, backtest);
25376
25491
  this.getPartial.clear(key);
25377
25492
  };
25378
25493
  }
@@ -25388,7 +25503,7 @@ class PartialConnectionService {
25388
25503
  * @param backtest - Whether running in backtest mode
25389
25504
  * @returns Unique string key for memoization
25390
25505
  */
25391
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
25506
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
25392
25507
  const parts = [symbol, strategyName, exchangeName];
25393
25508
  if (frameName)
25394
25509
  parts.push(frameName);
@@ -25611,7 +25726,7 @@ class PartialMarkdownService {
25611
25726
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25612
25727
  * Each combination gets its own isolated storage instance.
25613
25728
  */
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));
25729
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25615
25730
  /**
25616
25731
  * Subscribes to partial profit/loss signal emitters to receive events.
25617
25732
  * Protected against multiple subscriptions.
@@ -25821,7 +25936,7 @@ class PartialMarkdownService {
25821
25936
  payload,
25822
25937
  });
25823
25938
  if (payload) {
25824
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25939
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25825
25940
  this.getStorage.clear(key);
25826
25941
  }
25827
25942
  else {
@@ -25837,7 +25952,7 @@ class PartialMarkdownService {
25837
25952
  * @param context - Context with strategyName, exchangeName, frameName
25838
25953
  * @returns Unique string key for memoization
25839
25954
  */
25840
- const CREATE_KEY_FN$f = (context) => {
25955
+ const CREATE_KEY_FN$g = (context) => {
25841
25956
  const parts = [context.strategyName, context.exchangeName];
25842
25957
  if (context.frameName)
25843
25958
  parts.push(context.frameName);
@@ -25911,7 +26026,7 @@ class PartialGlobalService {
25911
26026
  * @param context - Context with strategyName, exchangeName and frameName
25912
26027
  * @param methodName - Name of the calling method for error tracking
25913
26028
  */
25914
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
26029
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
25915
26030
  this.loggerService.log("partialGlobalService validate", {
25916
26031
  context,
25917
26032
  methodName,
@@ -26366,7 +26481,7 @@ class ClientBreakeven {
26366
26481
  * @param backtest - Whether running in backtest mode
26367
26482
  * @returns Unique string key for memoization
26368
26483
  */
26369
- const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26484
+ const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26370
26485
  /**
26371
26486
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26372
26487
  *
@@ -26452,7 +26567,7 @@ class BreakevenConnectionService {
26452
26567
  * Key format: "signalId:backtest" or "signalId:live"
26453
26568
  * Value: ClientBreakeven instance with logger and event emitter
26454
26569
  */
26455
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
26570
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
26456
26571
  return new ClientBreakeven({
26457
26572
  signalId,
26458
26573
  logger: this.loggerService,
@@ -26513,7 +26628,7 @@ class BreakevenConnectionService {
26513
26628
  const breakeven = this.getBreakeven(data.id, backtest);
26514
26629
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26515
26630
  await breakeven.clear(symbol, data, priceClose, backtest);
26516
- const key = CREATE_KEY_FN$e(data.id, backtest);
26631
+ const key = CREATE_KEY_FN$f(data.id, backtest);
26517
26632
  this.getBreakeven.clear(key);
26518
26633
  };
26519
26634
  }
@@ -26529,7 +26644,7 @@ class BreakevenConnectionService {
26529
26644
  * @param backtest - Whether running in backtest mode
26530
26645
  * @returns Unique string key for memoization
26531
26646
  */
26532
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
26647
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
26533
26648
  const parts = [symbol, strategyName, exchangeName];
26534
26649
  if (frameName)
26535
26650
  parts.push(frameName);
@@ -26704,7 +26819,7 @@ class BreakevenMarkdownService {
26704
26819
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26705
26820
  * Each combination gets its own isolated storage instance.
26706
26821
  */
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));
26822
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26708
26823
  /**
26709
26824
  * Subscribes to breakeven signal emitter to receive events.
26710
26825
  * Protected against multiple subscriptions.
@@ -26893,7 +27008,7 @@ class BreakevenMarkdownService {
26893
27008
  payload,
26894
27009
  });
26895
27010
  if (payload) {
26896
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27011
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26897
27012
  this.getStorage.clear(key);
26898
27013
  }
26899
27014
  else {
@@ -26909,7 +27024,7 @@ class BreakevenMarkdownService {
26909
27024
  * @param context - Context with strategyName, exchangeName, frameName
26910
27025
  * @returns Unique string key for memoization
26911
27026
  */
26912
- const CREATE_KEY_FN$c = (context) => {
27027
+ const CREATE_KEY_FN$d = (context) => {
26913
27028
  const parts = [context.strategyName, context.exchangeName];
26914
27029
  if (context.frameName)
26915
27030
  parts.push(context.frameName);
@@ -26983,7 +27098,7 @@ class BreakevenGlobalService {
26983
27098
  * @param context - Context with strategyName, exchangeName and frameName
26984
27099
  * @param methodName - Name of the calling method for error tracking
26985
27100
  */
26986
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
27101
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
26987
27102
  this.loggerService.log("breakevenGlobalService validate", {
26988
27103
  context,
26989
27104
  methodName,
@@ -27204,7 +27319,7 @@ class ConfigValidationService {
27204
27319
  * @param backtest - Whether running in backtest mode
27205
27320
  * @returns Unique string key for memoization
27206
27321
  */
27207
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
27322
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
27208
27323
  const parts = [symbol, strategyName, exchangeName];
27209
27324
  if (frameName)
27210
27325
  parts.push(frameName);
@@ -27371,7 +27486,7 @@ class RiskMarkdownService {
27371
27486
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27372
27487
  * Each combination gets its own isolated storage instance.
27373
27488
  */
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));
27489
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27375
27490
  /**
27376
27491
  * Subscribes to risk rejection emitter to receive rejection events.
27377
27492
  * Protected against multiple subscriptions.
@@ -27560,7 +27675,7 @@ class RiskMarkdownService {
27560
27675
  payload,
27561
27676
  });
27562
27677
  if (payload) {
27563
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27678
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27564
27679
  this.getStorage.clear(key);
27565
27680
  }
27566
27681
  else {
@@ -29942,7 +30057,7 @@ class HighestProfitReportService {
29942
30057
  * @returns Colon-separated key string for memoization
29943
30058
  * @internal
29944
30059
  */
29945
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30060
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
29946
30061
  const parts = [symbol, strategyName, exchangeName];
29947
30062
  if (frameName)
29948
30063
  parts.push(frameName);
@@ -30184,7 +30299,7 @@ class StrategyMarkdownService {
30184
30299
  *
30185
30300
  * @internal
30186
30301
  */
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));
30302
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30188
30303
  /**
30189
30304
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30190
30305
  *
@@ -30758,7 +30873,7 @@ class StrategyMarkdownService {
30758
30873
  this.clear = async (payload) => {
30759
30874
  this.loggerService.log("strategyMarkdownService clear", { payload });
30760
30875
  if (payload) {
30761
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30876
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30762
30877
  this.getStorage.clear(key);
30763
30878
  }
30764
30879
  else {
@@ -30866,7 +30981,7 @@ class StrategyMarkdownService {
30866
30981
  * Creates a unique key for memoizing ReportStorage instances.
30867
30982
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30868
30983
  */
30869
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30984
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30870
30985
  const parts = [symbol, strategyName, exchangeName];
30871
30986
  if (frameName)
30872
30987
  parts.push(frameName);
@@ -31059,7 +31174,7 @@ let ReportStorage$2 = class ReportStorage {
31059
31174
  class SyncMarkdownService {
31060
31175
  constructor() {
31061
31176
  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));
31177
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31063
31178
  /**
31064
31179
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31065
31180
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31255,7 +31370,7 @@ class SyncMarkdownService {
31255
31370
  this.clear = async (payload) => {
31256
31371
  this.loggerService.log("syncMarkdownService clear", { payload });
31257
31372
  if (payload) {
31258
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31373
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31259
31374
  this.getStorage.clear(key);
31260
31375
  }
31261
31376
  else {
@@ -31268,7 +31383,7 @@ class SyncMarkdownService {
31268
31383
  /**
31269
31384
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31270
31385
  */
31271
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31386
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31272
31387
  const parts = [symbol, strategyName, exchangeName];
31273
31388
  if (frameName)
31274
31389
  parts.push(frameName);
@@ -31444,7 +31559,7 @@ let ReportStorage$1 = class ReportStorage {
31444
31559
  class HighestProfitMarkdownService {
31445
31560
  constructor() {
31446
31561
  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));
31562
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31448
31563
  /**
31449
31564
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31450
31565
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31610,7 +31725,7 @@ class HighestProfitMarkdownService {
31610
31725
  this.clear = async (payload) => {
31611
31726
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31612
31727
  if (payload) {
31613
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31728
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31614
31729
  this.getStorage.clear(key);
31615
31730
  }
31616
31731
  else {
@@ -31632,7 +31747,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31632
31747
  * @param backtest - Whether running in backtest mode
31633
31748
  * @returns Unique string key for memoization
31634
31749
  */
31635
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31750
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31636
31751
  const parts = [symbol, strategyName, exchangeName];
31637
31752
  if (frameName)
31638
31753
  parts.push(frameName);
@@ -31675,7 +31790,7 @@ class PriceMetaService {
31675
31790
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31676
31791
  * Instances are cached until clear() is called.
31677
31792
  */
31678
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31793
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31679
31794
  /**
31680
31795
  * Returns the current market price for the given symbol and context.
31681
31796
  *
@@ -31704,10 +31819,10 @@ class PriceMetaService {
31704
31819
  if (source.data) {
31705
31820
  return source.data;
31706
31821
  }
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...`);
31822
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31708
31823
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31709
31824
  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)}`);
31825
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31711
31826
  }
31712
31827
  return currentPrice;
31713
31828
  };
@@ -31749,7 +31864,7 @@ class PriceMetaService {
31749
31864
  this.getSource.clear();
31750
31865
  return;
31751
31866
  }
31752
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31867
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31753
31868
  this.getSource.clear(key);
31754
31869
  };
31755
31870
  }
@@ -31767,7 +31882,7 @@ const LISTEN_TIMEOUT = 120000;
31767
31882
  * @param backtest - Whether running in backtest mode
31768
31883
  * @returns Unique string key for memoization
31769
31884
  */
31770
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31885
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31771
31886
  const parts = [symbol, strategyName, exchangeName];
31772
31887
  if (frameName)
31773
31888
  parts.push(frameName);
@@ -31810,7 +31925,7 @@ class TimeMetaService {
31810
31925
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31811
31926
  * Instances are cached until clear() is called.
31812
31927
  */
31813
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31928
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31814
31929
  /**
31815
31930
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31816
31931
  *
@@ -31838,10 +31953,10 @@ class TimeMetaService {
31838
31953
  if (source.data) {
31839
31954
  return source.data;
31840
31955
  }
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...`);
31956
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31842
31957
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31843
31958
  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)}`);
31959
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31845
31960
  }
31846
31961
  return timestamp;
31847
31962
  };
@@ -31883,7 +31998,7 @@ class TimeMetaService {
31883
31998
  this.getSource.clear();
31884
31999
  return;
31885
32000
  }
31886
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32001
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31887
32002
  this.getSource.clear(key);
31888
32003
  };
31889
32004
  }
@@ -31979,7 +32094,7 @@ class MaxDrawdownReportService {
31979
32094
  /**
31980
32095
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31981
32096
  */
31982
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32097
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31983
32098
  const parts = [symbol, strategyName, exchangeName];
31984
32099
  if (frameName)
31985
32100
  parts.push(frameName);
@@ -32103,7 +32218,7 @@ class ReportStorage {
32103
32218
  class MaxDrawdownMarkdownService {
32104
32219
  constructor() {
32105
32220
  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));
32221
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32107
32222
  /**
32108
32223
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32109
32224
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32182,7 +32297,7 @@ class MaxDrawdownMarkdownService {
32182
32297
  this.clear = async (payload) => {
32183
32298
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32184
32299
  if (payload) {
32185
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32300
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32186
32301
  this.getStorage.clear(key);
32187
32302
  }
32188
32303
  else {
@@ -45248,6 +45363,503 @@ async function listRiskSchema() {
45248
45363
  return await backtest.riskValidationService.list();
45249
45364
  }
45250
45365
 
45366
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
45367
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
45368
+ const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
45369
+ const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
45370
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
45371
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
45372
+ const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
45373
+ const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
45374
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
45375
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
45376
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
45377
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
45378
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
45379
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
45380
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
45381
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
45382
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
45383
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
45384
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
45385
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45386
+ const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45387
+ const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45388
+ const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
45389
+ /**
45390
+ * Builds a composite storage key from context parts.
45391
+ * Includes backtest flag as the last segment to prevent live/backtest collisions.
45392
+ * @param symbol - Trading pair symbol
45393
+ * @param strategyName - Strategy identifier
45394
+ * @param exchangeName - Exchange identifier
45395
+ * @param frameName - Frame identifier
45396
+ * @param backtest - Flag indicating if the context is backtest or live
45397
+ * @returns Composite key string
45398
+ */
45399
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
45400
+ const parts = [symbol, strategyName, exchangeName];
45401
+ if (frameName)
45402
+ parts.push(frameName);
45403
+ parts.push(backtest ? "backtest" : "live");
45404
+ return parts.join(":");
45405
+ };
45406
+ /**
45407
+ * Persistent storage adapter for backtest recent signals.
45408
+ *
45409
+ * Features:
45410
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45411
+ * - Handles active ping events only
45412
+ *
45413
+ * Use this adapter for backtest recent signal persistence across sessions.
45414
+ */
45415
+ class RecentPersistBacktestUtils {
45416
+ constructor() {
45417
+ /**
45418
+ * Handles active ping event.
45419
+ * Persists the latest signal to disk via PersistRecentAdapter.
45420
+ * @param event - Active ping contract with signal data and backtest flag
45421
+ */
45422
+ this.handleActivePing = async (event) => {
45423
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45424
+ signalId: event.data.id,
45425
+ });
45426
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45427
+ };
45428
+ /**
45429
+ * Retrieves the latest persisted signal for the given context.
45430
+ * @param symbol - Trading pair symbol
45431
+ * @param strategyName - Strategy identifier
45432
+ * @param exchangeName - Exchange identifier
45433
+ * @param frameName - Frame identifier
45434
+ * @param backtest - Flag indicating if the context is backtest or live
45435
+ * @returns The latest signal or null if not found
45436
+ */
45437
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45438
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
45439
+ symbol,
45440
+ strategyName,
45441
+ exchangeName,
45442
+ frameName,
45443
+ backtest: backtest$1,
45444
+ });
45445
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45446
+ };
45447
+ }
45448
+ }
45449
+ /**
45450
+ * In-memory storage adapter for backtest recent signals.
45451
+ *
45452
+ * Features:
45453
+ * - Stores the latest active signal per context key in memory only
45454
+ * - Fast read/write operations
45455
+ * - Data is lost when application restarts
45456
+ *
45457
+ * Use this adapter for testing or when persistence is not required.
45458
+ */
45459
+ class RecentMemoryBacktestUtils {
45460
+ constructor() {
45461
+ /** Map of composite context keys to the latest signal */
45462
+ this._signals = new Map();
45463
+ /**
45464
+ * Handles active ping event.
45465
+ * Stores the latest signal in memory under the composite context key.
45466
+ * @param event - Active ping contract with signal data and backtest flag
45467
+ */
45468
+ this.handleActivePing = async (event) => {
45469
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45470
+ signalId: event.data.id,
45471
+ });
45472
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45473
+ this._signals.set(key, event.data);
45474
+ };
45475
+ /**
45476
+ * Retrieves the latest in-memory signal for the given context.
45477
+ * @param symbol - Trading pair symbol
45478
+ * @param strategyName - Strategy identifier
45479
+ * @param exchangeName - Exchange identifier
45480
+ * @param frameName - Frame identifier
45481
+ * @param backtest - Flag indicating if the context is backtest or live
45482
+ * @returns The latest signal or null if not found
45483
+ */
45484
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45485
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45486
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45487
+ return this._signals.get(key) ?? null;
45488
+ };
45489
+ }
45490
+ }
45491
+ /**
45492
+ * Persistent storage adapter for live recent signals.
45493
+ *
45494
+ * Features:
45495
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45496
+ * - Handles active ping events only
45497
+ *
45498
+ * Use this adapter (default) for live recent signal persistence across sessions.
45499
+ */
45500
+ class RecentPersistLiveUtils {
45501
+ constructor() {
45502
+ /**
45503
+ * Handles active ping event.
45504
+ * Persists the latest signal to disk via PersistRecentAdapter.
45505
+ * @param event - Active ping contract with signal data and backtest flag
45506
+ */
45507
+ this.handleActivePing = async (event) => {
45508
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45509
+ signalId: event.data.id,
45510
+ });
45511
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45512
+ };
45513
+ /**
45514
+ * Retrieves the latest persisted signal for the given context.
45515
+ * @param symbol - Trading pair symbol
45516
+ * @param strategyName - Strategy identifier
45517
+ * @param exchangeName - Exchange identifier
45518
+ * @param frameName - Frame identifier
45519
+ * @param backtest - Flag indicating if the context is backtest or live
45520
+ * @returns The latest signal or null if not found
45521
+ */
45522
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45523
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
45524
+ symbol,
45525
+ strategyName,
45526
+ exchangeName,
45527
+ frameName,
45528
+ backtest: backtest$1,
45529
+ });
45530
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45531
+ };
45532
+ }
45533
+ }
45534
+ /**
45535
+ * In-memory storage adapter for live recent signals.
45536
+ *
45537
+ * Features:
45538
+ * - Stores the latest active signal per context key in memory only
45539
+ * - Fast read/write operations
45540
+ * - Data is lost when application restarts
45541
+ *
45542
+ * Use this adapter for testing or when persistence is not required.
45543
+ */
45544
+ class RecentMemoryLiveUtils {
45545
+ constructor() {
45546
+ /** Map of composite context keys to the latest signal */
45547
+ this._signals = new Map();
45548
+ /**
45549
+ * Handles active ping event.
45550
+ * Stores the latest signal in memory under the composite context key.
45551
+ * @param event - Active ping contract with signal data and backtest flag
45552
+ */
45553
+ this.handleActivePing = async (event) => {
45554
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45555
+ signalId: event.data.id,
45556
+ });
45557
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45558
+ this._signals.set(key, event.data);
45559
+ };
45560
+ /**
45561
+ * Retrieves the latest in-memory signal for the given context.
45562
+ * @param symbol - Trading pair symbol
45563
+ * @param strategyName - Strategy identifier
45564
+ * @param exchangeName - Exchange identifier
45565
+ * @param frameName - Frame identifier
45566
+ * @param backtest - Flag indicating if the context is backtest or live
45567
+ * @returns The latest signal or null if not found
45568
+ */
45569
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45570
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45571
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45572
+ return this._signals.get(key) ?? null;
45573
+ };
45574
+ }
45575
+ }
45576
+ /**
45577
+ * Backtest recent signal adapter with pluggable storage backend.
45578
+ *
45579
+ * Features:
45580
+ * - Adapter pattern for swappable storage implementations
45581
+ * - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
45582
+ * - Alternative adapter: RecentPersistBacktestUtils
45583
+ * - Convenience methods: usePersist(), useMemory()
45584
+ */
45585
+ class RecentBacktestAdapter {
45586
+ constructor() {
45587
+ /** Internal storage utils instance */
45588
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45589
+ /**
45590
+ * Handles active ping event.
45591
+ * Proxies call to the underlying storage adapter.
45592
+ * @param event - Active ping contract with signal data
45593
+ */
45594
+ this.handleActivePing = async (event) => {
45595
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45596
+ signalId: event.data.id,
45597
+ });
45598
+ return await this._recentBacktestUtils.handleActivePing(event);
45599
+ };
45600
+ /**
45601
+ * Retrieves the latest signal for the given context.
45602
+ * Proxies call to the underlying storage adapter.
45603
+ * @param symbol - Trading pair symbol
45604
+ * @param strategyName - Strategy identifier
45605
+ * @param exchangeName - Exchange identifier
45606
+ * @param frameName - Frame identifier
45607
+ * @param backtest - Flag indicating if the context is backtest or live
45608
+ * @returns The latest signal or null if not found
45609
+ */
45610
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45611
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45612
+ symbol,
45613
+ strategyName,
45614
+ exchangeName,
45615
+ frameName,
45616
+ backtest: backtest$1,
45617
+ });
45618
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45619
+ };
45620
+ /**
45621
+ * Sets the storage adapter constructor.
45622
+ * All future storage operations will use this adapter.
45623
+ * @param Ctor - Constructor for recent adapter
45624
+ */
45625
+ this.useRecentAdapter = (Ctor) => {
45626
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
45627
+ this._recentBacktestUtils = Reflect.construct(Ctor, []);
45628
+ };
45629
+ /**
45630
+ * Switches to persistent storage adapter.
45631
+ * Signals will be persisted to disk.
45632
+ */
45633
+ this.usePersist = () => {
45634
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
45635
+ this._recentBacktestUtils = new RecentPersistBacktestUtils();
45636
+ };
45637
+ /**
45638
+ * Switches to in-memory storage adapter (default).
45639
+ * Signals will be stored in memory only.
45640
+ */
45641
+ this.useMemory = () => {
45642
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
45643
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45644
+ };
45645
+ /**
45646
+ * Clears the cached utils instance by resetting to the default in-memory adapter.
45647
+ */
45648
+ this.clear = () => {
45649
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
45650
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45651
+ };
45652
+ }
45653
+ }
45654
+ /**
45655
+ * Live recent signal adapter with pluggable storage backend.
45656
+ *
45657
+ * Features:
45658
+ * - Adapter pattern for swappable storage implementations
45659
+ * - Default adapter: RecentPersistLiveUtils (persistent storage)
45660
+ * - Alternative adapter: RecentMemoryLiveUtils
45661
+ * - Convenience methods: usePersist(), useMemory()
45662
+ */
45663
+ class RecentLiveAdapter {
45664
+ constructor() {
45665
+ /** Internal storage utils instance */
45666
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45667
+ /**
45668
+ * Handles active ping event.
45669
+ * Proxies call to the underlying storage adapter.
45670
+ * @param event - Active ping contract with signal data
45671
+ */
45672
+ this.handleActivePing = async (event) => {
45673
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45674
+ signalId: event.data.id,
45675
+ });
45676
+ return await this._recentLiveUtils.handleActivePing(event);
45677
+ };
45678
+ /**
45679
+ * Retrieves the latest signal for the given context.
45680
+ * Proxies call to the underlying storage adapter.
45681
+ * @param symbol - Trading pair symbol
45682
+ * @param strategyName - Strategy identifier
45683
+ * @param exchangeName - Exchange identifier
45684
+ * @param frameName - Frame identifier
45685
+ * @param backtest - Flag indicating if the context is backtest or live
45686
+ * @returns The latest signal or null if not found
45687
+ */
45688
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45689
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45690
+ symbol,
45691
+ strategyName,
45692
+ exchangeName,
45693
+ frameName,
45694
+ backtest: backtest$1,
45695
+ });
45696
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45697
+ };
45698
+ /**
45699
+ * Sets the storage adapter constructor.
45700
+ * All future storage operations will use this adapter.
45701
+ * @param Ctor - Constructor for recent adapter
45702
+ */
45703
+ this.useRecentAdapter = (Ctor) => {
45704
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
45705
+ this._recentLiveUtils = Reflect.construct(Ctor, []);
45706
+ };
45707
+ /**
45708
+ * Switches to persistent storage adapter (default).
45709
+ * Signals will be persisted to disk.
45710
+ */
45711
+ this.usePersist = () => {
45712
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
45713
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45714
+ };
45715
+ /**
45716
+ * Switches to in-memory storage adapter.
45717
+ * Signals will be stored in memory only.
45718
+ */
45719
+ this.useMemory = () => {
45720
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
45721
+ this._recentLiveUtils = new RecentMemoryLiveUtils();
45722
+ };
45723
+ /**
45724
+ * Clears the cached utils instance by resetting to the default persistent adapter.
45725
+ */
45726
+ this.clear = () => {
45727
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
45728
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45729
+ };
45730
+ }
45731
+ }
45732
+ /**
45733
+ * Main recent signal adapter that manages both backtest and live recent signal storage.
45734
+ *
45735
+ * Features:
45736
+ * - Subscribes to activePingSubject for automatic storage updates
45737
+ * - Provides unified access to the latest signal for any context
45738
+ * - Singleshot enable pattern prevents duplicate subscriptions
45739
+ * - Cleanup function for proper unsubscription
45740
+ */
45741
+ class RecentAdapter {
45742
+ constructor() {
45743
+ /**
45744
+ * Enables recent signal storage by subscribing to activePingSubject.
45745
+ * Uses singleshot to ensure one-time subscription.
45746
+ *
45747
+ * @returns Cleanup function that unsubscribes from all emitters
45748
+ */
45749
+ this.enable = functoolsKit.singleshot(() => {
45750
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
45751
+ let unBacktest;
45752
+ let unLive;
45753
+ {
45754
+ const unBacktestPingActive = activePingSubject
45755
+ .filter(({ backtest }) => backtest)
45756
+ .connect((event) => RecentBacktest.handleActivePing(event));
45757
+ unBacktest = functoolsKit.compose(() => unBacktestPingActive());
45758
+ }
45759
+ {
45760
+ const unLivePingActive = activePingSubject
45761
+ .filter(({ backtest }) => !backtest)
45762
+ .connect((event) => RecentLive.handleActivePing(event));
45763
+ unLive = functoolsKit.compose(() => unLivePingActive());
45764
+ }
45765
+ const unEnable = () => this.enable.clear();
45766
+ return functoolsKit.compose(() => unBacktest(), () => unLive(), () => unEnable());
45767
+ });
45768
+ /**
45769
+ * Disables recent signal storage by unsubscribing from all emitters.
45770
+ * Safe to call multiple times.
45771
+ */
45772
+ this.disable = () => {
45773
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
45774
+ if (this.enable.hasValue()) {
45775
+ const lastSubscription = this.enable();
45776
+ lastSubscription();
45777
+ }
45778
+ };
45779
+ /**
45780
+ * Retrieves the latest active signal for the given symbol and context.
45781
+ * Searches backtest storage first, then live storage.
45782
+ *
45783
+ * @param symbol - Trading pair symbol
45784
+ * @param context - Execution context with strategyName, exchangeName, and frameName
45785
+ * @param backtest - Flag indicating if the context is backtest or live
45786
+ * @returns The latest signal or null if not found
45787
+ * @throws Error if RecentAdapter is not enabled
45788
+ */
45789
+ this.getLatestSignal = async (symbol, context) => {
45790
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45791
+ symbol,
45792
+ context,
45793
+ });
45794
+ if (!this.enable.hasValue()) {
45795
+ throw new Error("RecentAdapter is not enabled. Call enable() first.");
45796
+ }
45797
+ let result = null;
45798
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
45799
+ return result;
45800
+ }
45801
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
45802
+ return result;
45803
+ }
45804
+ return null;
45805
+ };
45806
+ }
45807
+ }
45808
+ /**
45809
+ * Global singleton instance of RecentAdapter.
45810
+ * Provides unified recent signal management for backtest and live trading.
45811
+ */
45812
+ const Recent = new RecentAdapter();
45813
+ /**
45814
+ * Global singleton instance of RecentLiveAdapter.
45815
+ * Provides live trading recent signal storage with pluggable backends.
45816
+ */
45817
+ const RecentLive = new RecentLiveAdapter();
45818
+ /**
45819
+ * Global singleton instance of RecentBacktestAdapter.
45820
+ * Provides backtest recent signal storage with pluggable backends.
45821
+ */
45822
+ const RecentBacktest = new RecentBacktestAdapter();
45823
+
45824
+ const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
45825
+ /**
45826
+ * Returns the latest signal (pending or closed) for the current strategy context.
45827
+ *
45828
+ * Does not distinguish between active and closed signals — returns whichever
45829
+ * was recorded last. Useful for cooldown logic: e.g. skip opening a new position
45830
+ * for 4 hours after a stop-loss by checking the timestamp of the latest signal
45831
+ * regardless of its outcome.
45832
+ *
45833
+ * Searches backtest storage first, then live storage.
45834
+ * Returns null if no signal exists at all.
45835
+ *
45836
+ * Automatically detects backtest/live mode from execution context.
45837
+ *
45838
+ * @param symbol - Trading pair symbol
45839
+ * @returns Promise resolving to the latest signal or null
45840
+ *
45841
+ * @example
45842
+ * ```typescript
45843
+ * import { getLatestSignal } from "backtest-kit";
45844
+ *
45845
+ * const latest = await getLatestSignal("BTCUSDT");
45846
+ * if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
45847
+ * return; // cooldown after SL — skip new signal for 4 hours
45848
+ * }
45849
+ * ```
45850
+ */
45851
+ async function getLatestSignal(symbol) {
45852
+ backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
45853
+ if (!ExecutionContextService.hasContext()) {
45854
+ throw new Error("getLatestSignal requires an execution context");
45855
+ }
45856
+ if (!MethodContextService.hasContext()) {
45857
+ throw new Error("getLatestSignal requires a method context");
45858
+ }
45859
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
45860
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
45861
+ }
45862
+
45251
45863
  const DEFAULT_BM25_K1 = 1.5;
45252
45864
  const DEFAULT_BM25_B = 0.75;
45253
45865
  const DEFAULT_BM25_SCORE = 0.5;
@@ -48498,6 +49110,67 @@ class LogAdapter {
48498
49110
  */
48499
49111
  const Log = new LogAdapter();
48500
49112
 
49113
+ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49114
+ /** List of all global subjects whose listeners should be snapshotted for session isolation */
49115
+ const SUBJECT_ISOLATION_LIST = [
49116
+ activePingSubject,
49117
+ backtestScheduleOpenSubject,
49118
+ breakevenSubject,
49119
+ doneBacktestSubject,
49120
+ doneLiveSubject,
49121
+ errorEmitter,
49122
+ exitEmitter,
49123
+ highestProfitSubject,
49124
+ maxDrawdownSubject,
49125
+ partialLossSubject,
49126
+ partialProfitSubject,
49127
+ performanceEmitter,
49128
+ progressBacktestEmitter,
49129
+ riskSubject,
49130
+ schedulePingSubject,
49131
+ shutdownEmitter,
49132
+ signalBacktestEmitter,
49133
+ signalEmitter,
49134
+ signalLiveEmitter,
49135
+ strategyCommitSubject,
49136
+ syncSubject,
49137
+ validationSubject,
49138
+ ];
49139
+ /**
49140
+ * Creates a snapshot function for a given subject by clearing its internal
49141
+ * events map and returning a restore function that can put the original listeners back.
49142
+ * @param subject The subject to snapshot
49143
+ * @returns A function that restores the subject's original listeners when called
49144
+ */
49145
+ const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
49146
+ const emitter = subject["_emitter"];
49147
+ const events = emitter["_events"];
49148
+ emitter["_events"] = {};
49149
+ return () => {
49150
+ emitter["_events"] = events;
49151
+ };
49152
+ };
49153
+ /**
49154
+ * Manages isolation of global event-bus state between backtest sessions.
49155
+ * Allows temporarily detaching all subject subscriptions so that one session
49156
+ * does not interfere with another, then restoring them afterwards.
49157
+ */
49158
+ class SessionUtils {
49159
+ constructor() {
49160
+ /**
49161
+ * Snapshots the current listener state of every global subject by replacing
49162
+ * their internal `_events` map with an empty object.
49163
+ * @returns A restore function that, when called, puts all original listeners back.
49164
+ */
49165
+ this.createSnapshot = () => {
49166
+ backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
49167
+ const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
49168
+ return functoolsKit.compose(...snapshotList);
49169
+ };
49170
+ }
49171
+ }
49172
+ const Session = new SessionUtils();
49173
+
48501
49174
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
48502
49175
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
48503
49176
  const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
@@ -54735,12 +55408,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
54735
55408
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
54736
55409
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
54737
55410
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
55411
+ const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
54738
55412
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
54739
55413
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
55414
+ const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
54740
55415
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
54741
55416
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
54742
55417
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
54743
55418
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
55419
+ const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
54744
55420
  const MS_PER_MINUTE$1 = 60000;
54745
55421
  const INTERVAL_MINUTES$1 = {
54746
55422
  "1m": 1,
@@ -54930,6 +55606,36 @@ class CacheFnInstance {
54930
55606
  }
54931
55607
  }
54932
55608
  };
55609
+ /**
55610
+ * Check whether a valid (non-expired) cache entry exists for the current context and arguments.
55611
+ *
55612
+ * Returns `true` if a cached value exists and its interval is still current.
55613
+ * Returns `false` if there is no entry or the cached entry has expired.
55614
+ *
55615
+ * Requires active execution context and method context.
55616
+ *
55617
+ * @param args - Arguments to look up in the cache
55618
+ * @returns `true` if a fresh cached value exists, `false` otherwise
55619
+ */
55620
+ this.hasValue = (...args) => {
55621
+ if (!MethodContextService.hasContext()) {
55622
+ throw new Error("CacheFnInstance hasValue requires method context");
55623
+ }
55624
+ if (!ExecutionContextService.hasContext()) {
55625
+ throw new Error("CacheFnInstance hasValue requires execution context");
55626
+ }
55627
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
55628
+ const argKey = String(this.key(args));
55629
+ const key = `${contextKey}:${argKey}`;
55630
+ const cached = this._cacheMap.get(key);
55631
+ if (!cached) {
55632
+ return false;
55633
+ }
55634
+ const currentWhen = backtest.executionContextService.context.when;
55635
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
55636
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
55637
+ return currentAligned === cachedAligned;
55638
+ };
54933
55639
  /**
54934
55640
  * Garbage collect expired cache entries.
54935
55641
  *
@@ -55054,6 +55760,33 @@ class CacheFileInstance {
55054
55760
  await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
55055
55761
  return result;
55056
55762
  };
55763
+ /**
55764
+ * Check whether a cached value exists on disk for the given arguments and current interval.
55765
+ *
55766
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
55767
+ * Returns `false` if no record is found.
55768
+ *
55769
+ * Requires active execution context and method context.
55770
+ *
55771
+ * @param args - Arguments forwarded to the key generator
55772
+ * @returns `true` if a cached record exists, `false` otherwise
55773
+ */
55774
+ this.hasValue = async (...args) => {
55775
+ backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
55776
+ if (!MethodContextService.hasContext()) {
55777
+ throw new Error("CacheFileInstance hasValue requires method context");
55778
+ }
55779
+ if (!ExecutionContextService.hasContext()) {
55780
+ throw new Error("CacheFileInstance hasValue requires execution context");
55781
+ }
55782
+ const [symbol, ...rest] = args;
55783
+ const { when } = backtest.executionContextService.context;
55784
+ const alignedTs = align$1(when.getTime(), this.interval);
55785
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
55786
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
55787
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
55788
+ return cached !== null;
55789
+ };
55057
55790
  /**
55058
55791
  * Soft-delete all persisted records for this instance's bucket.
55059
55792
  * After this call the next `run()` will recompute and re-cache the value.
@@ -55140,23 +55873,30 @@ class CacheUtils {
55140
55873
  wrappedFn.clear = () => {
55141
55874
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
55142
55875
  if (!MethodContextService.hasContext()) {
55143
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55144
- return;
55876
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
55145
55877
  }
55146
55878
  if (!ExecutionContextService.hasContext()) {
55147
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55148
- return;
55879
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
55149
55880
  }
55150
55881
  this._getFnInstance.get(run)?.clear();
55151
55882
  };
55152
55883
  wrappedFn.gc = () => {
55153
55884
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
55154
55885
  if (!ExecutionContextService.hasContext()) {
55155
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_GC} called without execution context, skipping`);
55156
- return;
55886
+ throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
55157
55887
  }
55158
55888
  return this._getFnInstance.get(run)?.gc();
55159
55889
  };
55890
+ wrappedFn.hasValue = (...args) => {
55891
+ backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
55892
+ if (!MethodContextService.hasContext()) {
55893
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
55894
+ }
55895
+ if (!ExecutionContextService.hasContext()) {
55896
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
55897
+ }
55898
+ return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
55899
+ };
55160
55900
  return wrappedFn;
55161
55901
  };
55162
55902
  /**
@@ -55208,8 +55948,25 @@ class CacheUtils {
55208
55948
  };
55209
55949
  wrappedFn.clear = async () => {
55210
55950
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
55951
+ if (!MethodContextService.hasContext()) {
55952
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
55953
+ }
55954
+ if (!ExecutionContextService.hasContext()) {
55955
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
55956
+ }
55211
55957
  await this._getFileInstance.get(run)?.clear();
55212
55958
  };
55959
+ wrappedFn.hasValue = async (...args) => {
55960
+ backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
55961
+ if (!MethodContextService.hasContext()) {
55962
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
55963
+ }
55964
+ if (!ExecutionContextService.hasContext()) {
55965
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
55966
+ }
55967
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
55968
+ return await instance.hasValue(...args);
55969
+ };
55213
55970
  return wrappedFn;
55214
55971
  };
55215
55972
  /**
@@ -55278,11 +56035,14 @@ const Cache = new CacheUtils();
55278
56035
 
55279
56036
  const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
55280
56037
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
56038
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
55281
56039
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
55282
56040
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
55283
56041
  const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
56042
+ const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
55284
56043
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
55285
56044
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
56045
+ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
55286
56046
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
55287
56047
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
55288
56048
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
@@ -55440,6 +56200,31 @@ class IntervalFnInstance {
55440
56200
  }
55441
56201
  }
55442
56202
  };
56203
+ /**
56204
+ * Check whether the function has already fired for the current interval and context.
56205
+ *
56206
+ * Returns `true` if the function fired (non-null result) within the current interval boundary.
56207
+ * Returns `false` if there is no recorded firing for this interval.
56208
+ *
56209
+ * Requires active method context and execution context.
56210
+ *
56211
+ * @param args - Arguments to look up in the state map
56212
+ * @returns `true` if the function has already fired this interval, `false` otherwise
56213
+ */
56214
+ this.hasValue = (...args) => {
56215
+ if (!MethodContextService.hasContext()) {
56216
+ throw new Error("IntervalFnInstance hasValue requires method context");
56217
+ }
56218
+ if (!ExecutionContextService.hasContext()) {
56219
+ throw new Error("IntervalFnInstance hasValue requires execution context");
56220
+ }
56221
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56222
+ const currentWhen = backtest.executionContextService.context.when;
56223
+ const currentAligned = align(currentWhen.getTime(), this.interval);
56224
+ const argKey = this.key(args);
56225
+ const stateKey = `${contextKey}:${argKey}`;
56226
+ return this._stateMap.get(stateKey) === currentAligned;
56227
+ };
55443
56228
  /**
55444
56229
  * Garbage collect expired state entries.
55445
56230
  *
@@ -55561,6 +56346,33 @@ class IntervalFileInstance {
55561
56346
  }
55562
56347
  return result;
55563
56348
  };
56349
+ /**
56350
+ * Check whether the function has already fired for the current interval on disk.
56351
+ *
56352
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56353
+ * Returns `false` if no record is found.
56354
+ *
56355
+ * Requires active execution context and method context.
56356
+ *
56357
+ * @param args - Arguments forwarded to the key generator
56358
+ * @returns `true` if a fired record exists, `false` otherwise
56359
+ */
56360
+ this.hasValue = async (...args) => {
56361
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56362
+ if (!MethodContextService.hasContext()) {
56363
+ throw new Error("IntervalFileInstance hasValue requires method context");
56364
+ }
56365
+ if (!ExecutionContextService.hasContext()) {
56366
+ throw new Error("IntervalFileInstance hasValue requires execution context");
56367
+ }
56368
+ const [symbol, ...rest] = args;
56369
+ const { when } = backtest.executionContextService.context;
56370
+ const alignedMs = align(when.getTime(), this.interval);
56371
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56372
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
56373
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
56374
+ return cached !== null;
56375
+ };
55564
56376
  /**
55565
56377
  * Soft-delete all persisted records for this instance's bucket.
55566
56378
  * After this call the function will fire again on the next `run()`.
@@ -55641,23 +56453,30 @@ class IntervalUtils {
55641
56453
  wrappedFn.clear = () => {
55642
56454
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
55643
56455
  if (!MethodContextService.hasContext()) {
55644
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
55645
- return;
56456
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
55646
56457
  }
55647
56458
  if (!ExecutionContextService.hasContext()) {
55648
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
55649
- return;
56459
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
55650
56460
  }
55651
56461
  this._getInstance.get(run)?.clear();
55652
56462
  };
55653
56463
  wrappedFn.gc = () => {
55654
56464
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
55655
56465
  if (!ExecutionContextService.hasContext()) {
55656
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
55657
- return;
56466
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
55658
56467
  }
55659
56468
  return this._getInstance.get(run)?.gc();
55660
56469
  };
56470
+ wrappedFn.hasValue = (...args) => {
56471
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
56472
+ if (!MethodContextService.hasContext()) {
56473
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56474
+ }
56475
+ if (!ExecutionContextService.hasContext()) {
56476
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56477
+ }
56478
+ return this._getInstance.get(run)?.hasValue(...args) ?? false;
56479
+ };
55661
56480
  return wrappedFn;
55662
56481
  };
55663
56482
  /**
@@ -55700,8 +56519,25 @@ class IntervalUtils {
55700
56519
  };
55701
56520
  wrappedFn.clear = async () => {
55702
56521
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
56522
+ if (!MethodContextService.hasContext()) {
56523
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
56524
+ }
56525
+ if (!ExecutionContextService.hasContext()) {
56526
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
56527
+ }
55703
56528
  await this._getFileInstance.get(run)?.clear();
55704
56529
  };
56530
+ wrappedFn.hasValue = async (...args) => {
56531
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
56532
+ if (!MethodContextService.hasContext()) {
56533
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56534
+ }
56535
+ if (!ExecutionContextService.hasContext()) {
56536
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56537
+ }
56538
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56539
+ return await instance.hasValue(...args);
56540
+ };
55705
56541
  return wrappedFn;
55706
56542
  };
55707
56543
  /**
@@ -56911,18 +57747,23 @@ exports.PersistMeasureAdapter = PersistMeasureAdapter;
56911
57747
  exports.PersistMemoryAdapter = PersistMemoryAdapter;
56912
57748
  exports.PersistNotificationAdapter = PersistNotificationAdapter;
56913
57749
  exports.PersistPartialAdapter = PersistPartialAdapter;
57750
+ exports.PersistRecentAdapter = PersistRecentAdapter;
56914
57751
  exports.PersistRiskAdapter = PersistRiskAdapter;
56915
57752
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
56916
57753
  exports.PersistSignalAdapter = PersistSignalAdapter;
56917
57754
  exports.PersistStorageAdapter = PersistStorageAdapter;
56918
57755
  exports.Position = Position;
56919
57756
  exports.PositionSize = PositionSize;
57757
+ exports.Recent = Recent;
57758
+ exports.RecentBacktest = RecentBacktest;
57759
+ exports.RecentLive = RecentLive;
56920
57760
  exports.Reflect = Reflect$1;
56921
57761
  exports.Report = Report;
56922
57762
  exports.ReportBase = ReportBase;
56923
57763
  exports.ReportWriter = ReportWriter;
56924
57764
  exports.Risk = Risk;
56925
57765
  exports.Schedule = Schedule;
57766
+ exports.Session = Session;
56926
57767
  exports.Storage = Storage;
56927
57768
  exports.StorageBacktest = StorageBacktest;
56928
57769
  exports.StorageLive = StorageLive;
@@ -56976,6 +57817,7 @@ exports.getDefaultConfig = getDefaultConfig;
56976
57817
  exports.getEffectivePriceOpen = getEffectivePriceOpen;
56977
57818
  exports.getExchangeSchema = getExchangeSchema;
56978
57819
  exports.getFrameSchema = getFrameSchema;
57820
+ exports.getLatestSignal = getLatestSignal;
56979
57821
  exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
56980
57822
  exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
56981
57823
  exports.getMode = getMode;