backtest-kit 7.3.0 → 7.5.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
@@ -1017,6 +1017,13 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
1017
1017
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
1018
1018
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
1019
1019
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1020
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER = "PersistStateUtils.usePersistStateAdapter";
1021
+ const PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA = "PersistStateUtils.readStateData";
1022
+ const PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA = "PersistStateUtils.writeStateData";
1023
+ const PERSIST_STATE_UTILS_METHOD_NAME_CLEAR = "PersistStateUtils.clear";
1024
+ const PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE = "PersistStateUtils.dispose";
1025
+ const PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT = "PersistStateUtils.waitForInit";
1026
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY = "PersistStateUtils.useDummy";
1020
1027
  const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1021
1028
  const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1022
1029
  const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
@@ -2978,6 +2985,127 @@ class PersistRecentUtils {
2978
2985
  * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2979
2986
  */
2980
2987
  const PersistRecentAdapter = new PersistRecentUtils();
2988
+ /**
2989
+ * Utility class for managing state persistence.
2990
+ *
2991
+ * Features:
2992
+ * - Memoized storage instances per (signalId, bucketName) pair
2993
+ * - Custom adapter support
2994
+ * - Atomic read/write operations
2995
+ *
2996
+ * Storage layout: ./dump/state/<signalId>/<bucketName>.json
2997
+ *
2998
+ * Used by StatePersistInstance for crash-safe state persistence.
2999
+ */
3000
+ class PersistStateUtils {
3001
+ constructor() {
3002
+ this.PersistStateFactory = PersistBase;
3003
+ this.getStateStorage = functoolsKit.memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistStateFactory, [
3004
+ bucketName,
3005
+ `./dump/state/${signalId}/`,
3006
+ ]));
3007
+ /**
3008
+ * Initializes the storage for a given (signalId, bucketName) pair.
3009
+ *
3010
+ * @param signalId - Signal identifier
3011
+ * @param bucketName - Bucket name
3012
+ * @param initial - Whether this is the first initialization
3013
+ */
3014
+ this.waitForInit = async (signalId, bucketName, initial) => {
3015
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
3016
+ signalId,
3017
+ bucketName,
3018
+ initial,
3019
+ });
3020
+ const key = `${signalId}:${bucketName}`;
3021
+ const isInitial = initial && !this.getStateStorage.has(key);
3022
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3023
+ await stateStorage.waitForInit(isInitial);
3024
+ };
3025
+ /**
3026
+ * Reads a state entry from persistence storage.
3027
+ *
3028
+ * @param signalId - Signal identifier
3029
+ * @param bucketName - Bucket name
3030
+ * @returns Promise resolving to entry data or null if not found
3031
+ */
3032
+ this.readStateData = async (signalId, bucketName) => {
3033
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, {
3034
+ signalId,
3035
+ bucketName,
3036
+ });
3037
+ const key = `${signalId}:${bucketName}`;
3038
+ const isInitial = !this.getStateStorage.has(key);
3039
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3040
+ await stateStorage.waitForInit(isInitial);
3041
+ if (await stateStorage.hasValue(bucketName)) {
3042
+ return await stateStorage.readValue(bucketName);
3043
+ }
3044
+ return null;
3045
+ };
3046
+ /**
3047
+ * Writes a state entry to disk with atomic file writes.
3048
+ *
3049
+ * @param data - Entry data to persist
3050
+ * @param signalId - Signal identifier
3051
+ * @param bucketName - Bucket name
3052
+ */
3053
+ this.writeStateData = async (data, signalId, bucketName) => {
3054
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, {
3055
+ signalId,
3056
+ bucketName,
3057
+ });
3058
+ const key = `${signalId}:${bucketName}`;
3059
+ const isInitial = !this.getStateStorage.has(key);
3060
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3061
+ await stateStorage.waitForInit(isInitial);
3062
+ await stateStorage.writeValue(bucketName, data);
3063
+ };
3064
+ /**
3065
+ * Switches to a dummy persist adapter that discards all writes.
3066
+ * All future persistence writes will be no-ops.
3067
+ */
3068
+ this.useDummy = () => {
3069
+ LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
3070
+ this.usePersistStateAdapter(PersistDummy);
3071
+ };
3072
+ /**
3073
+ * Clears the memoized storage cache.
3074
+ * Call this when process.cwd() changes between strategy iterations
3075
+ * so new storage instances are created with the updated base path.
3076
+ */
3077
+ this.clear = () => {
3078
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
3079
+ this.getStateStorage.clear();
3080
+ };
3081
+ /**
3082
+ * Disposes of the state adapter and releases any resources.
3083
+ * Call this when a signal is removed to clean up its associated storage.
3084
+ *
3085
+ * @param signalId - Signal identifier
3086
+ * @param bucketName - Bucket name
3087
+ */
3088
+ this.dispose = (signalId, bucketName) => {
3089
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
3090
+ const key = `${signalId}:${bucketName}`;
3091
+ this.getStateStorage.clear(key);
3092
+ };
3093
+ }
3094
+ /**
3095
+ * Registers a custom persistence adapter.
3096
+ *
3097
+ * @param Ctor - Custom PersistBase constructor
3098
+ */
3099
+ usePersistStateAdapter(Ctor) {
3100
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
3101
+ this.PersistStateFactory = Ctor;
3102
+ }
3103
+ }
3104
+ /**
3105
+ * Global singleton instance of PersistStateUtils.
3106
+ * Used by StatePersistInstance for crash-safe state persistence.
3107
+ */
3108
+ const PersistStateAdapter = new PersistStateUtils();
2981
3109
 
2982
3110
  var _a$2, _b$2;
2983
3111
  const BUSY_DELAY = 100;
@@ -10443,7 +10571,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10443
10571
  * @param backtest - Whether running in backtest mode
10444
10572
  * @returns Unique string key for memoization
10445
10573
  */
10446
- const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10574
+ const CREATE_KEY_FN$w = (symbol, strategyName, exchangeName, frameName, backtest) => {
10447
10575
  const parts = [symbol, strategyName, exchangeName];
10448
10576
  if (frameName)
10449
10577
  parts.push(frameName);
@@ -10743,7 +10871,7 @@ class StrategyConnectionService {
10743
10871
  * @param backtest - Whether running in backtest mode
10744
10872
  * @returns Configured ClientStrategy instance
10745
10873
  */
10746
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10874
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$w(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10747
10875
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10748
10876
  return new ClientStrategy({
10749
10877
  symbol,
@@ -11705,7 +11833,7 @@ class StrategyConnectionService {
11705
11833
  }
11706
11834
  return;
11707
11835
  }
11708
- const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11836
+ const key = CREATE_KEY_FN$w(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11709
11837
  if (!this.getStrategy.has(key)) {
11710
11838
  return;
11711
11839
  }
@@ -12879,7 +13007,7 @@ class ClientRisk {
12879
13007
  * @param backtest - Whether running in backtest mode
12880
13008
  * @returns Unique string key for memoization
12881
13009
  */
12882
- const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
13010
+ const CREATE_KEY_FN$v = (riskName, exchangeName, frameName, backtest) => {
12883
13011
  const parts = [riskName, exchangeName];
12884
13012
  if (frameName)
12885
13013
  parts.push(frameName);
@@ -12979,7 +13107,7 @@ class RiskConnectionService {
12979
13107
  * @param backtest - True if backtest mode, false if live mode
12980
13108
  * @returns Configured ClientRisk instance
12981
13109
  */
12982
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
13110
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12983
13111
  const schema = this.riskSchemaService.get(riskName);
12984
13112
  return new ClientRisk({
12985
13113
  ...schema,
@@ -13048,7 +13176,7 @@ class RiskConnectionService {
13048
13176
  payload,
13049
13177
  });
13050
13178
  if (payload) {
13051
- const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13179
+ const key = CREATE_KEY_FN$v(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13052
13180
  this.getRisk.clear(key);
13053
13181
  }
13054
13182
  else {
@@ -14167,7 +14295,7 @@ class ClientAction {
14167
14295
  * @param backtest - Whether running in backtest mode
14168
14296
  * @returns Unique string key for memoization
14169
14297
  */
14170
- const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
14298
+ const CREATE_KEY_FN$u = (actionName, strategyName, exchangeName, frameName, backtest) => {
14171
14299
  const parts = [actionName, strategyName, exchangeName];
14172
14300
  if (frameName)
14173
14301
  parts.push(frameName);
@@ -14219,7 +14347,7 @@ class ActionConnectionService {
14219
14347
  * @param backtest - True if backtest mode, false if live mode
14220
14348
  * @returns Configured ClientAction instance
14221
14349
  */
14222
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14350
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14223
14351
  const schema = this.actionSchemaService.get(actionName);
14224
14352
  return new ClientAction({
14225
14353
  ...schema,
@@ -14445,7 +14573,7 @@ class ActionConnectionService {
14445
14573
  await Promise.all(actions.map(async (action) => await action.dispose()));
14446
14574
  return;
14447
14575
  }
14448
- const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14576
+ const key = CREATE_KEY_FN$u(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14449
14577
  if (!this.getAction.has(key)) {
14450
14578
  return;
14451
14579
  }
@@ -14463,7 +14591,7 @@ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14463
14591
  * @param exchangeName - Exchange name
14464
14592
  * @returns Unique string key for memoization
14465
14593
  */
14466
- const CREATE_KEY_FN$s = (exchangeName) => {
14594
+ const CREATE_KEY_FN$t = (exchangeName) => {
14467
14595
  return exchangeName;
14468
14596
  };
14469
14597
  /**
@@ -14487,7 +14615,7 @@ class ExchangeCoreService {
14487
14615
  * @param exchangeName - Name of the exchange to validate
14488
14616
  * @returns Promise that resolves when validation is complete
14489
14617
  */
14490
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14618
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$t(exchangeName), async (exchangeName) => {
14491
14619
  this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14492
14620
  exchangeName,
14493
14621
  });
@@ -14739,7 +14867,7 @@ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14739
14867
  * @param context - Execution context with strategyName, exchangeName, frameName
14740
14868
  * @returns Unique string key for memoization
14741
14869
  */
14742
- const CREATE_KEY_FN$r = (context) => {
14870
+ const CREATE_KEY_FN$s = (context) => {
14743
14871
  const parts = [context.strategyName, context.exchangeName];
14744
14872
  if (context.frameName)
14745
14873
  parts.push(context.frameName);
@@ -14771,7 +14899,7 @@ class StrategyCoreService {
14771
14899
  * @param context - Execution context with strategyName, exchangeName, frameName
14772
14900
  * @returns Promise that resolves when validation is complete
14773
14901
  */
14774
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14902
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$s(context), async (context) => {
14775
14903
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14776
14904
  context,
14777
14905
  });
@@ -16141,7 +16269,7 @@ class SizingGlobalService {
16141
16269
  * @param context - Context with riskName, exchangeName, frameName
16142
16270
  * @returns Unique string key for memoization
16143
16271
  */
16144
- const CREATE_KEY_FN$q = (context) => {
16272
+ const CREATE_KEY_FN$r = (context) => {
16145
16273
  const parts = [context.riskName, context.exchangeName];
16146
16274
  if (context.frameName)
16147
16275
  parts.push(context.frameName);
@@ -16167,7 +16295,7 @@ class RiskGlobalService {
16167
16295
  * @param payload - Payload with riskName, exchangeName and frameName
16168
16296
  * @returns Promise that resolves when validation is complete
16169
16297
  */
16170
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
16298
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
16171
16299
  this.loggerService.log("riskGlobalService validate", {
16172
16300
  context,
16173
16301
  });
@@ -16245,7 +16373,7 @@ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
16245
16373
  * @param context - Execution context with strategyName, exchangeName, frameName
16246
16374
  * @returns Unique string key for memoization
16247
16375
  */
16248
- const CREATE_KEY_FN$p = (context) => {
16376
+ const CREATE_KEY_FN$q = (context) => {
16249
16377
  const parts = [context.strategyName, context.exchangeName];
16250
16378
  if (context.frameName)
16251
16379
  parts.push(context.frameName);
@@ -16289,7 +16417,7 @@ class ActionCoreService {
16289
16417
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
16290
16418
  * @returns Promise that resolves when all validations complete
16291
16419
  */
16292
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
16420
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
16293
16421
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
16294
16422
  context,
16295
16423
  });
@@ -21359,7 +21487,7 @@ const ReportWriter = new ReportWriterAdapter();
21359
21487
  * @param backtest - Whether running in backtest mode
21360
21488
  * @returns Unique string key for memoization
21361
21489
  */
21362
- const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
21490
+ const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
21363
21491
  const parts = [symbol, strategyName, exchangeName];
21364
21492
  if (frameName)
21365
21493
  parts.push(frameName);
@@ -21605,7 +21733,7 @@ class BacktestMarkdownService {
21605
21733
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21606
21734
  * Each combination gets its own isolated storage instance.
21607
21735
  */
21608
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21736
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21609
21737
  /**
21610
21738
  * Processes tick events and accumulates closed signals.
21611
21739
  * Should be called from IStrategyCallbacks.onTick.
@@ -21762,7 +21890,7 @@ class BacktestMarkdownService {
21762
21890
  payload,
21763
21891
  });
21764
21892
  if (payload) {
21765
- const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21893
+ const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21766
21894
  this.getStorage.clear(key);
21767
21895
  }
21768
21896
  else {
@@ -21824,7 +21952,7 @@ class BacktestMarkdownService {
21824
21952
  * @param backtest - Whether running in backtest mode
21825
21953
  * @returns Unique string key for memoization
21826
21954
  */
21827
- const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21955
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
21828
21956
  const parts = [symbol, strategyName, exchangeName];
21829
21957
  if (frameName)
21830
21958
  parts.push(frameName);
@@ -22319,7 +22447,7 @@ class LiveMarkdownService {
22319
22447
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22320
22448
  * Each combination gets its own isolated storage instance.
22321
22449
  */
22322
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22450
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22323
22451
  /**
22324
22452
  * Subscribes to live signal emitter to receive tick events.
22325
22453
  * Protected against multiple subscriptions.
@@ -22537,7 +22665,7 @@ class LiveMarkdownService {
22537
22665
  payload,
22538
22666
  });
22539
22667
  if (payload) {
22540
- const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22668
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22541
22669
  this.getStorage.clear(key);
22542
22670
  }
22543
22671
  else {
@@ -22557,7 +22685,7 @@ class LiveMarkdownService {
22557
22685
  * @param backtest - Whether running in backtest mode
22558
22686
  * @returns Unique string key for memoization
22559
22687
  */
22560
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22688
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
22561
22689
  const parts = [symbol, strategyName, exchangeName];
22562
22690
  if (frameName)
22563
22691
  parts.push(frameName);
@@ -22846,7 +22974,7 @@ class ScheduleMarkdownService {
22846
22974
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22847
22975
  * Each combination gets its own isolated storage instance.
22848
22976
  */
22849
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22977
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22850
22978
  /**
22851
22979
  * Subscribes to signal emitter to receive scheduled signal events.
22852
22980
  * Protected against multiple subscriptions.
@@ -23049,7 +23177,7 @@ class ScheduleMarkdownService {
23049
23177
  payload,
23050
23178
  });
23051
23179
  if (payload) {
23052
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23180
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23053
23181
  this.getStorage.clear(key);
23054
23182
  }
23055
23183
  else {
@@ -23069,7 +23197,7 @@ class ScheduleMarkdownService {
23069
23197
  * @param backtest - Whether running in backtest mode
23070
23198
  * @returns Unique string key for memoization
23071
23199
  */
23072
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
23200
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
23073
23201
  const parts = [symbol, strategyName, exchangeName];
23074
23202
  if (frameName)
23075
23203
  parts.push(frameName);
@@ -23314,7 +23442,7 @@ class PerformanceMarkdownService {
23314
23442
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
23315
23443
  * Each combination gets its own isolated storage instance.
23316
23444
  */
23317
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23445
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23318
23446
  /**
23319
23447
  * Subscribes to performance emitter to receive performance events.
23320
23448
  * Protected against multiple subscriptions.
@@ -23481,7 +23609,7 @@ class PerformanceMarkdownService {
23481
23609
  payload,
23482
23610
  });
23483
23611
  if (payload) {
23484
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23612
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23485
23613
  this.getStorage.clear(key);
23486
23614
  }
23487
23615
  else {
@@ -23960,7 +24088,7 @@ class WalkerMarkdownService {
23960
24088
  * @param backtest - Whether running in backtest mode
23961
24089
  * @returns Unique string key for memoization
23962
24090
  */
23963
- const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
24091
+ const CREATE_KEY_FN$l = (exchangeName, frameName, backtest) => {
23964
24092
  const parts = [exchangeName];
23965
24093
  if (frameName)
23966
24094
  parts.push(frameName);
@@ -24407,7 +24535,7 @@ class HeatMarkdownService {
24407
24535
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
24408
24536
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
24409
24537
  */
24410
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24538
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24411
24539
  /**
24412
24540
  * Subscribes to signal emitter to receive tick events.
24413
24541
  * Protected against multiple subscriptions.
@@ -24625,7 +24753,7 @@ class HeatMarkdownService {
24625
24753
  payload,
24626
24754
  });
24627
24755
  if (payload) {
24628
- const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24756
+ const key = CREATE_KEY_FN$l(payload.exchangeName, payload.frameName, payload.backtest);
24629
24757
  this.getStorage.clear(key);
24630
24758
  }
24631
24759
  else {
@@ -25656,7 +25784,7 @@ class ClientPartial {
25656
25784
  * @param backtest - Whether running in backtest mode
25657
25785
  * @returns Unique string key for memoization
25658
25786
  */
25659
- const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25787
+ const CREATE_KEY_FN$k = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25660
25788
  /**
25661
25789
  * Creates a callback function for emitting profit events to partialProfitSubject.
25662
25790
  *
@@ -25778,7 +25906,7 @@ class PartialConnectionService {
25778
25906
  * Key format: "signalId:backtest" or "signalId:live"
25779
25907
  * Value: ClientPartial instance with logger and event emitters
25780
25908
  */
25781
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25909
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$k(signalId, backtest), (signalId, backtest) => {
25782
25910
  return new ClientPartial({
25783
25911
  signalId,
25784
25912
  logger: this.loggerService,
@@ -25868,7 +25996,7 @@ class PartialConnectionService {
25868
25996
  const partial = this.getPartial(data.id, backtest);
25869
25997
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25870
25998
  await partial.clear(symbol, data, priceClose, backtest);
25871
- const key = CREATE_KEY_FN$j(data.id, backtest);
25999
+ const key = CREATE_KEY_FN$k(data.id, backtest);
25872
26000
  this.getPartial.clear(key);
25873
26001
  };
25874
26002
  }
@@ -25884,7 +26012,7 @@ class PartialConnectionService {
25884
26012
  * @param backtest - Whether running in backtest mode
25885
26013
  * @returns Unique string key for memoization
25886
26014
  */
25887
- const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
26015
+ const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
25888
26016
  const parts = [symbol, strategyName, exchangeName];
25889
26017
  if (frameName)
25890
26018
  parts.push(frameName);
@@ -26107,7 +26235,7 @@ class PartialMarkdownService {
26107
26235
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26108
26236
  * Each combination gets its own isolated storage instance.
26109
26237
  */
26110
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
26238
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
26111
26239
  /**
26112
26240
  * Subscribes to partial profit/loss signal emitters to receive events.
26113
26241
  * Protected against multiple subscriptions.
@@ -26317,7 +26445,7 @@ class PartialMarkdownService {
26317
26445
  payload,
26318
26446
  });
26319
26447
  if (payload) {
26320
- const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26448
+ const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26321
26449
  this.getStorage.clear(key);
26322
26450
  }
26323
26451
  else {
@@ -26333,7 +26461,7 @@ class PartialMarkdownService {
26333
26461
  * @param context - Context with strategyName, exchangeName, frameName
26334
26462
  * @returns Unique string key for memoization
26335
26463
  */
26336
- const CREATE_KEY_FN$h = (context) => {
26464
+ const CREATE_KEY_FN$i = (context) => {
26337
26465
  const parts = [context.strategyName, context.exchangeName];
26338
26466
  if (context.frameName)
26339
26467
  parts.push(context.frameName);
@@ -26407,7 +26535,7 @@ class PartialGlobalService {
26407
26535
  * @param context - Context with strategyName, exchangeName and frameName
26408
26536
  * @param methodName - Name of the calling method for error tracking
26409
26537
  */
26410
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
26538
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$i(context), (context, methodName) => {
26411
26539
  this.loggerService.log("partialGlobalService validate", {
26412
26540
  context,
26413
26541
  methodName,
@@ -26862,7 +26990,7 @@ class ClientBreakeven {
26862
26990
  * @param backtest - Whether running in backtest mode
26863
26991
  * @returns Unique string key for memoization
26864
26992
  */
26865
- const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26993
+ const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26866
26994
  /**
26867
26995
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26868
26996
  *
@@ -26948,7 +27076,7 @@ class BreakevenConnectionService {
26948
27076
  * Key format: "signalId:backtest" or "signalId:live"
26949
27077
  * Value: ClientBreakeven instance with logger and event emitter
26950
27078
  */
26951
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
27079
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
26952
27080
  return new ClientBreakeven({
26953
27081
  signalId,
26954
27082
  logger: this.loggerService,
@@ -27009,7 +27137,7 @@ class BreakevenConnectionService {
27009
27137
  const breakeven = this.getBreakeven(data.id, backtest);
27010
27138
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
27011
27139
  await breakeven.clear(symbol, data, priceClose, backtest);
27012
- const key = CREATE_KEY_FN$g(data.id, backtest);
27140
+ const key = CREATE_KEY_FN$h(data.id, backtest);
27013
27141
  this.getBreakeven.clear(key);
27014
27142
  };
27015
27143
  }
@@ -27025,7 +27153,7 @@ class BreakevenConnectionService {
27025
27153
  * @param backtest - Whether running in backtest mode
27026
27154
  * @returns Unique string key for memoization
27027
27155
  */
27028
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
27156
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
27029
27157
  const parts = [symbol, strategyName, exchangeName];
27030
27158
  if (frameName)
27031
27159
  parts.push(frameName);
@@ -27200,7 +27328,7 @@ class BreakevenMarkdownService {
27200
27328
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27201
27329
  * Each combination gets its own isolated storage instance.
27202
27330
  */
27203
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
27331
+ 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$5(symbol, strategyName, exchangeName, frameName));
27204
27332
  /**
27205
27333
  * Subscribes to breakeven signal emitter to receive events.
27206
27334
  * Protected against multiple subscriptions.
@@ -27389,7 +27517,7 @@ class BreakevenMarkdownService {
27389
27517
  payload,
27390
27518
  });
27391
27519
  if (payload) {
27392
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27520
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27393
27521
  this.getStorage.clear(key);
27394
27522
  }
27395
27523
  else {
@@ -27405,7 +27533,7 @@ class BreakevenMarkdownService {
27405
27533
  * @param context - Context with strategyName, exchangeName, frameName
27406
27534
  * @returns Unique string key for memoization
27407
27535
  */
27408
- const CREATE_KEY_FN$e = (context) => {
27536
+ const CREATE_KEY_FN$f = (context) => {
27409
27537
  const parts = [context.strategyName, context.exchangeName];
27410
27538
  if (context.frameName)
27411
27539
  parts.push(context.frameName);
@@ -27479,7 +27607,7 @@ class BreakevenGlobalService {
27479
27607
  * @param context - Context with strategyName, exchangeName and frameName
27480
27608
  * @param methodName - Name of the calling method for error tracking
27481
27609
  */
27482
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
27610
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
27483
27611
  this.loggerService.log("breakevenGlobalService validate", {
27484
27612
  context,
27485
27613
  methodName,
@@ -27700,7 +27828,7 @@ class ConfigValidationService {
27700
27828
  * @param backtest - Whether running in backtest mode
27701
27829
  * @returns Unique string key for memoization
27702
27830
  */
27703
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27831
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
27704
27832
  const parts = [symbol, strategyName, exchangeName];
27705
27833
  if (frameName)
27706
27834
  parts.push(frameName);
@@ -27867,7 +27995,7 @@ class RiskMarkdownService {
27867
27995
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27868
27996
  * Each combination gets its own isolated storage instance.
27869
27997
  */
27870
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27998
+ 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$4(symbol, strategyName, exchangeName, frameName));
27871
27999
  /**
27872
28000
  * Subscribes to risk rejection emitter to receive rejection events.
27873
28001
  * Protected against multiple subscriptions.
@@ -28056,7 +28184,7 @@ class RiskMarkdownService {
28056
28184
  payload,
28057
28185
  });
28058
28186
  if (payload) {
28059
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28187
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28060
28188
  this.getStorage.clear(key);
28061
28189
  }
28062
28190
  else {
@@ -30636,7 +30764,7 @@ class HighestProfitReportService {
30636
30764
  * @returns Colon-separated key string for memoization
30637
30765
  * @internal
30638
30766
  */
30639
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
30767
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
30640
30768
  const parts = [symbol, strategyName, exchangeName];
30641
30769
  if (frameName)
30642
30770
  parts.push(frameName);
@@ -30878,7 +31006,7 @@ class StrategyMarkdownService {
30878
31006
  *
30879
31007
  * @internal
30880
31008
  */
30881
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
31009
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30882
31010
  /**
30883
31011
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30884
31012
  *
@@ -31452,7 +31580,7 @@ class StrategyMarkdownService {
31452
31580
  this.clear = async (payload) => {
31453
31581
  this.loggerService.log("strategyMarkdownService clear", { payload });
31454
31582
  if (payload) {
31455
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31583
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31456
31584
  this.getStorage.clear(key);
31457
31585
  }
31458
31586
  else {
@@ -31560,7 +31688,7 @@ class StrategyMarkdownService {
31560
31688
  * Creates a unique key for memoizing ReportStorage instances.
31561
31689
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
31562
31690
  */
31563
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
31691
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
31564
31692
  const parts = [symbol, strategyName, exchangeName];
31565
31693
  if (frameName)
31566
31694
  parts.push(frameName);
@@ -31753,7 +31881,7 @@ let ReportStorage$2 = class ReportStorage {
31753
31881
  class SyncMarkdownService {
31754
31882
  constructor() {
31755
31883
  this.loggerService = inject(TYPES.loggerService);
31756
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31884
+ 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$2(symbol, strategyName, exchangeName, frameName, backtest));
31757
31885
  /**
31758
31886
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31759
31887
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31951,7 +32079,7 @@ class SyncMarkdownService {
31951
32079
  this.clear = async (payload) => {
31952
32080
  this.loggerService.log("syncMarkdownService clear", { payload });
31953
32081
  if (payload) {
31954
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32082
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31955
32083
  this.getStorage.clear(key);
31956
32084
  }
31957
32085
  else {
@@ -31964,7 +32092,7 @@ class SyncMarkdownService {
31964
32092
  /**
31965
32093
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31966
32094
  */
31967
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32095
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
31968
32096
  const parts = [symbol, strategyName, exchangeName];
31969
32097
  if (frameName)
31970
32098
  parts.push(frameName);
@@ -32142,7 +32270,7 @@ let ReportStorage$1 = class ReportStorage {
32142
32270
  class HighestProfitMarkdownService {
32143
32271
  constructor() {
32144
32272
  this.loggerService = inject(TYPES.loggerService);
32145
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
32273
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
32146
32274
  /**
32147
32275
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
32148
32276
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -32308,7 +32436,7 @@ class HighestProfitMarkdownService {
32308
32436
  this.clear = async (payload) => {
32309
32437
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
32310
32438
  if (payload) {
32311
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32439
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32312
32440
  this.getStorage.clear(key);
32313
32441
  }
32314
32442
  else {
@@ -32330,7 +32458,7 @@ const LISTEN_TIMEOUT$1 = 120000;
32330
32458
  * @param backtest - Whether running in backtest mode
32331
32459
  * @returns Unique string key for memoization
32332
32460
  */
32333
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32461
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32334
32462
  const parts = [symbol, strategyName, exchangeName];
32335
32463
  if (frameName)
32336
32464
  parts.push(frameName);
@@ -32373,7 +32501,7 @@ class PriceMetaService {
32373
32501
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
32374
32502
  * Instances are cached until clear() is called.
32375
32503
  */
32376
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32504
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32377
32505
  /**
32378
32506
  * Returns the current market price for the given symbol and context.
32379
32507
  *
@@ -32402,10 +32530,10 @@ class PriceMetaService {
32402
32530
  if (source.data) {
32403
32531
  return source.data;
32404
32532
  }
32405
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32533
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32406
32534
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
32407
32535
  if (typeof currentPrice === "symbol") {
32408
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32536
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32409
32537
  }
32410
32538
  return currentPrice;
32411
32539
  };
@@ -32447,7 +32575,7 @@ class PriceMetaService {
32447
32575
  this.getSource.clear();
32448
32576
  return;
32449
32577
  }
32450
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32578
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32451
32579
  this.getSource.clear(key);
32452
32580
  };
32453
32581
  }
@@ -32465,7 +32593,7 @@ const LISTEN_TIMEOUT = 120000;
32465
32593
  * @param backtest - Whether running in backtest mode
32466
32594
  * @returns Unique string key for memoization
32467
32595
  */
32468
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32596
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32469
32597
  const parts = [symbol, strategyName, exchangeName];
32470
32598
  if (frameName)
32471
32599
  parts.push(frameName);
@@ -32508,7 +32636,7 @@ class TimeMetaService {
32508
32636
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
32509
32637
  * Instances are cached until clear() is called.
32510
32638
  */
32511
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32639
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32512
32640
  /**
32513
32641
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
32514
32642
  *
@@ -32536,10 +32664,10 @@ class TimeMetaService {
32536
32664
  if (source.data) {
32537
32665
  return source.data;
32538
32666
  }
32539
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32667
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32540
32668
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
32541
32669
  if (typeof timestamp === "symbol") {
32542
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32670
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32543
32671
  }
32544
32672
  return timestamp;
32545
32673
  };
@@ -32581,7 +32709,7 @@ class TimeMetaService {
32581
32709
  this.getSource.clear();
32582
32710
  return;
32583
32711
  }
32584
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32712
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32585
32713
  this.getSource.clear(key);
32586
32714
  };
32587
32715
  }
@@ -32687,7 +32815,7 @@ class MaxDrawdownReportService {
32687
32815
  /**
32688
32816
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
32689
32817
  */
32690
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32818
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32691
32819
  const parts = [symbol, strategyName, exchangeName];
32692
32820
  if (frameName)
32693
32821
  parts.push(frameName);
@@ -32813,7 +32941,7 @@ class ReportStorage {
32813
32941
  class MaxDrawdownMarkdownService {
32814
32942
  constructor() {
32815
32943
  this.loggerService = inject(TYPES.loggerService);
32816
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32944
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32817
32945
  /**
32818
32946
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32819
32947
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32892,7 +33020,7 @@ class MaxDrawdownMarkdownService {
32892
33020
  this.clear = async (payload) => {
32893
33021
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32894
33022
  if (payload) {
32895
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
33023
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32896
33024
  this.getStorage.clear(key);
32897
33025
  }
32898
33026
  else {
@@ -32910,7 +33038,7 @@ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32910
33038
  * @param context - Execution context with strategyName, exchangeName, frameName
32911
33039
  * @returns Unique string key for memoization
32912
33040
  */
32913
- const CREATE_KEY_FN$6 = (context) => {
33041
+ const CREATE_KEY_FN$7 = (context) => {
32914
33042
  const parts = [context.strategyName, context.exchangeName];
32915
33043
  if (context.frameName)
32916
33044
  parts.push(context.frameName);
@@ -32943,7 +33071,7 @@ class NotificationHelperService {
32943
33071
  * @param context - Routing context: strategyName, exchangeName, frameName
32944
33072
  * @throws {Error} If any registered schema fails validation
32945
33073
  */
32946
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
33074
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), async (context) => {
32947
33075
  this.loggerService.log(METHOD_NAME_VALIDATE, {
32948
33076
  context,
32949
33077
  });
@@ -46487,7 +46615,7 @@ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapte
46487
46615
  * @param backtest - Flag indicating if the context is backtest or live
46488
46616
  * @returns Composite key string
46489
46617
  */
46490
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46618
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46491
46619
  const parts = [symbol, strategyName, exchangeName];
46492
46620
  if (frameName)
46493
46621
  parts.push(frameName);
@@ -46577,7 +46705,7 @@ class RecentMemoryBacktestUtils {
46577
46705
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
46578
46706
  signalId: event.data.id,
46579
46707
  });
46580
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46708
+ const key = CREATE_KEY_FN$6(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46581
46709
  this._signals.set(key, event.data);
46582
46710
  };
46583
46711
  /**
@@ -46590,7 +46718,7 @@ class RecentMemoryBacktestUtils {
46590
46718
  * @returns The latest signal or null if not found
46591
46719
  */
46592
46720
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46593
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46721
+ const key = CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest$1);
46594
46722
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46595
46723
  return this._signals.get(key) ?? null;
46596
46724
  };
@@ -46696,7 +46824,7 @@ class RecentMemoryLiveUtils {
46696
46824
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
46697
46825
  signalId: event.data.id,
46698
46826
  });
46699
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46827
+ const key = CREATE_KEY_FN$6(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46700
46828
  this._signals.set(key, event.data);
46701
46829
  };
46702
46830
  /**
@@ -46709,7 +46837,7 @@ class RecentMemoryLiveUtils {
46709
46837
  * @returns The latest signal or null if not found
46710
46838
  */
46711
46839
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46712
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46840
+ const key = CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest$1);
46713
46841
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46714
46842
  return this._signals.get(key) ?? null;
46715
46843
  };
@@ -47060,6 +47188,554 @@ const RecentLive = new RecentLiveAdapter();
47060
47188
  */
47061
47189
  const RecentBacktest = new RecentBacktestAdapter();
47062
47190
 
47191
+ const CREATE_KEY_FN$5 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47192
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_GET = "StateLocalInstance.getState";
47193
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_SET = "StateLocalInstance.setState";
47194
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT = "StatePersistInstance.waitForInit";
47195
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_GET = "StatePersistInstance.getState";
47196
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_SET = "StatePersistInstance.setState";
47197
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "StateBacktestAdapter.dispose";
47198
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_GET = "StateBacktestAdapter.getState";
47199
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_SET = "StateBacktestAdapter.setState";
47200
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "StateBacktestAdapter.useLocal";
47201
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "StateBacktestAdapter.usePersist";
47202
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "StateBacktestAdapter.useDummy";
47203
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateBacktestAdapter.useStateAdapter";
47204
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "StateBacktestAdapter.clear";
47205
+ const STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "StateLiveAdapter.dispose";
47206
+ const STATE_LIVE_ADAPTER_METHOD_NAME_GET = "StateLiveAdapter.getState";
47207
+ const STATE_LIVE_ADAPTER_METHOD_NAME_SET = "StateLiveAdapter.setState";
47208
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "StateLiveAdapter.useLocal";
47209
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "StateLiveAdapter.usePersist";
47210
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "StateLiveAdapter.useDummy";
47211
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateLiveAdapter.useStateAdapter";
47212
+ const STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR = "StateLiveAdapter.clear";
47213
+ const STATE_ADAPTER_METHOD_NAME_ENABLE = "StateAdapter.enable";
47214
+ const STATE_ADAPTER_METHOD_NAME_DISABLE = "StateAdapter.disable";
47215
+ const STATE_ADAPTER_METHOD_NAME_GET = "StateAdapter.getState";
47216
+ const STATE_ADAPTER_METHOD_NAME_SET = "StateAdapter.setState";
47217
+ /**
47218
+ * In-process state instance backed by a plain object reference.
47219
+ * All data lives in process memory only - no disk persistence.
47220
+ *
47221
+ * Features:
47222
+ * - Mutable in-memory state with functional dispatch support
47223
+ * - Scoped per (signalId, bucketName) pair
47224
+ *
47225
+ * Use for backtesting and unit tests where persistence between runs is not needed.
47226
+ * Tracks per-trade metrics such as peakPercent and minutesOpen to implement
47227
+ * the capitulation rule: exit when peak < threshold after N minutes open.
47228
+ */
47229
+ class StateLocalInstance {
47230
+ constructor(initialValue, signalId, bucketName) {
47231
+ this.initialValue = initialValue;
47232
+ this.signalId = signalId;
47233
+ this.bucketName = bucketName;
47234
+ /**
47235
+ * Initializes _value from initialValue - local state needs no async setup.
47236
+ * @returns Promise that resolves immediately
47237
+ */
47238
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
47239
+ this._value = this.initialValue;
47240
+ });
47241
+ /**
47242
+ * Update the in-memory state value.
47243
+ * @param dispatch - New value or updater function receiving current value
47244
+ * @returns Updated state value
47245
+ */
47246
+ this.setState = functoolsKit.queued(async (dispatch) => {
47247
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
47248
+ signalId: this.signalId,
47249
+ bucketName: this.bucketName,
47250
+ });
47251
+ if (typeof dispatch === "function") {
47252
+ this._value = await dispatch(this._value);
47253
+ }
47254
+ else {
47255
+ this._value = dispatch;
47256
+ }
47257
+ return this._value;
47258
+ });
47259
+ }
47260
+ /**
47261
+ * Read the current in-memory state value.
47262
+ * @returns Current state value
47263
+ */
47264
+ async getState() {
47265
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
47266
+ signalId: this.signalId,
47267
+ bucketName: this.bucketName,
47268
+ });
47269
+ return this._value;
47270
+ }
47271
+ /** Releases resources held by this instance. */
47272
+ async dispose() {
47273
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47274
+ signalId: this.signalId,
47275
+ bucketName: this.bucketName,
47276
+ });
47277
+ }
47278
+ }
47279
+ /**
47280
+ * No-op state instance that discards all writes.
47281
+ * Used for disabling state in tests or dry-run scenarios.
47282
+ *
47283
+ * Useful when replaying historical candles without needing to accumulate
47284
+ * peakPercent/minutesOpen — the capitulation rule is simply never triggered.
47285
+ */
47286
+ class StateDummyInstance {
47287
+ constructor(initialValue, signalId, bucketName) {
47288
+ this.initialValue = initialValue;
47289
+ this.signalId = signalId;
47290
+ this.bucketName = bucketName;
47291
+ /**
47292
+ * No-op initialization.
47293
+ * @returns Promise that resolves immediately
47294
+ */
47295
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
47296
+ });
47297
+ }
47298
+ /**
47299
+ * No-op read - always returns initialValue.
47300
+ * @returns initialValue
47301
+ */
47302
+ async getState() {
47303
+ return this.initialValue;
47304
+ }
47305
+ /**
47306
+ * No-op write - discards the value and returns initialValue.
47307
+ * @returns initialValue
47308
+ */
47309
+ async setState(_dispatch) {
47310
+ return this.initialValue;
47311
+ }
47312
+ /** No-op. */
47313
+ async dispose() {
47314
+ }
47315
+ }
47316
+ /**
47317
+ * File-system backed state instance.
47318
+ * Data is persisted atomically to disk via PersistStateAdapter.
47319
+ * State is restored from disk on waitForInit.
47320
+ *
47321
+ * Features:
47322
+ * - Crash-safe atomic file writes
47323
+ * - Functional dispatch support
47324
+ * - Scoped per (signalId, bucketName) pair
47325
+ *
47326
+ * Use in live trading to survive process restarts mid-trade.
47327
+ * Preserves peakPercent and minutesOpen so the capitulation rule
47328
+ * (exit if peak < threshold after N minutes) continues correctly after a crash.
47329
+ */
47330
+ class StatePersistInstance {
47331
+ constructor(initialValue, signalId, bucketName) {
47332
+ this.initialValue = initialValue;
47333
+ this.signalId = signalId;
47334
+ this.bucketName = bucketName;
47335
+ /**
47336
+ * Initialize persistence storage and restore state from disk.
47337
+ * @param initial - Whether this is the first initialization
47338
+ */
47339
+ this.waitForInit = functoolsKit.singleshot(async (initial) => {
47340
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT, {
47341
+ signalId: this.signalId,
47342
+ bucketName: this.bucketName,
47343
+ initial,
47344
+ });
47345
+ await PersistStateAdapter.waitForInit(this.signalId, this.bucketName, initial);
47346
+ const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
47347
+ if (data) {
47348
+ this._value = data.data;
47349
+ return;
47350
+ }
47351
+ this._value = this.initialValue;
47352
+ });
47353
+ /**
47354
+ * Update state and persist to disk atomically.
47355
+ * @param dispatch - New value or updater function receiving current value
47356
+ * @returns Updated state value
47357
+ */
47358
+ this.setState = functoolsKit.queued(async (dispatch) => {
47359
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
47360
+ signalId: this.signalId,
47361
+ bucketName: this.bucketName,
47362
+ });
47363
+ if (typeof dispatch === "function") {
47364
+ this._value = await dispatch(this._value);
47365
+ }
47366
+ else {
47367
+ this._value = dispatch;
47368
+ }
47369
+ const id = CREATE_KEY_FN$5(this.signalId, this.bucketName);
47370
+ await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
47371
+ return this._value;
47372
+ });
47373
+ }
47374
+ /**
47375
+ * Read the current persisted state value.
47376
+ * @returns Current state value
47377
+ */
47378
+ async getState() {
47379
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
47380
+ signalId: this.signalId,
47381
+ bucketName: this.bucketName,
47382
+ });
47383
+ return this._value;
47384
+ }
47385
+ /** Releases resources held by this instance. */
47386
+ async dispose() {
47387
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47388
+ signalId: this.signalId,
47389
+ bucketName: this.bucketName,
47390
+ });
47391
+ await PersistStateAdapter.dispose(this.signalId, this.bucketName);
47392
+ }
47393
+ }
47394
+ /**
47395
+ * Backtest state adapter with pluggable storage backend.
47396
+ *
47397
+ * Features:
47398
+ * - Adapter pattern for swappable state instance implementations
47399
+ * - Default backend: StateLocalInstance (in-memory, no disk persistence)
47400
+ * - Alternative backends: StatePersistInstance, StateDummyInstance
47401
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47402
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47403
+ *
47404
+ * Primary use case — LLM-driven capitulation rule:
47405
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47406
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47407
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47408
+ * the LLM thesis was not confirmed by market — exit immediately.
47409
+ * State tracks `{ peakPercent, minutesOpen }` per signal across onActivePing ticks.
47410
+ */
47411
+ class StateBacktestAdapter {
47412
+ constructor() {
47413
+ this.StateFactory = StateLocalInstance;
47414
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$5(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47415
+ /**
47416
+ * Disposes all memoized instances for the given signalId.
47417
+ * Called by StateAdapter when a signal is cancelled or closed.
47418
+ * @param signalId - Signal identifier to dispose
47419
+ */
47420
+ this.disposeSignal = (signalId) => {
47421
+ const prefix = CREATE_KEY_FN$5(signalId, "");
47422
+ for (const key of this.getInstance.keys()) {
47423
+ if (key.startsWith(prefix)) {
47424
+ const instance = this.getInstance.get(key);
47425
+ instance && instance.dispose();
47426
+ this.getInstance.clear(key);
47427
+ }
47428
+ }
47429
+ };
47430
+ /**
47431
+ * Read the current state value for a signal.
47432
+ * @param dto.signalId - Signal identifier
47433
+ * @param dto.bucketName - Bucket name
47434
+ * @param dto.initialValue - Default value when no persisted state exists
47435
+ * @returns Current state value
47436
+ */
47437
+ this.getState = async (dto) => {
47438
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_GET, {
47439
+ signalId: dto.signalId,
47440
+ bucketName: dto.bucketName,
47441
+ });
47442
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47443
+ const isInitial = !this.getInstance.has(key);
47444
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47445
+ await instance.waitForInit(isInitial);
47446
+ return await instance.getState();
47447
+ };
47448
+ /**
47449
+ * Update the state value for a signal.
47450
+ * @param dispatch - New value or updater function receiving current value
47451
+ * @param dto.signalId - Signal identifier
47452
+ * @param dto.bucketName - Bucket name
47453
+ * @param dto.initialValue - Default value when no persisted state exists
47454
+ * @returns Updated state value
47455
+ */
47456
+ this.setState = async (dispatch, dto) => {
47457
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_SET, {
47458
+ signalId: dto.signalId,
47459
+ bucketName: dto.bucketName,
47460
+ });
47461
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47462
+ const isInitial = !this.getInstance.has(key);
47463
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47464
+ await instance.waitForInit(isInitial);
47465
+ return await instance.setState(dispatch);
47466
+ };
47467
+ /**
47468
+ * Switches to in-memory adapter (default).
47469
+ * All data lives in process memory only.
47470
+ */
47471
+ this.useLocal = () => {
47472
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
47473
+ this.StateFactory = StateLocalInstance;
47474
+ };
47475
+ /**
47476
+ * Switches to file-system backed adapter.
47477
+ * Data is persisted to disk via PersistStateAdapter.
47478
+ */
47479
+ this.usePersist = () => {
47480
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
47481
+ this.StateFactory = StatePersistInstance;
47482
+ };
47483
+ /**
47484
+ * Switches to dummy adapter that discards all writes.
47485
+ */
47486
+ this.useDummy = () => {
47487
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
47488
+ this.StateFactory = StateDummyInstance;
47489
+ };
47490
+ /**
47491
+ * Switches to a custom state adapter implementation.
47492
+ * @param Ctor - Constructor for the custom state instance
47493
+ */
47494
+ this.useStateAdapter = (Ctor) => {
47495
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47496
+ this.StateFactory = Ctor;
47497
+ };
47498
+ /**
47499
+ * Clears the memoized instance cache.
47500
+ * Call this when process.cwd() changes between strategy iterations
47501
+ * so new instances are created with the updated base path.
47502
+ */
47503
+ this.clear = () => {
47504
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
47505
+ this.getInstance.clear();
47506
+ };
47507
+ }
47508
+ }
47509
+ /**
47510
+ * Live trading state adapter with pluggable storage backend.
47511
+ *
47512
+ * Features:
47513
+ * - Adapter pattern for swappable state instance implementations
47514
+ * - Default backend: StatePersistInstance (file-system backed, survives restarts)
47515
+ * - Alternative backends: StateLocalInstance, StateDummyInstance
47516
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47517
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47518
+ *
47519
+ * Primary use case — LLM-driven capitulation rule:
47520
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47521
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47522
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47523
+ * the LLM thesis was not confirmed by market — exit immediately.
47524
+ * State persists `{ peakPercent, minutesOpen }` per signal across process restarts.
47525
+ */
47526
+ class StateLiveAdapter {
47527
+ constructor() {
47528
+ this.StateFactory = StatePersistInstance;
47529
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$5(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47530
+ /**
47531
+ * Disposes all memoized instances for the given signalId.
47532
+ * Called by StateAdapter when a signal is cancelled or closed.
47533
+ * @param signalId - Signal identifier to dispose
47534
+ */
47535
+ this.disposeSignal = (signalId) => {
47536
+ const prefix = CREATE_KEY_FN$5(signalId, "");
47537
+ for (const key of this.getInstance.keys()) {
47538
+ if (key.startsWith(prefix)) {
47539
+ const instance = this.getInstance.get(key);
47540
+ instance && instance.dispose();
47541
+ this.getInstance.clear(key);
47542
+ }
47543
+ }
47544
+ };
47545
+ /**
47546
+ * Read the current state value for a signal.
47547
+ * @param dto.signalId - Signal identifier
47548
+ * @param dto.bucketName - Bucket name
47549
+ * @param dto.initialValue - Default value when no persisted state exists
47550
+ * @returns Current state value
47551
+ */
47552
+ this.getState = async (dto) => {
47553
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_GET, {
47554
+ signalId: dto.signalId,
47555
+ bucketName: dto.bucketName,
47556
+ });
47557
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47558
+ const isInitial = !this.getInstance.has(key);
47559
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47560
+ await instance.waitForInit(isInitial);
47561
+ return await instance.getState();
47562
+ };
47563
+ /**
47564
+ * Update the state value for a signal.
47565
+ * @param dispatch - New value or updater function receiving current value
47566
+ * @param dto.signalId - Signal identifier
47567
+ * @param dto.bucketName - Bucket name
47568
+ * @param dto.initialValue - Default value when no persisted state exists
47569
+ * @returns Updated state value
47570
+ */
47571
+ this.setState = async (dispatch, dto) => {
47572
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_SET, {
47573
+ signalId: dto.signalId,
47574
+ bucketName: dto.bucketName,
47575
+ });
47576
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47577
+ const isInitial = !this.getInstance.has(key);
47578
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47579
+ await instance.waitForInit(isInitial);
47580
+ return await instance.setState(dispatch);
47581
+ };
47582
+ /**
47583
+ * Switches to in-memory adapter.
47584
+ * All data lives in process memory only.
47585
+ */
47586
+ this.useLocal = () => {
47587
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
47588
+ this.StateFactory = StateLocalInstance;
47589
+ };
47590
+ /**
47591
+ * Switches to file-system backed adapter (default).
47592
+ * Data is persisted to disk via PersistStateAdapter.
47593
+ */
47594
+ this.usePersist = () => {
47595
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
47596
+ this.StateFactory = StatePersistInstance;
47597
+ };
47598
+ /**
47599
+ * Switches to dummy adapter that discards all writes.
47600
+ */
47601
+ this.useDummy = () => {
47602
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
47603
+ this.StateFactory = StateDummyInstance;
47604
+ };
47605
+ /**
47606
+ * Switches to a custom state adapter implementation.
47607
+ * @param Ctor - Constructor for the custom state instance
47608
+ */
47609
+ this.useStateAdapter = (Ctor) => {
47610
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47611
+ this.StateFactory = Ctor;
47612
+ };
47613
+ /**
47614
+ * Clears the memoized instance cache.
47615
+ * Call this when process.cwd() changes between strategy iterations
47616
+ * so new instances are created with the updated base path.
47617
+ */
47618
+ this.clear = () => {
47619
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR);
47620
+ this.getInstance.clear();
47621
+ };
47622
+ }
47623
+ }
47624
+ /**
47625
+ * Main state adapter that manages both backtest and live state storage.
47626
+ *
47627
+ * Features:
47628
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
47629
+ * - Routes all operations to StateBacktest or StateLive based on dto.backtest
47630
+ * - Singleshot enable pattern prevents duplicate subscriptions
47631
+ * - Cleanup function for proper unsubscription
47632
+ */
47633
+ class StateAdapter {
47634
+ constructor() {
47635
+ /**
47636
+ * Enables state storage by subscribing to signal lifecycle events.
47637
+ * Clears memoized instances in StateBacktest and StateLive when a signal
47638
+ * is cancelled or closed, preventing stale instances from accumulating.
47639
+ * Uses singleshot to ensure one-time subscription.
47640
+ *
47641
+ * @returns Cleanup function that unsubscribes from all emitters
47642
+ */
47643
+ this.enable = functoolsKit.singleshot(() => {
47644
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_ENABLE);
47645
+ const unCancel = signalEmitter
47646
+ .filter(({ action }) => action === "cancelled")
47647
+ .connect(({ signal }) => {
47648
+ StateBacktest.disposeSignal(signal.id);
47649
+ StateLive.disposeSignal(signal.id);
47650
+ });
47651
+ const unClose = signalEmitter
47652
+ .filter(({ action }) => action === "closed")
47653
+ .connect(({ signal }) => {
47654
+ StateBacktest.disposeSignal(signal.id);
47655
+ StateLive.disposeSignal(signal.id);
47656
+ });
47657
+ return functoolsKit.compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47658
+ });
47659
+ /**
47660
+ * Disables state storage by unsubscribing from signal lifecycle events.
47661
+ * Safe to call multiple times.
47662
+ */
47663
+ this.disable = () => {
47664
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_DISABLE);
47665
+ if (this.enable.hasValue()) {
47666
+ const lastSubscription = this.enable();
47667
+ lastSubscription();
47668
+ }
47669
+ };
47670
+ /**
47671
+ * Read the current state value for a signal.
47672
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47673
+ * @param dto.signalId - Signal identifier
47674
+ * @param dto.bucketName - Bucket name
47675
+ * @param dto.initialValue - Default value when no persisted state exists
47676
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47677
+ * @returns Current state value
47678
+ * @throws Error if adapter is not enabled
47679
+ */
47680
+ this.getState = async (dto) => {
47681
+ if (!this.enable.hasValue()) {
47682
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47683
+ }
47684
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_GET, {
47685
+ signalId: dto.signalId,
47686
+ bucketName: dto.bucketName,
47687
+ backtest: dto.backtest,
47688
+ });
47689
+ if (dto.backtest) {
47690
+ return await StateBacktest.getState(dto);
47691
+ }
47692
+ return await StateLive.getState(dto);
47693
+ };
47694
+ /**
47695
+ * Update the state value for a signal.
47696
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47697
+ * @param dispatch - New value or updater function receiving current value
47698
+ * @param dto.signalId - Signal identifier
47699
+ * @param dto.bucketName - Bucket name
47700
+ * @param dto.initialValue - Default value when no persisted state exists
47701
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47702
+ * @returns Updated state value
47703
+ * @throws Error if adapter is not enabled
47704
+ */
47705
+ this.setState = async (dispatch, dto) => {
47706
+ if (!this.enable.hasValue()) {
47707
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47708
+ }
47709
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_SET, {
47710
+ signalId: dto.signalId,
47711
+ bucketName: dto.bucketName,
47712
+ backtest: dto.backtest,
47713
+ });
47714
+ if (dto.backtest) {
47715
+ return await StateBacktest.setState(dispatch, dto);
47716
+ }
47717
+ return await StateLive.setState(dispatch, dto);
47718
+ };
47719
+ }
47720
+ }
47721
+ /**
47722
+ * Global singleton instance of StateAdapter.
47723
+ * Provides unified state management for backtest and live trading.
47724
+ */
47725
+ const State = new StateAdapter();
47726
+ /**
47727
+ * Global singleton instance of StateLiveAdapter.
47728
+ * Provides live trading state storage with pluggable backends.
47729
+ */
47730
+ const StateLive = new StateLiveAdapter();
47731
+ /**
47732
+ * Global singleton instance of StateBacktestAdapter.
47733
+ * Provides backtest state storage with pluggable backends.
47734
+ */
47735
+ const StateBacktest = new StateBacktestAdapter();
47736
+
47737
+ const GET_SIGNAL_STATE_METHOD_NAME = "signal.getSignalState";
47738
+ const SET_SIGNAL_STATE_METHOD_NAME = "signal.setSignalState";
47063
47739
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
47064
47740
  const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
47065
47741
  /**
@@ -47135,6 +47811,243 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
47135
47811
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47136
47812
  return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
47137
47813
  }
47814
+ /**
47815
+ * Reads the state value scoped to the current active signal.
47816
+ *
47817
+ * Resolves the active pending signal automatically from execution context.
47818
+ * If no pending signal exists, logs a warning and returns the initialValue.
47819
+ *
47820
+ * Automatically detects backtest/live mode from execution context.
47821
+ *
47822
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47823
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47824
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47825
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47826
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47827
+ *
47828
+ * @param dto.bucketName - State bucket name
47829
+ * @param dto.initialValue - Default value when no persisted state exists
47830
+ * @returns Promise resolving to current state value, or initialValue if no signal
47831
+ *
47832
+ * @deprecated Better use `createSignalState().getState` with codestyle native syntax
47833
+ *
47834
+ * @example
47835
+ * ```typescript
47836
+ * import { getSignalState } from "backtest-kit";
47837
+ *
47838
+ * const { peakPercent, minutesOpen } = await getSignalState({
47839
+ * bucketName: "trade",
47840
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
47841
+ * });
47842
+ * if (minutesOpen >= 15 && peakPercent < 0.3) {
47843
+ * await commitMarketClose(symbol); // capitulate — LLM thesis not confirmed
47844
+ * }
47845
+ * ```
47846
+ */
47847
+ async function getSignalState(dto) {
47848
+ const { bucketName, initialValue } = dto;
47849
+ backtest.loggerService.info(GET_SIGNAL_STATE_METHOD_NAME, { bucketName });
47850
+ if (!ExecutionContextService.hasContext()) {
47851
+ throw new Error("getSignalState requires an execution context");
47852
+ }
47853
+ if (!MethodContextService.hasContext()) {
47854
+ throw new Error("getSignalState requires a method context");
47855
+ }
47856
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47857
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47858
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47859
+ let signal;
47860
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47861
+ return await State.getState({
47862
+ signalId: signal.id,
47863
+ bucketName,
47864
+ initialValue,
47865
+ backtest: isBacktest,
47866
+ });
47867
+ }
47868
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47869
+ return await State.getState({
47870
+ signalId: signal.id,
47871
+ bucketName,
47872
+ initialValue,
47873
+ backtest: isBacktest,
47874
+ });
47875
+ }
47876
+ throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47877
+ }
47878
+ /**
47879
+ * Updates the state value scoped to the current active signal.
47880
+ *
47881
+ * Resolves the active pending signal automatically from execution context.
47882
+ * If no pending signal exists, logs a warning and returns without writing.
47883
+ *
47884
+ * Automatically detects backtest/live mode from execution context.
47885
+ *
47886
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47887
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47888
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47889
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47890
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47891
+ *
47892
+ * @param dto.bucketName - State bucket name
47893
+ * @param dto.initialValue - Default value when no persisted state exists
47894
+ * @param dto.dispatch - New value or updater function receiving current value
47895
+ * @returns Promise resolving to updated state value, or initialValue if no signal
47896
+ *
47897
+ * @deprecated Better use `createSignalState().setState` with codestyle native syntax
47898
+ *
47899
+ * @example
47900
+ * ```typescript
47901
+ * import { setSignalState } from "backtest-kit";
47902
+ *
47903
+ * await setSignalState(
47904
+ * dispatch: (s) => ({
47905
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
47906
+ * minutesOpen: s.minutesOpen + 1,
47907
+ * }),
47908
+ * {
47909
+ * bucketName: "trade",
47910
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
47911
+ * }
47912
+ * );
47913
+ * ```
47914
+ */
47915
+ async function setSignalState(dispatch, dto) {
47916
+ const { bucketName, initialValue } = dto;
47917
+ backtest.loggerService.info(SET_SIGNAL_STATE_METHOD_NAME, { bucketName });
47918
+ if (!ExecutionContextService.hasContext()) {
47919
+ throw new Error("setSignalState requires an execution context");
47920
+ }
47921
+ if (!MethodContextService.hasContext()) {
47922
+ throw new Error("setSignalState requires a method context");
47923
+ }
47924
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47925
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47926
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47927
+ let signal;
47928
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47929
+ return await State.setState(dispatch, {
47930
+ signalId: signal.id,
47931
+ bucketName,
47932
+ initialValue,
47933
+ backtest: isBacktest,
47934
+ });
47935
+ }
47936
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47937
+ return await State.setState(dispatch, {
47938
+ signalId: signal.id,
47939
+ bucketName,
47940
+ initialValue,
47941
+ backtest: isBacktest,
47942
+ });
47943
+ }
47944
+ throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47945
+ }
47946
+
47947
+ const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
47948
+ const CREATE_SET_STATE_FN = (params) => async (dispatch) => {
47949
+ if (!ExecutionContextService.hasContext()) {
47950
+ throw new Error("createSignalState requires an execution context");
47951
+ }
47952
+ if (!MethodContextService.hasContext()) {
47953
+ throw new Error("createSignalState requires a method context");
47954
+ }
47955
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47956
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47957
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47958
+ let signal;
47959
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47960
+ return await State.setState(dispatch, {
47961
+ backtest: isBacktest,
47962
+ bucketName: params.bucketName,
47963
+ initialValue: params.initialValue,
47964
+ signalId: signal.id,
47965
+ });
47966
+ }
47967
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47968
+ return await State.setState(dispatch, {
47969
+ backtest: isBacktest,
47970
+ bucketName: params.bucketName,
47971
+ initialValue: params.initialValue,
47972
+ signalId: signal.id,
47973
+ });
47974
+ }
47975
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
47976
+ };
47977
+ const CREATE_GET_STATE_FN = (params) => async () => {
47978
+ if (!ExecutionContextService.hasContext()) {
47979
+ throw new Error("createSignalState requires an execution context");
47980
+ }
47981
+ if (!MethodContextService.hasContext()) {
47982
+ throw new Error("createSignalState requires a method context");
47983
+ }
47984
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47985
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47986
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47987
+ let signal;
47988
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47989
+ return await State.getState({
47990
+ backtest: isBacktest,
47991
+ bucketName: params.bucketName,
47992
+ initialValue: params.initialValue,
47993
+ signalId: signal.id,
47994
+ });
47995
+ }
47996
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47997
+ return await State.getState({
47998
+ backtest: isBacktest,
47999
+ bucketName: params.bucketName,
48000
+ initialValue: params.initialValue,
48001
+ signalId: signal.id,
48002
+ });
48003
+ }
48004
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
48005
+ };
48006
+ /**
48007
+ * Creates a bound [getState, setState] tuple scoped to a bucket and initial value.
48008
+ *
48009
+ * Both returned functions resolve the active pending or scheduled signal and the
48010
+ * backtest/live flag automatically from execution context — no signalId argument required.
48011
+ *
48012
+ * Automatically detects backtest/live mode from execution context.
48013
+ *
48014
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
48015
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
48016
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
48017
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
48018
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
48019
+ *
48020
+ * @param params.bucketName - Logical namespace for grouping state buckets within a signal
48021
+ * @param params.initialValue - Default value when no persisted state exists
48022
+ * @returns Tuple [getState, setState] bound to the bucket and initial value
48023
+ *
48024
+ * @example
48025
+ * ```typescript
48026
+ * import { createSignalState } from "backtest-kit";
48027
+ *
48028
+ * const [getTradeState, setTradeState] = createSignalState({
48029
+ * bucketName: "trade",
48030
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
48031
+ * });
48032
+ *
48033
+ * // in onActivePing:
48034
+ * await setTradeState((s) => ({
48035
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
48036
+ * minutesOpen: s.minutesOpen + 1,
48037
+ * }));
48038
+ * const { peakPercent, minutesOpen } = await getTradeState();
48039
+ * if (minutesOpen >= 15 && peakPercent < 0.3) await commitMarketClose(symbol);
48040
+ * ```
48041
+ */
48042
+ function createSignalState(params) {
48043
+ backtest.loggerService.info(CREATE_SIGNAL_STATE_METHOD_NAME, {
48044
+ bucketName: params.bucketName,
48045
+ });
48046
+ return [
48047
+ CREATE_GET_STATE_FN(params),
48048
+ CREATE_SET_STATE_FN(params),
48049
+ ];
48050
+ }
47138
48051
 
47139
48052
  const DEFAULT_BM25_K1 = 1.5;
47140
48053
  const DEFAULT_BM25_B = 0.75;
@@ -47221,7 +48134,7 @@ const createSearchIndex = () => {
47221
48134
  return { upsert, remove, list, search, read };
47222
48135
  };
47223
48136
 
47224
- const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
48137
+ const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47225
48138
  const LIST_MEMORY_FN = ({ id, content }) => ({
47226
48139
  memoryId: id,
47227
48140
  content: content,
@@ -47242,18 +48155,35 @@ const MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ = "MemoryPersistInstance.readMemo
47242
48155
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH = "MemoryPersistInstance.searchMemory";
47243
48156
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST = "MemoryPersistInstance.listMemory";
47244
48157
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE = "MemoryPersistInstance.removeMemory";
48158
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "MemoryBacktestAdapter.dispose";
48159
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE = "MemoryBacktestAdapter.writeMemory";
48160
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH = "MemoryBacktestAdapter.searchMemory";
48161
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST = "MemoryBacktestAdapter.listMemory";
48162
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE = "MemoryBacktestAdapter.removeMemory";
48163
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ = "MemoryBacktestAdapter.readMemory";
48164
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryBacktestAdapter.useLocal";
48165
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryBacktestAdapter.usePersist";
48166
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryBacktestAdapter.useDummy";
48167
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryBacktestAdapter.useMemoryAdapter";
48168
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "MemoryBacktestAdapter.clear";
48169
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "MemoryLiveAdapter.dispose";
48170
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE = "MemoryLiveAdapter.writeMemory";
48171
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH = "MemoryLiveAdapter.searchMemory";
48172
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST = "MemoryLiveAdapter.listMemory";
48173
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE = "MemoryLiveAdapter.removeMemory";
48174
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_READ = "MemoryLiveAdapter.readMemory";
48175
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryLiveAdapter.useLocal";
48176
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryLiveAdapter.usePersist";
48177
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryLiveAdapter.useDummy";
48178
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryLiveAdapter.useMemoryAdapter";
48179
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR = "MemoryLiveAdapter.clear";
47245
48180
  const MEMORY_ADAPTER_METHOD_NAME_ENABLE = "MemoryAdapter.enable";
47246
48181
  const MEMORY_ADAPTER_METHOD_NAME_DISABLE = "MemoryAdapter.disable";
47247
- const MEMORY_ADAPTER_METHOD_NAME_DISPOSE = "MemoryAdapter.dispose";
47248
48182
  const MEMORY_ADAPTER_METHOD_NAME_WRITE = "MemoryAdapter.writeMemory";
47249
48183
  const MEMORY_ADAPTER_METHOD_NAME_SEARCH = "MemoryAdapter.searchMemory";
47250
48184
  const MEMORY_ADAPTER_METHOD_NAME_LIST = "MemoryAdapter.listMemory";
47251
48185
  const MEMORY_ADAPTER_METHOD_NAME_REMOVE = "MemoryAdapter.removeMemory";
47252
48186
  const MEMORY_ADAPTER_METHOD_NAME_READ = "MemoryAdapter.readMemory";
47253
- const MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryAdapter.useLocal";
47254
- const MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryAdapter.usePersist";
47255
- const MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryAdapter.useDummy";
47256
- const MEMORY_ADAPTER_METHOD_NAME_CLEAR = "MemoryAdapter.clear";
47257
48187
  /**
47258
48188
  * In-memory BM25 search index backed instance.
47259
48189
  * All data lives in the process memory only - no disk persistence.
@@ -47278,7 +48208,7 @@ class MemoryLocalInstance {
47278
48208
  * Write a value into the BM25 index.
47279
48209
  * @param memoryId - Unique entry identifier
47280
48210
  * @param value - Value to store and index
47281
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
48211
+ * @param description - BM25 index string
47282
48212
  */
47283
48213
  async writeMemory(memoryId, value, description) {
47284
48214
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
@@ -47349,7 +48279,7 @@ class MemoryLocalInstance {
47349
48279
  }
47350
48280
  /** Releases resources held by this instance. */
47351
48281
  dispose() {
47352
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
48282
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47353
48283
  signalId: this.signalId,
47354
48284
  bucketName: this.bucketName,
47355
48285
  });
@@ -47398,7 +48328,7 @@ class MemoryPersistInstance {
47398
48328
  * Write a value to disk and update the BM25 index.
47399
48329
  * @param memoryId - Unique entry identifier
47400
48330
  * @param value - Value to persist and index
47401
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
48331
+ * @param index - BM25 index string; defaults to JSON.stringify(value)
47402
48332
  */
47403
48333
  async writeMemory(memoryId, value, index = JSON.stringify(value)) {
47404
48334
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
@@ -47472,7 +48402,7 @@ class MemoryPersistInstance {
47472
48402
  }
47473
48403
  /** Releases resources held by this instance. */
47474
48404
  dispose() {
47475
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
48405
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47476
48406
  signalId: this.signalId,
47477
48407
  bucketName: this.bucketName,
47478
48408
  });
@@ -47532,48 +48462,377 @@ class MemoryDummyInstance {
47532
48462
  }
47533
48463
  }
47534
48464
  /**
47535
- * Facade for memory instances scoped per (signalId, bucketName).
47536
- * Manages lazy initialization and instance lifecycle.
48465
+ * Backtest memory adapter with pluggable storage backend.
47537
48466
  *
47538
48467
  * Features:
47539
- * - Memoized instances per (signalId, bucketName) pair
47540
- * - Swappable backend via useLocal(), usePersist(), useDummy()
47541
- * - Default backend: MemoryPersistInstance (in-memory BM25 + persist storage)
48468
+ * - Adapter pattern for swappable memory instance implementations
48469
+ * - Default backend: MemoryLocalInstance (in-memory BM25, no disk persistence)
48470
+ * - Alternative backends: MemoryPersistInstance, MemoryDummyInstance
48471
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
48472
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
48473
+ *
48474
+ * Use this adapter for backtest memory storage.
47542
48475
  */
47543
- class MemoryAdapter {
48476
+ class MemoryBacktestAdapter {
48477
+ constructor() {
48478
+ this.MemoryFactory = MemoryLocalInstance;
48479
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
48480
+ /**
48481
+ * Disposes all memoized instances for the given signalId.
48482
+ * Called by MemoryAdapter when a signal is cancelled or closed.
48483
+ * @param signalId - Signal identifier to dispose
48484
+ */
48485
+ this.disposeSignal = (signalId) => {
48486
+ const prefix = CREATE_KEY_FN$4(signalId, "");
48487
+ for (const key of this.getInstance.keys()) {
48488
+ if (key.startsWith(prefix)) {
48489
+ const instance = this.getInstance.get(key);
48490
+ instance && instance.dispose();
48491
+ this.getInstance.clear(key);
48492
+ }
48493
+ }
48494
+ };
48495
+ /**
48496
+ * Write a value to memory.
48497
+ * @param dto.memoryId - Unique entry identifier
48498
+ * @param dto.value - Value to store
48499
+ * @param dto.signalId - Signal identifier
48500
+ * @param dto.bucketName - Bucket name
48501
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48502
+ */
48503
+ this.writeMemory = async (dto) => {
48504
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
48505
+ signalId: dto.signalId,
48506
+ bucketName: dto.bucketName,
48507
+ memoryId: dto.memoryId,
48508
+ });
48509
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48510
+ const isInitial = !this.getInstance.has(key);
48511
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48512
+ await instance.waitForInit(isInitial);
48513
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48514
+ };
48515
+ /**
48516
+ * Search memory using BM25 full-text scoring.
48517
+ * @param dto.query - Search query string
48518
+ * @param dto.signalId - Signal identifier
48519
+ * @param dto.bucketName - Bucket name
48520
+ * @returns Matching entries sorted by relevance score
48521
+ */
48522
+ this.searchMemory = async (dto) => {
48523
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH, {
48524
+ signalId: dto.signalId,
48525
+ bucketName: dto.bucketName,
48526
+ query: dto.query,
48527
+ });
48528
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48529
+ const isInitial = !this.getInstance.has(key);
48530
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48531
+ await instance.waitForInit(isInitial);
48532
+ return await instance.searchMemory(dto.query, dto.settings);
48533
+ };
48534
+ /**
48535
+ * List all entries in memory.
48536
+ * @param dto.signalId - Signal identifier
48537
+ * @param dto.bucketName - Bucket name
48538
+ * @returns Array of all stored entries
48539
+ */
48540
+ this.listMemory = async (dto) => {
48541
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST, {
48542
+ signalId: dto.signalId,
48543
+ bucketName: dto.bucketName,
48544
+ });
48545
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48546
+ const isInitial = !this.getInstance.has(key);
48547
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48548
+ await instance.waitForInit(isInitial);
48549
+ return await instance.listMemory();
48550
+ };
48551
+ /**
48552
+ * Remove an entry from memory.
48553
+ * @param dto.memoryId - Unique entry identifier
48554
+ * @param dto.signalId - Signal identifier
48555
+ * @param dto.bucketName - Bucket name
48556
+ */
48557
+ this.removeMemory = async (dto) => {
48558
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
48559
+ signalId: dto.signalId,
48560
+ bucketName: dto.bucketName,
48561
+ memoryId: dto.memoryId,
48562
+ });
48563
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48564
+ const isInitial = !this.getInstance.has(key);
48565
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48566
+ await instance.waitForInit(isInitial);
48567
+ return await instance.removeMemory(dto.memoryId);
48568
+ };
48569
+ /**
48570
+ * Read a single entry from memory.
48571
+ * @param dto.memoryId - Unique entry identifier
48572
+ * @param dto.signalId - Signal identifier
48573
+ * @param dto.bucketName - Bucket name
48574
+ * @returns Entry value
48575
+ * @throws Error if entry not found
48576
+ */
48577
+ this.readMemory = async (dto) => {
48578
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ, {
48579
+ signalId: dto.signalId,
48580
+ bucketName: dto.bucketName,
48581
+ memoryId: dto.memoryId,
48582
+ });
48583
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48584
+ const isInitial = !this.getInstance.has(key);
48585
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48586
+ await instance.waitForInit(isInitial);
48587
+ return await instance.readMemory(dto.memoryId);
48588
+ };
48589
+ /**
48590
+ * Switches to in-memory BM25 adapter (default).
48591
+ * All data lives in process memory only.
48592
+ */
48593
+ this.useLocal = () => {
48594
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
48595
+ this.MemoryFactory = MemoryLocalInstance;
48596
+ };
48597
+ /**
48598
+ * Switches to file-system backed adapter.
48599
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
48600
+ */
48601
+ this.usePersist = () => {
48602
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
48603
+ this.MemoryFactory = MemoryPersistInstance;
48604
+ };
48605
+ /**
48606
+ * Switches to dummy adapter that discards all writes.
48607
+ */
48608
+ this.useDummy = () => {
48609
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
48610
+ this.MemoryFactory = MemoryDummyInstance;
48611
+ };
48612
+ /**
48613
+ * Switches to a custom memory adapter implementation.
48614
+ * @param Ctor - Constructor for the custom memory instance
48615
+ */
48616
+ this.useMemoryAdapter = (Ctor) => {
48617
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
48618
+ this.MemoryFactory = Ctor;
48619
+ };
48620
+ /**
48621
+ * Clears the memoized instance cache.
48622
+ * Call this when process.cwd() changes between strategy iterations
48623
+ * so new instances are created with the updated base path.
48624
+ */
48625
+ this.clear = () => {
48626
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
48627
+ this.getInstance.clear();
48628
+ };
48629
+ }
48630
+ }
48631
+ /**
48632
+ * Live trading memory adapter with pluggable storage backend.
48633
+ *
48634
+ * Features:
48635
+ * - Adapter pattern for swappable memory instance implementations
48636
+ * - Default backend: MemoryPersistInstance (file-system backed, survives restarts)
48637
+ * - Alternative backends: MemoryLocalInstance, MemoryDummyInstance
48638
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
48639
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
48640
+ *
48641
+ * Use this adapter for live trading memory storage.
48642
+ */
48643
+ class MemoryLiveAdapter {
47544
48644
  constructor() {
47545
48645
  this.MemoryFactory = MemoryPersistInstance;
47546
48646
  this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
47547
48647
  /**
47548
- * Activates the adapter by subscribing to signal lifecycle events.
47549
- * Clears memoized instances for a signalId when it is cancelled or closed,
47550
- * preventing stale instances from accumulating in memory.
47551
- * Idempotent — subsequent calls return the same subscription handle.
47552
- * Must be called before any memory method is used.
48648
+ * Disposes all memoized instances for the given signalId.
48649
+ * Called by MemoryAdapter when a signal is cancelled or closed.
48650
+ * @param signalId - Signal identifier to dispose
48651
+ */
48652
+ this.disposeSignal = (signalId) => {
48653
+ const prefix = CREATE_KEY_FN$4(signalId, "");
48654
+ for (const key of this.getInstance.keys()) {
48655
+ if (key.startsWith(prefix)) {
48656
+ const instance = this.getInstance.get(key);
48657
+ instance && instance.dispose();
48658
+ this.getInstance.clear(key);
48659
+ }
48660
+ }
48661
+ };
48662
+ /**
48663
+ * Write a value to memory.
48664
+ * @param dto.memoryId - Unique entry identifier
48665
+ * @param dto.value - Value to store
48666
+ * @param dto.signalId - Signal identifier
48667
+ * @param dto.bucketName - Bucket name
48668
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48669
+ */
48670
+ this.writeMemory = async (dto) => {
48671
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
48672
+ signalId: dto.signalId,
48673
+ bucketName: dto.bucketName,
48674
+ memoryId: dto.memoryId,
48675
+ });
48676
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48677
+ const isInitial = !this.getInstance.has(key);
48678
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48679
+ await instance.waitForInit(isInitial);
48680
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48681
+ };
48682
+ /**
48683
+ * Search memory using BM25 full-text scoring.
48684
+ * @param dto.query - Search query string
48685
+ * @param dto.signalId - Signal identifier
48686
+ * @param dto.bucketName - Bucket name
48687
+ * @returns Matching entries sorted by relevance score
48688
+ */
48689
+ this.searchMemory = async (dto) => {
48690
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH, {
48691
+ signalId: dto.signalId,
48692
+ bucketName: dto.bucketName,
48693
+ query: dto.query,
48694
+ });
48695
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48696
+ const isInitial = !this.getInstance.has(key);
48697
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48698
+ await instance.waitForInit(isInitial);
48699
+ return await instance.searchMemory(dto.query, dto.settings);
48700
+ };
48701
+ /**
48702
+ * List all entries in memory.
48703
+ * @param dto.signalId - Signal identifier
48704
+ * @param dto.bucketName - Bucket name
48705
+ * @returns Array of all stored entries
48706
+ */
48707
+ this.listMemory = async (dto) => {
48708
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST, {
48709
+ signalId: dto.signalId,
48710
+ bucketName: dto.bucketName,
48711
+ });
48712
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48713
+ const isInitial = !this.getInstance.has(key);
48714
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48715
+ await instance.waitForInit(isInitial);
48716
+ return await instance.listMemory();
48717
+ };
48718
+ /**
48719
+ * Remove an entry from memory.
48720
+ * @param dto.memoryId - Unique entry identifier
48721
+ * @param dto.signalId - Signal identifier
48722
+ * @param dto.bucketName - Bucket name
48723
+ */
48724
+ this.removeMemory = async (dto) => {
48725
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
48726
+ signalId: dto.signalId,
48727
+ bucketName: dto.bucketName,
48728
+ memoryId: dto.memoryId,
48729
+ });
48730
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48731
+ const isInitial = !this.getInstance.has(key);
48732
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48733
+ await instance.waitForInit(isInitial);
48734
+ return await instance.removeMemory(dto.memoryId);
48735
+ };
48736
+ /**
48737
+ * Read a single entry from memory.
48738
+ * @param dto.memoryId - Unique entry identifier
48739
+ * @param dto.signalId - Signal identifier
48740
+ * @param dto.bucketName - Bucket name
48741
+ * @returns Entry value
48742
+ * @throws Error if entry not found
48743
+ */
48744
+ this.readMemory = async (dto) => {
48745
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_READ, {
48746
+ signalId: dto.signalId,
48747
+ bucketName: dto.bucketName,
48748
+ memoryId: dto.memoryId,
48749
+ });
48750
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48751
+ const isInitial = !this.getInstance.has(key);
48752
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48753
+ await instance.waitForInit(isInitial);
48754
+ return await instance.readMemory(dto.memoryId);
48755
+ };
48756
+ /**
48757
+ * Switches to in-memory BM25 adapter.
48758
+ * All data lives in process memory only.
48759
+ */
48760
+ this.useLocal = () => {
48761
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
48762
+ this.MemoryFactory = MemoryLocalInstance;
48763
+ };
48764
+ /**
48765
+ * Switches to file-system backed adapter (default).
48766
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
48767
+ */
48768
+ this.usePersist = () => {
48769
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
48770
+ this.MemoryFactory = MemoryPersistInstance;
48771
+ };
48772
+ /**
48773
+ * Switches to dummy adapter that discards all writes.
48774
+ */
48775
+ this.useDummy = () => {
48776
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
48777
+ this.MemoryFactory = MemoryDummyInstance;
48778
+ };
48779
+ /**
48780
+ * Switches to a custom memory adapter implementation.
48781
+ * @param Ctor - Constructor for the custom memory instance
48782
+ */
48783
+ this.useMemoryAdapter = (Ctor) => {
48784
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
48785
+ this.MemoryFactory = Ctor;
48786
+ };
48787
+ /**
48788
+ * Clears the memoized instance cache.
48789
+ * Call this when process.cwd() changes between strategy iterations
48790
+ * so new instances are created with the updated base path.
48791
+ */
48792
+ this.clear = () => {
48793
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR);
48794
+ this.getInstance.clear();
48795
+ };
48796
+ }
48797
+ }
48798
+ /**
48799
+ * Main memory adapter that manages both backtest and live memory storage.
48800
+ *
48801
+ * Features:
48802
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
48803
+ * - Routes all operations to MemoryBacktest or MemoryLive based on dto.backtest
48804
+ * - Singleshot enable pattern prevents duplicate subscriptions
48805
+ * - Cleanup function for proper unsubscription
48806
+ */
48807
+ class MemoryAdapter {
48808
+ constructor() {
48809
+ /**
48810
+ * Enables memory storage by subscribing to signal lifecycle events.
48811
+ * Clears memoized instances in MemoryBacktest and MemoryLive when a signal
48812
+ * is cancelled or closed, preventing stale instances from accumulating.
48813
+ * Uses singleshot to ensure one-time subscription.
48814
+ *
48815
+ * @returns Cleanup function that unsubscribes from all emitters
47553
48816
  */
47554
48817
  this.enable = functoolsKit.singleshot(() => {
47555
48818
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
47556
- const handleDispose = (signalId) => {
47557
- const prefix = CREATE_KEY_FN$4(signalId, "");
47558
- for (const key of this.getInstance.keys()) {
47559
- if (key.startsWith(prefix)) {
47560
- const instance = this.getInstance.get(key);
47561
- instance && instance.dispose();
47562
- this.getInstance.clear(key);
47563
- }
47564
- }
47565
- };
47566
48819
  const unCancel = signalEmitter
47567
48820
  .filter(({ action }) => action === "cancelled")
47568
- .connect(({ signal }) => handleDispose(signal.id));
48821
+ .connect(({ signal }) => {
48822
+ MemoryBacktest.disposeSignal(signal.id);
48823
+ MemoryLive.disposeSignal(signal.id);
48824
+ });
47569
48825
  const unClose = signalEmitter
47570
48826
  .filter(({ action }) => action === "closed")
47571
- .connect(({ signal }) => handleDispose(signal.id));
47572
- return functoolsKit.compose(() => unCancel(), () => unClose());
48827
+ .connect(({ signal }) => {
48828
+ MemoryBacktest.disposeSignal(signal.id);
48829
+ MemoryLive.disposeSignal(signal.id);
48830
+ });
48831
+ return functoolsKit.compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47573
48832
  });
47574
48833
  /**
47575
- * Deactivates the adapter by unsubscribing from signal lifecycle events.
47576
- * No-op if enable() was never called.
48834
+ * Disables memory storage by unsubscribing from signal lifecycle events.
48835
+ * Safe to call multiple times.
47577
48836
  */
47578
48837
  this.disable = () => {
47579
48838
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISABLE);
@@ -47584,11 +48843,13 @@ class MemoryAdapter {
47584
48843
  };
47585
48844
  /**
47586
48845
  * Write a value to memory.
48846
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47587
48847
  * @param dto.memoryId - Unique entry identifier
47588
48848
  * @param dto.value - Value to store
47589
48849
  * @param dto.signalId - Signal identifier
47590
48850
  * @param dto.bucketName - Bucket name
47591
- * @param dto.description - Optional BM25 index string; defaults to JSON.stringify(value)
48851
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48852
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47592
48853
  */
47593
48854
  this.writeMemory = async (dto) => {
47594
48855
  if (!this.enable.hasValue()) {
@@ -47598,18 +48859,20 @@ class MemoryAdapter {
47598
48859
  signalId: dto.signalId,
47599
48860
  bucketName: dto.bucketName,
47600
48861
  memoryId: dto.memoryId,
48862
+ backtest: dto.backtest,
47601
48863
  });
47602
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47603
- const isInitial = !this.getInstance.has(key);
47604
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47605
- await instance.waitForInit(isInitial);
47606
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48864
+ if (dto.backtest) {
48865
+ return await MemoryBacktest.writeMemory(dto);
48866
+ }
48867
+ return await MemoryLive.writeMemory(dto);
47607
48868
  };
47608
48869
  /**
47609
48870
  * Search memory using BM25 full-text scoring.
48871
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47610
48872
  * @param dto.query - Search query string
47611
48873
  * @param dto.signalId - Signal identifier
47612
48874
  * @param dto.bucketName - Bucket name
48875
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47613
48876
  * @returns Matching entries sorted by relevance score
47614
48877
  */
47615
48878
  this.searchMemory = async (dto) => {
@@ -47620,17 +48883,19 @@ class MemoryAdapter {
47620
48883
  signalId: dto.signalId,
47621
48884
  bucketName: dto.bucketName,
47622
48885
  query: dto.query,
48886
+ backtest: dto.backtest,
47623
48887
  });
47624
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47625
- const isInitial = !this.getInstance.has(key);
47626
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47627
- await instance.waitForInit(isInitial);
47628
- return await instance.searchMemory(dto.query, dto.settings);
48888
+ if (dto.backtest) {
48889
+ return await MemoryBacktest.searchMemory(dto);
48890
+ }
48891
+ return await MemoryLive.searchMemory(dto);
47629
48892
  };
47630
48893
  /**
47631
48894
  * List all entries in memory.
48895
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47632
48896
  * @param dto.signalId - Signal identifier
47633
48897
  * @param dto.bucketName - Bucket name
48898
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47634
48899
  * @returns Array of all stored entries
47635
48900
  */
47636
48901
  this.listMemory = async (dto) => {
@@ -47640,18 +48905,20 @@ class MemoryAdapter {
47640
48905
  backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_LIST, {
47641
48906
  signalId: dto.signalId,
47642
48907
  bucketName: dto.bucketName,
48908
+ backtest: dto.backtest,
47643
48909
  });
47644
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47645
- const isInitial = !this.getInstance.has(key);
47646
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47647
- await instance.waitForInit(isInitial);
47648
- return await instance.listMemory();
48910
+ if (dto.backtest) {
48911
+ return await MemoryBacktest.listMemory(dto);
48912
+ }
48913
+ return await MemoryLive.listMemory(dto);
47649
48914
  };
47650
48915
  /**
47651
48916
  * Remove an entry from memory.
48917
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47652
48918
  * @param dto.memoryId - Unique entry identifier
47653
48919
  * @param dto.signalId - Signal identifier
47654
48920
  * @param dto.bucketName - Bucket name
48921
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47655
48922
  */
47656
48923
  this.removeMemory = async (dto) => {
47657
48924
  if (!this.enable.hasValue()) {
@@ -47661,18 +48928,20 @@ class MemoryAdapter {
47661
48928
  signalId: dto.signalId,
47662
48929
  bucketName: dto.bucketName,
47663
48930
  memoryId: dto.memoryId,
48931
+ backtest: dto.backtest,
47664
48932
  });
47665
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47666
- const isInitial = !this.getInstance.has(key);
47667
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47668
- await instance.waitForInit(isInitial);
47669
- return await instance.removeMemory(dto.memoryId);
48933
+ if (dto.backtest) {
48934
+ return await MemoryBacktest.removeMemory(dto);
48935
+ }
48936
+ return await MemoryLive.removeMemory(dto);
47670
48937
  };
47671
48938
  /**
47672
48939
  * Read a single entry from memory.
48940
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47673
48941
  * @param dto.memoryId - Unique entry identifier
47674
48942
  * @param dto.signalId - Signal identifier
47675
48943
  * @param dto.bucketName - Bucket name
48944
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47676
48945
  * @returns Entry value
47677
48946
  * @throws Error if entry not found
47678
48947
  */
@@ -47684,56 +48953,30 @@ class MemoryAdapter {
47684
48953
  signalId: dto.signalId,
47685
48954
  bucketName: dto.bucketName,
47686
48955
  memoryId: dto.memoryId,
48956
+ backtest: dto.backtest,
47687
48957
  });
47688
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47689
- const isInitial = !this.getInstance.has(key);
47690
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47691
- await instance.waitForInit(isInitial);
47692
- return await instance.readMemory(dto.memoryId);
47693
- };
47694
- /**
47695
- * Switches to in-memory BM25 adapter (default).
47696
- * All data lives in process memory only.
47697
- */
47698
- this.useLocal = () => {
47699
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL);
47700
- this.MemoryFactory = MemoryLocalInstance;
47701
- };
47702
- /**
47703
- * Switches to file-system backed adapter.
47704
- * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
47705
- */
47706
- this.usePersist = () => {
47707
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST);
47708
- this.MemoryFactory = MemoryPersistInstance;
47709
- };
47710
- /**
47711
- * Switches to dummy adapter that discards all writes.
47712
- */
47713
- this.useDummy = () => {
47714
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY);
47715
- this.MemoryFactory = MemoryDummyInstance;
47716
- };
47717
- /**
47718
- * Clears the memoized instance cache.
47719
- * Call this when process.cwd() changes between strategy iterations
47720
- * so new instances are created with the updated base path.
47721
- */
47722
- this.clear = () => {
47723
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_CLEAR);
47724
- this.getInstance.clear();
47725
- };
47726
- /**
47727
- * Releases resources held by this adapter.
47728
- * Delegates to disable() to unsubscribe from signal lifecycle events.
47729
- */
47730
- this.dispose = () => {
47731
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISPOSE);
47732
- this.disable();
48958
+ if (dto.backtest) {
48959
+ return await MemoryBacktest.readMemory(dto);
48960
+ }
48961
+ return await MemoryLive.readMemory(dto);
47733
48962
  };
47734
48963
  }
47735
48964
  }
48965
+ /**
48966
+ * Global singleton instance of MemoryAdapter.
48967
+ * Provides unified memory management for backtest and live trading.
48968
+ */
47736
48969
  const Memory = new MemoryAdapter();
48970
+ /**
48971
+ * Global singleton instance of MemoryLiveAdapter.
48972
+ * Provides live trading memory storage with pluggable backends.
48973
+ */
48974
+ const MemoryLive = new MemoryLiveAdapter();
48975
+ /**
48976
+ * Global singleton instance of MemoryBacktestAdapter.
48977
+ * Provides backtest memory storage with pluggable backends.
48978
+ */
48979
+ const MemoryBacktest = new MemoryBacktestAdapter();
47737
48980
 
47738
48981
  const WRITE_MEMORY_METHOD_NAME = "memory.writeMemory";
47739
48982
  const READ_MEMORY_METHOD_NAME = "memory.readMemory";
@@ -47743,23 +48986,20 @@ const REMOVE_MEMORY_METHOD_NAME = "memory.removeMemory";
47743
48986
  /**
47744
48987
  * Writes a value to memory scoped to the current signal.
47745
48988
  *
47746
- * Reads symbol from execution context and signalId from the active pending signal.
47747
- * If no pending signal exists, logs a warning and returns without writing.
47748
- *
48989
+ * Resolves the active pending or scheduled signal automatically from execution context.
47749
48990
  * Automatically detects backtest/live mode from execution context.
47750
48991
  *
47751
48992
  * @param dto.bucketName - Memory bucket name
47752
48993
  * @param dto.memoryId - Unique memory entry identifier
47753
48994
  * @param dto.value - Value to store
48995
+ * @param dto.description - BM25 index string for contextual search
47754
48996
  * @returns Promise that resolves when write is complete
47755
48997
  *
47756
- * @deprecated Better use Memory.writeMemory with manual signalId argument
47757
- *
47758
48998
  * @example
47759
48999
  * ```typescript
47760
49000
  * import { writeMemory } from "backtest-kit";
47761
49001
  *
47762
- * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 } });
49002
+ * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 }, description: "Signal context at entry" });
47763
49003
  * ```
47764
49004
  */
47765
49005
  async function writeMemory(dto) {
@@ -47777,33 +49017,41 @@ async function writeMemory(dto) {
47777
49017
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47778
49018
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47779
49019
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47780
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47781
- if (!signal) {
47782
- console.warn(`backtest-kit writeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49020
+ let signal;
49021
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49022
+ await Memory.writeMemory({
49023
+ memoryId,
49024
+ value,
49025
+ signalId: signal.id,
49026
+ bucketName,
49027
+ description,
49028
+ backtest: isBacktest,
49029
+ });
47783
49030
  return;
47784
49031
  }
47785
- await Memory.writeMemory({
47786
- memoryId,
47787
- value,
47788
- signalId: signal.id,
47789
- bucketName,
47790
- description,
47791
- });
49032
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49033
+ await Memory.writeMemory({
49034
+ memoryId,
49035
+ value,
49036
+ signalId: signal.id,
49037
+ bucketName,
49038
+ description,
49039
+ backtest: isBacktest,
49040
+ });
49041
+ return;
49042
+ }
49043
+ throw new Error(`writeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47792
49044
  }
47793
49045
  /**
47794
49046
  * Reads a value from memory scoped to the current signal.
47795
49047
  *
47796
- * Reads symbol from execution context and signalId from the active pending signal.
47797
- * If no pending signal exists, logs a warning and returns null.
47798
- *
49048
+ * Resolves the active pending or scheduled signal automatically from execution context.
47799
49049
  * Automatically detects backtest/live mode from execution context.
47800
49050
  *
47801
49051
  * @param dto.bucketName - Memory bucket name
47802
49052
  * @param dto.memoryId - Unique memory entry identifier
47803
- * @returns Promise resolving to stored value or null if no signal
47804
- * @throws Error if entry not found within an active signal
47805
- *
47806
- * @deprecated Better use Memory.readMemory with manual signalId argument
49053
+ * @returns Promise resolving to stored value
49054
+ * @throws Error if no pending or scheduled signal exists, or if entry not found
47807
49055
  *
47808
49056
  * @example
47809
49057
  * ```typescript
@@ -47827,30 +49075,35 @@ async function readMemory(dto) {
47827
49075
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47828
49076
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47829
49077
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47830
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47831
- if (!signal) {
47832
- console.warn(`backtest-kit readMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
47833
- return null;
49078
+ let signal;
49079
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49080
+ return await Memory.readMemory({
49081
+ memoryId,
49082
+ signalId: signal.id,
49083
+ bucketName,
49084
+ backtest: isBacktest,
49085
+ });
47834
49086
  }
47835
- return await Memory.readMemory({
47836
- memoryId,
47837
- signalId: signal.id,
47838
- bucketName,
47839
- });
49087
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49088
+ return await Memory.readMemory({
49089
+ memoryId,
49090
+ signalId: signal.id,
49091
+ bucketName,
49092
+ backtest: isBacktest,
49093
+ });
49094
+ }
49095
+ throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47840
49096
  }
47841
49097
  /**
47842
49098
  * Searches memory entries for the current signal using BM25 full-text scoring.
47843
49099
  *
47844
- * Reads symbol from execution context and signalId from the active pending signal.
47845
- * If no pending signal exists, logs a warning and returns an empty array.
47846
- *
49100
+ * Resolves the active pending or scheduled signal automatically from execution context.
47847
49101
  * Automatically detects backtest/live mode from execution context.
47848
49102
  *
47849
49103
  * @param dto.bucketName - Memory bucket name
47850
49104
  * @param dto.query - Search query string
47851
- * @returns Promise resolving to matching entries sorted by relevance, or empty array if no signal
47852
- *
47853
- * @deprecated Better use Memory.searchMemory with manual signalId argument
49105
+ * @returns Promise resolving to matching entries sorted by relevance
49106
+ * @throws Error if no pending or scheduled signal exists
47854
49107
  *
47855
49108
  * @example
47856
49109
  * ```typescript
@@ -47874,29 +49127,34 @@ async function searchMemory(dto) {
47874
49127
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47875
49128
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47876
49129
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47877
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47878
- if (!signal) {
47879
- console.warn(`backtest-kit searchMemory no pending signal for symbol=${symbol} query=${query}`);
47880
- return [];
49130
+ let signal;
49131
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49132
+ return await Memory.searchMemory({
49133
+ query,
49134
+ signalId: signal.id,
49135
+ bucketName,
49136
+ backtest: isBacktest,
49137
+ });
47881
49138
  }
47882
- return await Memory.searchMemory({
47883
- query,
47884
- signalId: signal.id,
47885
- bucketName,
47886
- });
49139
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49140
+ return await Memory.searchMemory({
49141
+ query,
49142
+ signalId: signal.id,
49143
+ bucketName,
49144
+ backtest: isBacktest,
49145
+ });
49146
+ }
49147
+ throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
47887
49148
  }
47888
49149
  /**
47889
49150
  * Lists all memory entries for the current signal.
47890
49151
  *
47891
- * Reads symbol from execution context and signalId from the active pending signal.
47892
- * If no pending signal exists, logs a warning and returns an empty array.
47893
- *
49152
+ * Resolves the active pending or scheduled signal automatically from execution context.
47894
49153
  * Automatically detects backtest/live mode from execution context.
47895
49154
  *
47896
49155
  * @param dto.bucketName - Memory bucket name
47897
- * @returns Promise resolving to all stored entries, or empty array if no signal
47898
- *
47899
- * @deprecated Better use Memory.listMemory with manual signalId argument
49156
+ * @returns Promise resolving to all stored entries
49157
+ * @throws Error if no pending or scheduled signal exists
47900
49158
  *
47901
49159
  * @example
47902
49160
  * ```typescript
@@ -47919,29 +49177,33 @@ async function listMemory(dto) {
47919
49177
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47920
49178
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47921
49179
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47922
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47923
- if (!signal) {
47924
- console.warn(`backtest-kit listMemory no pending signal for symbol=${symbol}`);
47925
- return [];
49180
+ let signal;
49181
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49182
+ return await Memory.listMemory({
49183
+ signalId: signal.id,
49184
+ bucketName,
49185
+ backtest: isBacktest,
49186
+ });
47926
49187
  }
47927
- return await Memory.listMemory({
47928
- signalId: signal.id,
47929
- bucketName,
47930
- });
49188
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49189
+ return await Memory.listMemory({
49190
+ signalId: signal.id,
49191
+ bucketName,
49192
+ backtest: isBacktest,
49193
+ });
49194
+ }
49195
+ throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47931
49196
  }
47932
49197
  /**
47933
49198
  * Removes a memory entry for the current signal.
47934
49199
  *
47935
- * Reads symbol from execution context and signalId from the active pending signal.
47936
- * If no pending signal exists, logs a warning and returns without removing.
47937
- *
49200
+ * Resolves the active pending or scheduled signal automatically from execution context.
47938
49201
  * Automatically detects backtest/live mode from execution context.
47939
49202
  *
47940
49203
  * @param dto.bucketName - Memory bucket name
47941
49204
  * @param dto.memoryId - Unique memory entry identifier
47942
49205
  * @returns Promise that resolves when removal is complete
47943
- *
47944
- * @deprecated Better use Memory.removeMemory with manual signalId argument
49206
+ * @throws Error if no pending or scheduled signal exists
47945
49207
  *
47946
49208
  * @example
47947
49209
  * ```typescript
@@ -47965,16 +49227,26 @@ async function removeMemory(dto) {
47965
49227
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47966
49228
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47967
49229
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47968
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47969
- if (!signal) {
47970
- console.warn(`backtest-kit removeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49230
+ let signal;
49231
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49232
+ await Memory.removeMemory({
49233
+ memoryId,
49234
+ signalId: signal.id,
49235
+ bucketName,
49236
+ backtest: isBacktest,
49237
+ });
47971
49238
  return;
47972
49239
  }
47973
- await Memory.removeMemory({
47974
- memoryId,
47975
- signalId: signal.id,
47976
- bucketName,
47977
- });
49240
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49241
+ await Memory.removeMemory({
49242
+ memoryId,
49243
+ signalId: signal.id,
49244
+ bucketName,
49245
+ backtest: isBacktest,
49246
+ });
49247
+ return;
49248
+ }
49249
+ throw new Error(`removeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47978
49250
  }
47979
49251
 
47980
49252
  const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
@@ -48069,11 +49341,12 @@ const RENDER_TABLE_FN = (rows) => {
48069
49341
  * Scoped to (signalId, bucketName) via constructor.
48070
49342
  */
48071
49343
  class DumpBothInstance {
48072
- constructor(signalId, bucketName) {
49344
+ constructor(signalId, bucketName, backtest) {
48073
49345
  this.signalId = signalId;
48074
49346
  this.bucketName = bucketName;
48075
- this._memory = new DumpMemoryInstance(signalId, bucketName);
48076
- this._markdown = new DumpMarkdownInstance(signalId, bucketName);
49347
+ this.backtest = backtest;
49348
+ this._memory = new DumpMemoryInstance(signalId, bucketName, backtest);
49349
+ this._markdown = new DumpMarkdownInstance(signalId, bucketName, backtest);
48077
49350
  }
48078
49351
  /** Releases resources held by both backends. */
48079
49352
  dispose() {
@@ -48205,9 +49478,10 @@ class DumpBothInstance {
48205
49478
  * Useful for downstream LLM retrieval via Memory.searchMemory.
48206
49479
  */
48207
49480
  class DumpMemoryInstance {
48208
- constructor(signalId, bucketName) {
49481
+ constructor(signalId, bucketName, backtest) {
48209
49482
  this.signalId = signalId;
48210
49483
  this.bucketName = bucketName;
49484
+ this.backtest = backtest;
48211
49485
  }
48212
49486
  /**
48213
49487
  * Stores the full agent message history in Memory as a `{ messages }` object.
@@ -48233,6 +49507,7 @@ class DumpMemoryInstance {
48233
49507
  signalId: this.signalId,
48234
49508
  value: { messages },
48235
49509
  description,
49510
+ backtest: this.backtest,
48236
49511
  });
48237
49512
  }
48238
49513
  /**
@@ -48254,6 +49529,7 @@ class DumpMemoryInstance {
48254
49529
  signalId: this.signalId,
48255
49530
  value: record,
48256
49531
  description,
49532
+ backtest: this.backtest,
48257
49533
  });
48258
49534
  }
48259
49535
  /**
@@ -48276,6 +49552,7 @@ class DumpMemoryInstance {
48276
49552
  signalId: this.signalId,
48277
49553
  value: { rows },
48278
49554
  description,
49555
+ backtest: this.backtest,
48279
49556
  });
48280
49557
  }
48281
49558
  /**
@@ -48297,6 +49574,7 @@ class DumpMemoryInstance {
48297
49574
  signalId: this.signalId,
48298
49575
  value: { content },
48299
49576
  description,
49577
+ backtest: this.backtest,
48300
49578
  });
48301
49579
  }
48302
49580
  /**
@@ -48318,6 +49596,7 @@ class DumpMemoryInstance {
48318
49596
  signalId: this.signalId,
48319
49597
  value: { content },
48320
49598
  description,
49599
+ backtest: this.backtest,
48321
49600
  });
48322
49601
  }
48323
49602
  /**
@@ -48340,6 +49619,7 @@ class DumpMemoryInstance {
48340
49619
  signalId: this.signalId,
48341
49620
  value: json,
48342
49621
  description,
49622
+ backtest: this.backtest,
48343
49623
  });
48344
49624
  }
48345
49625
  /** Releases resources held by this instance. */
@@ -48361,9 +49641,10 @@ class DumpMemoryInstance {
48361
49641
  * If the file already exists, the call is skipped (idempotent).
48362
49642
  */
48363
49643
  class DumpMarkdownInstance {
48364
- constructor(signalId, bucketName) {
49644
+ constructor(signalId, bucketName, backtest) {
48365
49645
  this.signalId = signalId;
48366
49646
  this.bucketName = bucketName;
49647
+ this.backtest = backtest;
48367
49648
  }
48368
49649
  getFilePath(dumpId) {
48369
49650
  return path.join("./dump/agent", this.signalId, this.bucketName, `${dumpId}.md`);
@@ -48552,9 +49833,10 @@ class DumpMarkdownInstance {
48552
49833
  * Used for disabling dumps in tests or dry-run scenarios.
48553
49834
  */
48554
49835
  class DumpDummyInstance {
48555
- constructor(signalId, bucketName) {
49836
+ constructor(signalId, bucketName, backtest) {
48556
49837
  this.signalId = signalId;
48557
49838
  this.bucketName = bucketName;
49839
+ this.backtest = backtest;
48558
49840
  }
48559
49841
  /** No-op. */
48560
49842
  async dumpAgentAnswer() {
@@ -48597,7 +49879,7 @@ class DumpDummyInstance {
48597
49879
  class DumpAdapter {
48598
49880
  constructor() {
48599
49881
  this.DumpFactory = DumpMarkdownInstance;
48600
- this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
49882
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName, backtest) => Reflect.construct(this.DumpFactory, [signalId, bucketName, backtest]));
48601
49883
  /**
48602
49884
  * Activates the adapter by subscribing to signal lifecycle events.
48603
49885
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -48648,7 +49930,7 @@ class DumpAdapter {
48648
49930
  bucketName: context.bucketName,
48649
49931
  dumpId: context.dumpId,
48650
49932
  });
48651
- const instance = this.getInstance(context.signalId, context.bucketName);
49933
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48652
49934
  return await instance.dumpAgentAnswer(messages, context.dumpId, context.description);
48653
49935
  };
48654
49936
  /**
@@ -48663,7 +49945,7 @@ class DumpAdapter {
48663
49945
  bucketName: context.bucketName,
48664
49946
  dumpId: context.dumpId,
48665
49947
  });
48666
- const instance = this.getInstance(context.signalId, context.bucketName);
49948
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48667
49949
  return await instance.dumpRecord(record, context.dumpId, context.description);
48668
49950
  };
48669
49951
  /**
@@ -48678,7 +49960,7 @@ class DumpAdapter {
48678
49960
  bucketName: context.bucketName,
48679
49961
  dumpId: context.dumpId,
48680
49962
  });
48681
- const instance = this.getInstance(context.signalId, context.bucketName);
49963
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48682
49964
  return await instance.dumpTable(rows, context.dumpId, context.description);
48683
49965
  };
48684
49966
  /**
@@ -48693,7 +49975,7 @@ class DumpAdapter {
48693
49975
  bucketName: context.bucketName,
48694
49976
  dumpId: context.dumpId,
48695
49977
  });
48696
- const instance = this.getInstance(context.signalId, context.bucketName);
49978
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48697
49979
  return await instance.dumpText(content, context.dumpId, context.description);
48698
49980
  };
48699
49981
  /**
@@ -48708,7 +49990,7 @@ class DumpAdapter {
48708
49990
  bucketName: context.bucketName,
48709
49991
  dumpId: context.dumpId,
48710
49992
  });
48711
- const instance = this.getInstance(context.signalId, context.bucketName);
49993
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48712
49994
  return await instance.dumpError(content, context.dumpId, context.description);
48713
49995
  };
48714
49996
  /**
@@ -48724,7 +50006,7 @@ class DumpAdapter {
48724
50006
  bucketName: context.bucketName,
48725
50007
  dumpId: context.dumpId,
48726
50008
  });
48727
- const instance = this.getInstance(context.signalId, context.bucketName);
50009
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48728
50010
  return await instance.dumpJson(json, context.dumpId, context.description);
48729
50011
  };
48730
50012
  /**
@@ -48790,16 +50072,15 @@ const DUMP_JSON_METHOD_NAME = "dump.dumpJson";
48790
50072
  /**
48791
50073
  * Dumps the full agent message history scoped to the current signal.
48792
50074
  *
48793
- * Reads signalId from the active pending signal via execution and method context.
48794
- * If no pending signal exists, logs a warning and returns without writing.
50075
+ * Resolves the active pending or scheduled signal automatically from execution context.
50076
+ * Automatically detects backtest/live mode from execution context.
48795
50077
  *
48796
50078
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48797
50079
  * @param dto.dumpId - Unique identifier for this agent invocation
48798
50080
  * @param dto.messages - Full chat history (system, user, assistant, tool)
48799
50081
  * @param dto.description - Human-readable label describing the agent invocation context; included in the BM25 index for Memory search
48800
50082
  * @returns Promise that resolves when the dump is complete
48801
- *
48802
- * @deprecated Better use Dump.dumpAgentAnswer with manual signalId argument
50083
+ * @throws Error if no pending or scheduled signal exists
48803
50084
  *
48804
50085
  * @example
48805
50086
  * ```typescript
@@ -48824,31 +50105,41 @@ async function dumpAgentAnswer(dto) {
48824
50105
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48825
50106
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48826
50107
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48827
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48828
- if (!signal) {
48829
- console.warn(`backtest-kit dumpAgentAnswer no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50108
+ let signal;
50109
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50110
+ await Dump.dumpAgentAnswer(messages, {
50111
+ dumpId,
50112
+ bucketName,
50113
+ signalId: signal.id,
50114
+ description,
50115
+ backtest: isBacktest,
50116
+ });
48830
50117
  return;
48831
50118
  }
48832
- await Dump.dumpAgentAnswer(messages, {
48833
- dumpId,
48834
- bucketName,
48835
- signalId: signal.id,
48836
- description,
48837
- });
50119
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50120
+ await Dump.dumpAgentAnswer(messages, {
50121
+ dumpId,
50122
+ bucketName,
50123
+ signalId: signal.id,
50124
+ description,
50125
+ backtest: isBacktest,
50126
+ });
50127
+ return;
50128
+ }
50129
+ throw new Error(`dumpAgentAnswer requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48838
50130
  }
48839
50131
  /**
48840
50132
  * Dumps a flat key-value record scoped to the current signal.
48841
50133
  *
48842
- * Reads signalId from the active pending signal via execution and method context.
48843
- * If no pending signal exists, logs a warning and returns without writing.
50134
+ * Resolves the active pending or scheduled signal automatically from execution context.
50135
+ * Automatically detects backtest/live mode from execution context.
48844
50136
  *
48845
50137
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48846
50138
  * @param dto.dumpId - Unique identifier for this dump entry
48847
50139
  * @param dto.record - Arbitrary flat object to persist
48848
50140
  * @param dto.description - Human-readable label describing the record contents; included in the BM25 index for Memory search
48849
50141
  * @returns Promise that resolves when the dump is complete
48850
- *
48851
- * @deprecated Better use Dump.dumpRecord with manual signalId argument
50142
+ * @throws Error if no pending or scheduled signal exists
48852
50143
  *
48853
50144
  * @example
48854
50145
  * ```typescript
@@ -48872,23 +50163,34 @@ async function dumpRecord(dto) {
48872
50163
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48873
50164
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48874
50165
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48875
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48876
- if (!signal) {
48877
- console.warn(`backtest-kit dumpRecord no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50166
+ let signal;
50167
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50168
+ await Dump.dumpRecord(record, {
50169
+ dumpId,
50170
+ bucketName,
50171
+ signalId: signal.id,
50172
+ description,
50173
+ backtest: isBacktest,
50174
+ });
48878
50175
  return;
48879
50176
  }
48880
- await Dump.dumpRecord(record, {
48881
- dumpId,
48882
- bucketName,
48883
- signalId: signal.id,
48884
- description,
48885
- });
50177
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50178
+ await Dump.dumpRecord(record, {
50179
+ dumpId,
50180
+ bucketName,
50181
+ signalId: signal.id,
50182
+ description,
50183
+ backtest: isBacktest,
50184
+ });
50185
+ return;
50186
+ }
50187
+ throw new Error(`dumpRecord requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48886
50188
  }
48887
50189
  /**
48888
50190
  * Dumps an array of objects as a table scoped to the current signal.
48889
50191
  *
48890
- * Reads signalId from the active pending signal via execution and method context.
48891
- * If no pending signal exists, logs a warning and returns without writing.
50192
+ * Resolves the active pending or scheduled signal automatically from execution context.
50193
+ * Automatically detects backtest/live mode from execution context.
48892
50194
  *
48893
50195
  * Column headers are derived from the union of all keys across all rows.
48894
50196
  *
@@ -48897,8 +50199,7 @@ async function dumpRecord(dto) {
48897
50199
  * @param dto.rows - Array of arbitrary objects to render as a table
48898
50200
  * @param dto.description - Human-readable label describing the table contents; included in the BM25 index for Memory search
48899
50201
  * @returns Promise that resolves when the dump is complete
48900
- *
48901
- * @deprecated Better use Dump.dumpTable with manual signalId argument
50202
+ * @throws Error if no pending or scheduled signal exists
48902
50203
  *
48903
50204
  * @example
48904
50205
  * ```typescript
@@ -48923,31 +50224,41 @@ async function dumpTable(dto) {
48923
50224
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48924
50225
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48925
50226
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48926
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48927
- if (!signal) {
48928
- console.warn(`backtest-kit dumpTable no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50227
+ let signal;
50228
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50229
+ await Dump.dumpTable(rows, {
50230
+ dumpId,
50231
+ bucketName,
50232
+ signalId: signal.id,
50233
+ description,
50234
+ backtest: isBacktest,
50235
+ });
48929
50236
  return;
48930
50237
  }
48931
- await Dump.dumpTable(rows, {
48932
- dumpId,
48933
- bucketName,
48934
- signalId: signal.id,
48935
- description,
48936
- });
50238
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50239
+ await Dump.dumpTable(rows, {
50240
+ dumpId,
50241
+ bucketName,
50242
+ signalId: signal.id,
50243
+ description,
50244
+ backtest: isBacktest,
50245
+ });
50246
+ return;
50247
+ }
50248
+ throw new Error(`dumpTable requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48937
50249
  }
48938
50250
  /**
48939
50251
  * Dumps raw text content scoped to the current signal.
48940
50252
  *
48941
- * Reads signalId from the active pending signal via execution and method context.
48942
- * If no pending signal exists, logs a warning and returns without writing.
50253
+ * Resolves the active pending or scheduled signal automatically from execution context.
50254
+ * Automatically detects backtest/live mode from execution context.
48943
50255
  *
48944
50256
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48945
50257
  * @param dto.dumpId - Unique identifier for this dump entry
48946
50258
  * @param dto.content - Arbitrary text content to persist
48947
50259
  * @param dto.description - Human-readable label describing the content; included in the BM25 index for Memory search
48948
50260
  * @returns Promise that resolves when the dump is complete
48949
- *
48950
- * @deprecated Better use Dump.dumpText with manual signalId argument
50261
+ * @throws Error if no pending or scheduled signal exists
48951
50262
  *
48952
50263
  * @example
48953
50264
  * ```typescript
@@ -48971,31 +50282,41 @@ async function dumpText(dto) {
48971
50282
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48972
50283
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48973
50284
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48974
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48975
- if (!signal) {
48976
- console.warn(`backtest-kit dumpText no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50285
+ let signal;
50286
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50287
+ await Dump.dumpText(content, {
50288
+ dumpId,
50289
+ bucketName,
50290
+ signalId: signal.id,
50291
+ description,
50292
+ backtest: isBacktest,
50293
+ });
48977
50294
  return;
48978
50295
  }
48979
- await Dump.dumpText(content, {
48980
- dumpId,
48981
- bucketName,
48982
- signalId: signal.id,
48983
- description,
48984
- });
50296
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50297
+ await Dump.dumpText(content, {
50298
+ dumpId,
50299
+ bucketName,
50300
+ signalId: signal.id,
50301
+ description,
50302
+ backtest: isBacktest,
50303
+ });
50304
+ return;
50305
+ }
50306
+ throw new Error(`dumpText requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48985
50307
  }
48986
50308
  /**
48987
50309
  * Dumps an error description scoped to the current signal.
48988
50310
  *
48989
- * Reads signalId from the active pending signal via execution and method context.
48990
- * If no pending signal exists, logs a warning and returns without writing.
50311
+ * Resolves the active pending or scheduled signal automatically from execution context.
50312
+ * Automatically detects backtest/live mode from execution context.
48991
50313
  *
48992
50314
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48993
50315
  * @param dto.dumpId - Unique identifier for this dump entry
48994
50316
  * @param dto.content - Error message or description to persist
48995
50317
  * @param dto.description - Human-readable label describing the error context; included in the BM25 index for Memory search
48996
50318
  * @returns Promise that resolves when the dump is complete
48997
- *
48998
- * @deprecated Better use Dump.dumpError with manual signalId argument
50319
+ * @throws Error if no pending or scheduled signal exists
48999
50320
  *
49000
50321
  * @example
49001
50322
  * ```typescript
@@ -49019,29 +50340,41 @@ async function dumpError(dto) {
49019
50340
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49020
50341
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49021
50342
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49022
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49023
- if (!signal) {
49024
- console.warn(`backtest-kit dumpError no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50343
+ let signal;
50344
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50345
+ await Dump.dumpError(content, {
50346
+ dumpId,
50347
+ bucketName,
50348
+ signalId: signal.id,
50349
+ description,
50350
+ backtest: isBacktest,
50351
+ });
49025
50352
  return;
49026
50353
  }
49027
- await Dump.dumpError(content, {
49028
- dumpId,
49029
- bucketName,
49030
- signalId: signal.id,
49031
- description,
49032
- });
50354
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50355
+ await Dump.dumpError(content, {
50356
+ dumpId,
50357
+ bucketName,
50358
+ signalId: signal.id,
50359
+ description,
50360
+ backtest: isBacktest,
50361
+ });
50362
+ return;
50363
+ }
50364
+ throw new Error(`dumpError requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49033
50365
  }
49034
50366
  /**
49035
50367
  * Dumps an arbitrary nested object as a fenced JSON block scoped to the current signal.
49036
50368
  *
49037
- * Reads signalId from the active pending signal via execution and method context.
49038
- * If no pending signal exists, logs a warning and returns without writing.
50369
+ * Resolves the active pending or scheduled signal automatically from execution context.
50370
+ * Automatically detects backtest/live mode from execution context.
49039
50371
  *
49040
50372
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
49041
50373
  * @param dto.dumpId - Unique identifier for this dump entry
49042
50374
  * @param dto.json - Arbitrary nested object to serialize with JSON.stringify
49043
50375
  * @param dto.description - Human-readable label describing the object contents; included in the BM25 index for Memory search
49044
50376
  * @returns Promise that resolves when the dump is complete
50377
+ * @throws Error if no pending or scheduled signal exists
49045
50378
  *
49046
50379
  * @deprecated Prefer dumpRecord — flat key-value structure maps naturally to markdown tables and SQL storage
49047
50380
  *
@@ -49067,17 +50400,28 @@ async function dumpJson(dto) {
49067
50400
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49068
50401
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49069
50402
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49070
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49071
- if (!signal) {
49072
- console.warn(`backtest-kit dumpJson no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50403
+ let signal;
50404
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50405
+ await Dump.dumpJson(json, {
50406
+ dumpId,
50407
+ bucketName,
50408
+ signalId: signal.id,
50409
+ description,
50410
+ backtest: isBacktest,
50411
+ });
49073
50412
  return;
49074
50413
  }
49075
- await Dump.dumpJson(json, {
49076
- dumpId,
49077
- bucketName,
49078
- signalId: signal.id,
49079
- description,
49080
- });
50414
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50415
+ await Dump.dumpJson(json, {
50416
+ dumpId,
50417
+ bucketName,
50418
+ signalId: signal.id,
50419
+ description,
50420
+ backtest: isBacktest,
50421
+ });
50422
+ return;
50423
+ }
50424
+ throw new Error(`dumpJson requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49081
50425
  }
49082
50426
 
49083
50427
  /**
@@ -59565,6 +60909,10 @@ exports.MarkdownFolderBase = MarkdownFolderBase;
59565
60909
  exports.MarkdownWriter = MarkdownWriter;
59566
60910
  exports.MaxDrawdown = MaxDrawdown;
59567
60911
  exports.Memory = Memory;
60912
+ exports.MemoryBacktest = MemoryBacktest;
60913
+ exports.MemoryBacktestAdapter = MemoryBacktestAdapter;
60914
+ exports.MemoryLive = MemoryLive;
60915
+ exports.MemoryLiveAdapter = MemoryLiveAdapter;
59568
60916
  exports.MethodContextService = MethodContextService;
59569
60917
  exports.Notification = Notification;
59570
60918
  exports.NotificationBacktest = NotificationBacktest;
@@ -59584,6 +60932,7 @@ exports.PersistRecentAdapter = PersistRecentAdapter;
59584
60932
  exports.PersistRiskAdapter = PersistRiskAdapter;
59585
60933
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
59586
60934
  exports.PersistSignalAdapter = PersistSignalAdapter;
60935
+ exports.PersistStateAdapter = PersistStateAdapter;
59587
60936
  exports.PersistStorageAdapter = PersistStorageAdapter;
59588
60937
  exports.Position = Position;
59589
60938
  exports.PositionSize = PositionSize;
@@ -59597,6 +60946,11 @@ exports.ReportWriter = ReportWriter;
59597
60946
  exports.Risk = Risk;
59598
60947
  exports.Schedule = Schedule;
59599
60948
  exports.Session = Session;
60949
+ exports.State = State;
60950
+ exports.StateBacktest = StateBacktest;
60951
+ exports.StateBacktestAdapter = StateBacktestAdapter;
60952
+ exports.StateLive = StateLive;
60953
+ exports.StateLiveAdapter = StateLiveAdapter;
59600
60954
  exports.Storage = Storage;
59601
60955
  exports.StorageBacktest = StorageBacktest;
59602
60956
  exports.StorageLive = StorageLive;
@@ -59626,6 +60980,7 @@ exports.commitTrailingStop = commitTrailingStop;
59626
60980
  exports.commitTrailingStopCost = commitTrailingStopCost;
59627
60981
  exports.commitTrailingTake = commitTrailingTake;
59628
60982
  exports.commitTrailingTakeCost = commitTrailingTakeCost;
60983
+ exports.createSignalState = createSignalState;
59629
60984
  exports.dumpAgentAnswer = dumpAgentAnswer;
59630
60985
  exports.dumpError = dumpError;
59631
60986
  exports.dumpJson = dumpJson;
@@ -59692,6 +61047,7 @@ exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
59692
61047
  exports.getRawCandles = getRawCandles;
59693
61048
  exports.getRiskSchema = getRiskSchema;
59694
61049
  exports.getScheduledSignal = getScheduledSignal;
61050
+ exports.getSignalState = getSignalState;
59695
61051
  exports.getSizingSchema = getSizingSchema;
59696
61052
  exports.getStrategySchema = getStrategySchema;
59697
61053
  exports.getSymbol = getSymbol;
@@ -59777,6 +61133,7 @@ exports.set = set;
59777
61133
  exports.setColumns = setColumns;
59778
61134
  exports.setConfig = setConfig;
59779
61135
  exports.setLogger = setLogger;
61136
+ exports.setSignalState = setSignalState;
59780
61137
  exports.shutdown = shutdown;
59781
61138
  exports.slPercentShiftToPrice = slPercentShiftToPrice;
59782
61139
  exports.slPriceToPercentShift = slPriceToPercentShift;