backtest-kit 7.4.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.mjs CHANGED
@@ -997,6 +997,13 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
997
997
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
998
998
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
999
999
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1000
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER = "PersistStateUtils.usePersistStateAdapter";
1001
+ const PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA = "PersistStateUtils.readStateData";
1002
+ const PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA = "PersistStateUtils.writeStateData";
1003
+ const PERSIST_STATE_UTILS_METHOD_NAME_CLEAR = "PersistStateUtils.clear";
1004
+ const PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE = "PersistStateUtils.dispose";
1005
+ const PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT = "PersistStateUtils.waitForInit";
1006
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY = "PersistStateUtils.useDummy";
1000
1007
  const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1001
1008
  const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1002
1009
  const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
@@ -2958,6 +2965,127 @@ class PersistRecentUtils {
2958
2965
  * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2959
2966
  */
2960
2967
  const PersistRecentAdapter = new PersistRecentUtils();
2968
+ /**
2969
+ * Utility class for managing state persistence.
2970
+ *
2971
+ * Features:
2972
+ * - Memoized storage instances per (signalId, bucketName) pair
2973
+ * - Custom adapter support
2974
+ * - Atomic read/write operations
2975
+ *
2976
+ * Storage layout: ./dump/state/<signalId>/<bucketName>.json
2977
+ *
2978
+ * Used by StatePersistInstance for crash-safe state persistence.
2979
+ */
2980
+ class PersistStateUtils {
2981
+ constructor() {
2982
+ this.PersistStateFactory = PersistBase;
2983
+ this.getStateStorage = memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistStateFactory, [
2984
+ bucketName,
2985
+ `./dump/state/${signalId}/`,
2986
+ ]));
2987
+ /**
2988
+ * Initializes the storage for a given (signalId, bucketName) pair.
2989
+ *
2990
+ * @param signalId - Signal identifier
2991
+ * @param bucketName - Bucket name
2992
+ * @param initial - Whether this is the first initialization
2993
+ */
2994
+ this.waitForInit = async (signalId, bucketName, initial) => {
2995
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
2996
+ signalId,
2997
+ bucketName,
2998
+ initial,
2999
+ });
3000
+ const key = `${signalId}:${bucketName}`;
3001
+ const isInitial = initial && !this.getStateStorage.has(key);
3002
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3003
+ await stateStorage.waitForInit(isInitial);
3004
+ };
3005
+ /**
3006
+ * Reads a state entry from persistence storage.
3007
+ *
3008
+ * @param signalId - Signal identifier
3009
+ * @param bucketName - Bucket name
3010
+ * @returns Promise resolving to entry data or null if not found
3011
+ */
3012
+ this.readStateData = async (signalId, bucketName) => {
3013
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, {
3014
+ signalId,
3015
+ bucketName,
3016
+ });
3017
+ const key = `${signalId}:${bucketName}`;
3018
+ const isInitial = !this.getStateStorage.has(key);
3019
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3020
+ await stateStorage.waitForInit(isInitial);
3021
+ if (await stateStorage.hasValue(bucketName)) {
3022
+ return await stateStorage.readValue(bucketName);
3023
+ }
3024
+ return null;
3025
+ };
3026
+ /**
3027
+ * Writes a state entry to disk with atomic file writes.
3028
+ *
3029
+ * @param data - Entry data to persist
3030
+ * @param signalId - Signal identifier
3031
+ * @param bucketName - Bucket name
3032
+ */
3033
+ this.writeStateData = async (data, signalId, bucketName) => {
3034
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, {
3035
+ signalId,
3036
+ bucketName,
3037
+ });
3038
+ const key = `${signalId}:${bucketName}`;
3039
+ const isInitial = !this.getStateStorage.has(key);
3040
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3041
+ await stateStorage.waitForInit(isInitial);
3042
+ await stateStorage.writeValue(bucketName, data);
3043
+ };
3044
+ /**
3045
+ * Switches to a dummy persist adapter that discards all writes.
3046
+ * All future persistence writes will be no-ops.
3047
+ */
3048
+ this.useDummy = () => {
3049
+ LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
3050
+ this.usePersistStateAdapter(PersistDummy);
3051
+ };
3052
+ /**
3053
+ * Clears the memoized storage cache.
3054
+ * Call this when process.cwd() changes between strategy iterations
3055
+ * so new storage instances are created with the updated base path.
3056
+ */
3057
+ this.clear = () => {
3058
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
3059
+ this.getStateStorage.clear();
3060
+ };
3061
+ /**
3062
+ * Disposes of the state adapter and releases any resources.
3063
+ * Call this when a signal is removed to clean up its associated storage.
3064
+ *
3065
+ * @param signalId - Signal identifier
3066
+ * @param bucketName - Bucket name
3067
+ */
3068
+ this.dispose = (signalId, bucketName) => {
3069
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
3070
+ const key = `${signalId}:${bucketName}`;
3071
+ this.getStateStorage.clear(key);
3072
+ };
3073
+ }
3074
+ /**
3075
+ * Registers a custom persistence adapter.
3076
+ *
3077
+ * @param Ctor - Custom PersistBase constructor
3078
+ */
3079
+ usePersistStateAdapter(Ctor) {
3080
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
3081
+ this.PersistStateFactory = Ctor;
3082
+ }
3083
+ }
3084
+ /**
3085
+ * Global singleton instance of PersistStateUtils.
3086
+ * Used by StatePersistInstance for crash-safe state persistence.
3087
+ */
3088
+ const PersistStateAdapter = new PersistStateUtils();
2961
3089
 
2962
3090
  var _a$2, _b$2;
2963
3091
  const BUSY_DELAY = 100;
@@ -10423,7 +10551,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10423
10551
  * @param backtest - Whether running in backtest mode
10424
10552
  * @returns Unique string key for memoization
10425
10553
  */
10426
- const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10554
+ const CREATE_KEY_FN$w = (symbol, strategyName, exchangeName, frameName, backtest) => {
10427
10555
  const parts = [symbol, strategyName, exchangeName];
10428
10556
  if (frameName)
10429
10557
  parts.push(frameName);
@@ -10723,7 +10851,7 @@ class StrategyConnectionService {
10723
10851
  * @param backtest - Whether running in backtest mode
10724
10852
  * @returns Configured ClientStrategy instance
10725
10853
  */
10726
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10854
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$w(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10727
10855
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10728
10856
  return new ClientStrategy({
10729
10857
  symbol,
@@ -11685,7 +11813,7 @@ class StrategyConnectionService {
11685
11813
  }
11686
11814
  return;
11687
11815
  }
11688
- const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11816
+ const key = CREATE_KEY_FN$w(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11689
11817
  if (!this.getStrategy.has(key)) {
11690
11818
  return;
11691
11819
  }
@@ -12859,7 +12987,7 @@ class ClientRisk {
12859
12987
  * @param backtest - Whether running in backtest mode
12860
12988
  * @returns Unique string key for memoization
12861
12989
  */
12862
- const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
12990
+ const CREATE_KEY_FN$v = (riskName, exchangeName, frameName, backtest) => {
12863
12991
  const parts = [riskName, exchangeName];
12864
12992
  if (frameName)
12865
12993
  parts.push(frameName);
@@ -12959,7 +13087,7 @@ class RiskConnectionService {
12959
13087
  * @param backtest - True if backtest mode, false if live mode
12960
13088
  * @returns Configured ClientRisk instance
12961
13089
  */
12962
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
13090
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12963
13091
  const schema = this.riskSchemaService.get(riskName);
12964
13092
  return new ClientRisk({
12965
13093
  ...schema,
@@ -13028,7 +13156,7 @@ class RiskConnectionService {
13028
13156
  payload,
13029
13157
  });
13030
13158
  if (payload) {
13031
- const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13159
+ const key = CREATE_KEY_FN$v(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13032
13160
  this.getRisk.clear(key);
13033
13161
  }
13034
13162
  else {
@@ -14147,7 +14275,7 @@ class ClientAction {
14147
14275
  * @param backtest - Whether running in backtest mode
14148
14276
  * @returns Unique string key for memoization
14149
14277
  */
14150
- const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
14278
+ const CREATE_KEY_FN$u = (actionName, strategyName, exchangeName, frameName, backtest) => {
14151
14279
  const parts = [actionName, strategyName, exchangeName];
14152
14280
  if (frameName)
14153
14281
  parts.push(frameName);
@@ -14199,7 +14327,7 @@ class ActionConnectionService {
14199
14327
  * @param backtest - True if backtest mode, false if live mode
14200
14328
  * @returns Configured ClientAction instance
14201
14329
  */
14202
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14330
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14203
14331
  const schema = this.actionSchemaService.get(actionName);
14204
14332
  return new ClientAction({
14205
14333
  ...schema,
@@ -14425,7 +14553,7 @@ class ActionConnectionService {
14425
14553
  await Promise.all(actions.map(async (action) => await action.dispose()));
14426
14554
  return;
14427
14555
  }
14428
- const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14556
+ const key = CREATE_KEY_FN$u(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14429
14557
  if (!this.getAction.has(key)) {
14430
14558
  return;
14431
14559
  }
@@ -14443,7 +14571,7 @@ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14443
14571
  * @param exchangeName - Exchange name
14444
14572
  * @returns Unique string key for memoization
14445
14573
  */
14446
- const CREATE_KEY_FN$s = (exchangeName) => {
14574
+ const CREATE_KEY_FN$t = (exchangeName) => {
14447
14575
  return exchangeName;
14448
14576
  };
14449
14577
  /**
@@ -14467,7 +14595,7 @@ class ExchangeCoreService {
14467
14595
  * @param exchangeName - Name of the exchange to validate
14468
14596
  * @returns Promise that resolves when validation is complete
14469
14597
  */
14470
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14598
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$t(exchangeName), async (exchangeName) => {
14471
14599
  this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14472
14600
  exchangeName,
14473
14601
  });
@@ -14719,7 +14847,7 @@ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14719
14847
  * @param context - Execution context with strategyName, exchangeName, frameName
14720
14848
  * @returns Unique string key for memoization
14721
14849
  */
14722
- const CREATE_KEY_FN$r = (context) => {
14850
+ const CREATE_KEY_FN$s = (context) => {
14723
14851
  const parts = [context.strategyName, context.exchangeName];
14724
14852
  if (context.frameName)
14725
14853
  parts.push(context.frameName);
@@ -14751,7 +14879,7 @@ class StrategyCoreService {
14751
14879
  * @param context - Execution context with strategyName, exchangeName, frameName
14752
14880
  * @returns Promise that resolves when validation is complete
14753
14881
  */
14754
- this.validate = memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
14882
+ this.validate = memoize(([context]) => CREATE_KEY_FN$s(context), async (context) => {
14755
14883
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14756
14884
  context,
14757
14885
  });
@@ -16121,7 +16249,7 @@ class SizingGlobalService {
16121
16249
  * @param context - Context with riskName, exchangeName, frameName
16122
16250
  * @returns Unique string key for memoization
16123
16251
  */
16124
- const CREATE_KEY_FN$q = (context) => {
16252
+ const CREATE_KEY_FN$r = (context) => {
16125
16253
  const parts = [context.riskName, context.exchangeName];
16126
16254
  if (context.frameName)
16127
16255
  parts.push(context.frameName);
@@ -16147,7 +16275,7 @@ class RiskGlobalService {
16147
16275
  * @param payload - Payload with riskName, exchangeName and frameName
16148
16276
  * @returns Promise that resolves when validation is complete
16149
16277
  */
16150
- this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
16278
+ this.validate = memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
16151
16279
  this.loggerService.log("riskGlobalService validate", {
16152
16280
  context,
16153
16281
  });
@@ -16225,7 +16353,7 @@ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
16225
16353
  * @param context - Execution context with strategyName, exchangeName, frameName
16226
16354
  * @returns Unique string key for memoization
16227
16355
  */
16228
- const CREATE_KEY_FN$p = (context) => {
16356
+ const CREATE_KEY_FN$q = (context) => {
16229
16357
  const parts = [context.strategyName, context.exchangeName];
16230
16358
  if (context.frameName)
16231
16359
  parts.push(context.frameName);
@@ -16269,7 +16397,7 @@ class ActionCoreService {
16269
16397
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
16270
16398
  * @returns Promise that resolves when all validations complete
16271
16399
  */
16272
- this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
16400
+ this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
16273
16401
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
16274
16402
  context,
16275
16403
  });
@@ -21339,7 +21467,7 @@ const ReportWriter = new ReportWriterAdapter();
21339
21467
  * @param backtest - Whether running in backtest mode
21340
21468
  * @returns Unique string key for memoization
21341
21469
  */
21342
- const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
21470
+ const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
21343
21471
  const parts = [symbol, strategyName, exchangeName];
21344
21472
  if (frameName)
21345
21473
  parts.push(frameName);
@@ -21585,7 +21713,7 @@ class BacktestMarkdownService {
21585
21713
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21586
21714
  * Each combination gets its own isolated storage instance.
21587
21715
  */
21588
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21716
+ this.getStorage = 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));
21589
21717
  /**
21590
21718
  * Processes tick events and accumulates closed signals.
21591
21719
  * Should be called from IStrategyCallbacks.onTick.
@@ -21742,7 +21870,7 @@ class BacktestMarkdownService {
21742
21870
  payload,
21743
21871
  });
21744
21872
  if (payload) {
21745
- const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21873
+ const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21746
21874
  this.getStorage.clear(key);
21747
21875
  }
21748
21876
  else {
@@ -21804,7 +21932,7 @@ class BacktestMarkdownService {
21804
21932
  * @param backtest - Whether running in backtest mode
21805
21933
  * @returns Unique string key for memoization
21806
21934
  */
21807
- const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
21935
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
21808
21936
  const parts = [symbol, strategyName, exchangeName];
21809
21937
  if (frameName)
21810
21938
  parts.push(frameName);
@@ -22299,7 +22427,7 @@ class LiveMarkdownService {
22299
22427
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22300
22428
  * Each combination gets its own isolated storage instance.
22301
22429
  */
22302
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22430
+ this.getStorage = 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));
22303
22431
  /**
22304
22432
  * Subscribes to live signal emitter to receive tick events.
22305
22433
  * Protected against multiple subscriptions.
@@ -22517,7 +22645,7 @@ class LiveMarkdownService {
22517
22645
  payload,
22518
22646
  });
22519
22647
  if (payload) {
22520
- const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22648
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22521
22649
  this.getStorage.clear(key);
22522
22650
  }
22523
22651
  else {
@@ -22537,7 +22665,7 @@ class LiveMarkdownService {
22537
22665
  * @param backtest - Whether running in backtest mode
22538
22666
  * @returns Unique string key for memoization
22539
22667
  */
22540
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22668
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
22541
22669
  const parts = [symbol, strategyName, exchangeName];
22542
22670
  if (frameName)
22543
22671
  parts.push(frameName);
@@ -22826,7 +22954,7 @@ class ScheduleMarkdownService {
22826
22954
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22827
22955
  * Each combination gets its own isolated storage instance.
22828
22956
  */
22829
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22957
+ this.getStorage = 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));
22830
22958
  /**
22831
22959
  * Subscribes to signal emitter to receive scheduled signal events.
22832
22960
  * Protected against multiple subscriptions.
@@ -23029,7 +23157,7 @@ class ScheduleMarkdownService {
23029
23157
  payload,
23030
23158
  });
23031
23159
  if (payload) {
23032
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23160
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23033
23161
  this.getStorage.clear(key);
23034
23162
  }
23035
23163
  else {
@@ -23049,7 +23177,7 @@ class ScheduleMarkdownService {
23049
23177
  * @param backtest - Whether running in backtest mode
23050
23178
  * @returns Unique string key for memoization
23051
23179
  */
23052
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
23180
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
23053
23181
  const parts = [symbol, strategyName, exchangeName];
23054
23182
  if (frameName)
23055
23183
  parts.push(frameName);
@@ -23294,7 +23422,7 @@ class PerformanceMarkdownService {
23294
23422
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
23295
23423
  * Each combination gets its own isolated storage instance.
23296
23424
  */
23297
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23425
+ this.getStorage = 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));
23298
23426
  /**
23299
23427
  * Subscribes to performance emitter to receive performance events.
23300
23428
  * Protected against multiple subscriptions.
@@ -23461,7 +23589,7 @@ class PerformanceMarkdownService {
23461
23589
  payload,
23462
23590
  });
23463
23591
  if (payload) {
23464
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23592
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23465
23593
  this.getStorage.clear(key);
23466
23594
  }
23467
23595
  else {
@@ -23940,7 +24068,7 @@ class WalkerMarkdownService {
23940
24068
  * @param backtest - Whether running in backtest mode
23941
24069
  * @returns Unique string key for memoization
23942
24070
  */
23943
- const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
24071
+ const CREATE_KEY_FN$l = (exchangeName, frameName, backtest) => {
23944
24072
  const parts = [exchangeName];
23945
24073
  if (frameName)
23946
24074
  parts.push(frameName);
@@ -24387,7 +24515,7 @@ class HeatMarkdownService {
24387
24515
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
24388
24516
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
24389
24517
  */
24390
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24518
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24391
24519
  /**
24392
24520
  * Subscribes to signal emitter to receive tick events.
24393
24521
  * Protected against multiple subscriptions.
@@ -24605,7 +24733,7 @@ class HeatMarkdownService {
24605
24733
  payload,
24606
24734
  });
24607
24735
  if (payload) {
24608
- const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24736
+ const key = CREATE_KEY_FN$l(payload.exchangeName, payload.frameName, payload.backtest);
24609
24737
  this.getStorage.clear(key);
24610
24738
  }
24611
24739
  else {
@@ -25636,7 +25764,7 @@ class ClientPartial {
25636
25764
  * @param backtest - Whether running in backtest mode
25637
25765
  * @returns Unique string key for memoization
25638
25766
  */
25639
- const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25767
+ const CREATE_KEY_FN$k = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25640
25768
  /**
25641
25769
  * Creates a callback function for emitting profit events to partialProfitSubject.
25642
25770
  *
@@ -25758,7 +25886,7 @@ class PartialConnectionService {
25758
25886
  * Key format: "signalId:backtest" or "signalId:live"
25759
25887
  * Value: ClientPartial instance with logger and event emitters
25760
25888
  */
25761
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
25889
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$k(signalId, backtest), (signalId, backtest) => {
25762
25890
  return new ClientPartial({
25763
25891
  signalId,
25764
25892
  logger: this.loggerService,
@@ -25848,7 +25976,7 @@ class PartialConnectionService {
25848
25976
  const partial = this.getPartial(data.id, backtest);
25849
25977
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25850
25978
  await partial.clear(symbol, data, priceClose, backtest);
25851
- const key = CREATE_KEY_FN$j(data.id, backtest);
25979
+ const key = CREATE_KEY_FN$k(data.id, backtest);
25852
25980
  this.getPartial.clear(key);
25853
25981
  };
25854
25982
  }
@@ -25864,7 +25992,7 @@ class PartialConnectionService {
25864
25992
  * @param backtest - Whether running in backtest mode
25865
25993
  * @returns Unique string key for memoization
25866
25994
  */
25867
- const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
25995
+ const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
25868
25996
  const parts = [symbol, strategyName, exchangeName];
25869
25997
  if (frameName)
25870
25998
  parts.push(frameName);
@@ -26087,7 +26215,7 @@ class PartialMarkdownService {
26087
26215
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26088
26216
  * Each combination gets its own isolated storage instance.
26089
26217
  */
26090
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
26218
+ this.getStorage = 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));
26091
26219
  /**
26092
26220
  * Subscribes to partial profit/loss signal emitters to receive events.
26093
26221
  * Protected against multiple subscriptions.
@@ -26297,7 +26425,7 @@ class PartialMarkdownService {
26297
26425
  payload,
26298
26426
  });
26299
26427
  if (payload) {
26300
- const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26428
+ const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26301
26429
  this.getStorage.clear(key);
26302
26430
  }
26303
26431
  else {
@@ -26313,7 +26441,7 @@ class PartialMarkdownService {
26313
26441
  * @param context - Context with strategyName, exchangeName, frameName
26314
26442
  * @returns Unique string key for memoization
26315
26443
  */
26316
- const CREATE_KEY_FN$h = (context) => {
26444
+ const CREATE_KEY_FN$i = (context) => {
26317
26445
  const parts = [context.strategyName, context.exchangeName];
26318
26446
  if (context.frameName)
26319
26447
  parts.push(context.frameName);
@@ -26387,7 +26515,7 @@ class PartialGlobalService {
26387
26515
  * @param context - Context with strategyName, exchangeName and frameName
26388
26516
  * @param methodName - Name of the calling method for error tracking
26389
26517
  */
26390
- this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
26518
+ this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), (context, methodName) => {
26391
26519
  this.loggerService.log("partialGlobalService validate", {
26392
26520
  context,
26393
26521
  methodName,
@@ -26842,7 +26970,7 @@ class ClientBreakeven {
26842
26970
  * @param backtest - Whether running in backtest mode
26843
26971
  * @returns Unique string key for memoization
26844
26972
  */
26845
- const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26973
+ const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26846
26974
  /**
26847
26975
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26848
26976
  *
@@ -26928,7 +27056,7 @@ class BreakevenConnectionService {
26928
27056
  * Key format: "signalId:backtest" or "signalId:live"
26929
27057
  * Value: ClientBreakeven instance with logger and event emitter
26930
27058
  */
26931
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
27059
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
26932
27060
  return new ClientBreakeven({
26933
27061
  signalId,
26934
27062
  logger: this.loggerService,
@@ -26989,7 +27117,7 @@ class BreakevenConnectionService {
26989
27117
  const breakeven = this.getBreakeven(data.id, backtest);
26990
27118
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26991
27119
  await breakeven.clear(symbol, data, priceClose, backtest);
26992
- const key = CREATE_KEY_FN$g(data.id, backtest);
27120
+ const key = CREATE_KEY_FN$h(data.id, backtest);
26993
27121
  this.getBreakeven.clear(key);
26994
27122
  };
26995
27123
  }
@@ -27005,7 +27133,7 @@ class BreakevenConnectionService {
27005
27133
  * @param backtest - Whether running in backtest mode
27006
27134
  * @returns Unique string key for memoization
27007
27135
  */
27008
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
27136
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
27009
27137
  const parts = [symbol, strategyName, exchangeName];
27010
27138
  if (frameName)
27011
27139
  parts.push(frameName);
@@ -27180,7 +27308,7 @@ class BreakevenMarkdownService {
27180
27308
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27181
27309
  * Each combination gets its own isolated storage instance.
27182
27310
  */
27183
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
27311
+ this.getStorage = 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));
27184
27312
  /**
27185
27313
  * Subscribes to breakeven signal emitter to receive events.
27186
27314
  * Protected against multiple subscriptions.
@@ -27369,7 +27497,7 @@ class BreakevenMarkdownService {
27369
27497
  payload,
27370
27498
  });
27371
27499
  if (payload) {
27372
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27500
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27373
27501
  this.getStorage.clear(key);
27374
27502
  }
27375
27503
  else {
@@ -27385,7 +27513,7 @@ class BreakevenMarkdownService {
27385
27513
  * @param context - Context with strategyName, exchangeName, frameName
27386
27514
  * @returns Unique string key for memoization
27387
27515
  */
27388
- const CREATE_KEY_FN$e = (context) => {
27516
+ const CREATE_KEY_FN$f = (context) => {
27389
27517
  const parts = [context.strategyName, context.exchangeName];
27390
27518
  if (context.frameName)
27391
27519
  parts.push(context.frameName);
@@ -27459,7 +27587,7 @@ class BreakevenGlobalService {
27459
27587
  * @param context - Context with strategyName, exchangeName and frameName
27460
27588
  * @param methodName - Name of the calling method for error tracking
27461
27589
  */
27462
- this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
27590
+ this.validate = memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
27463
27591
  this.loggerService.log("breakevenGlobalService validate", {
27464
27592
  context,
27465
27593
  methodName,
@@ -27680,7 +27808,7 @@ class ConfigValidationService {
27680
27808
  * @param backtest - Whether running in backtest mode
27681
27809
  * @returns Unique string key for memoization
27682
27810
  */
27683
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27811
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
27684
27812
  const parts = [symbol, strategyName, exchangeName];
27685
27813
  if (frameName)
27686
27814
  parts.push(frameName);
@@ -27847,7 +27975,7 @@ class RiskMarkdownService {
27847
27975
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27848
27976
  * Each combination gets its own isolated storage instance.
27849
27977
  */
27850
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27978
+ this.getStorage = 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));
27851
27979
  /**
27852
27980
  * Subscribes to risk rejection emitter to receive rejection events.
27853
27981
  * Protected against multiple subscriptions.
@@ -28036,7 +28164,7 @@ class RiskMarkdownService {
28036
28164
  payload,
28037
28165
  });
28038
28166
  if (payload) {
28039
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28167
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28040
28168
  this.getStorage.clear(key);
28041
28169
  }
28042
28170
  else {
@@ -30616,7 +30744,7 @@ class HighestProfitReportService {
30616
30744
  * @returns Colon-separated key string for memoization
30617
30745
  * @internal
30618
30746
  */
30619
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
30747
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
30620
30748
  const parts = [symbol, strategyName, exchangeName];
30621
30749
  if (frameName)
30622
30750
  parts.push(frameName);
@@ -30858,7 +30986,7 @@ class StrategyMarkdownService {
30858
30986
  *
30859
30987
  * @internal
30860
30988
  */
30861
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30989
+ this.getStorage = 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));
30862
30990
  /**
30863
30991
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30864
30992
  *
@@ -31432,7 +31560,7 @@ class StrategyMarkdownService {
31432
31560
  this.clear = async (payload) => {
31433
31561
  this.loggerService.log("strategyMarkdownService clear", { payload });
31434
31562
  if (payload) {
31435
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31563
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31436
31564
  this.getStorage.clear(key);
31437
31565
  }
31438
31566
  else {
@@ -31540,7 +31668,7 @@ class StrategyMarkdownService {
31540
31668
  * Creates a unique key for memoizing ReportStorage instances.
31541
31669
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
31542
31670
  */
31543
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
31671
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
31544
31672
  const parts = [symbol, strategyName, exchangeName];
31545
31673
  if (frameName)
31546
31674
  parts.push(frameName);
@@ -31733,7 +31861,7 @@ let ReportStorage$2 = class ReportStorage {
31733
31861
  class SyncMarkdownService {
31734
31862
  constructor() {
31735
31863
  this.loggerService = inject(TYPES.loggerService);
31736
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31864
+ this.getStorage = 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));
31737
31865
  /**
31738
31866
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31739
31867
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31931,7 +32059,7 @@ class SyncMarkdownService {
31931
32059
  this.clear = async (payload) => {
31932
32060
  this.loggerService.log("syncMarkdownService clear", { payload });
31933
32061
  if (payload) {
31934
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32062
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31935
32063
  this.getStorage.clear(key);
31936
32064
  }
31937
32065
  else {
@@ -31944,7 +32072,7 @@ class SyncMarkdownService {
31944
32072
  /**
31945
32073
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31946
32074
  */
31947
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32075
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
31948
32076
  const parts = [symbol, strategyName, exchangeName];
31949
32077
  if (frameName)
31950
32078
  parts.push(frameName);
@@ -32122,7 +32250,7 @@ let ReportStorage$1 = class ReportStorage {
32122
32250
  class HighestProfitMarkdownService {
32123
32251
  constructor() {
32124
32252
  this.loggerService = inject(TYPES.loggerService);
32125
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
32253
+ this.getStorage = 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));
32126
32254
  /**
32127
32255
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
32128
32256
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -32288,7 +32416,7 @@ class HighestProfitMarkdownService {
32288
32416
  this.clear = async (payload) => {
32289
32417
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
32290
32418
  if (payload) {
32291
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32419
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32292
32420
  this.getStorage.clear(key);
32293
32421
  }
32294
32422
  else {
@@ -32310,7 +32438,7 @@ const LISTEN_TIMEOUT$1 = 120000;
32310
32438
  * @param backtest - Whether running in backtest mode
32311
32439
  * @returns Unique string key for memoization
32312
32440
  */
32313
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32441
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32314
32442
  const parts = [symbol, strategyName, exchangeName];
32315
32443
  if (frameName)
32316
32444
  parts.push(frameName);
@@ -32353,7 +32481,7 @@ class PriceMetaService {
32353
32481
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
32354
32482
  * Instances are cached until clear() is called.
32355
32483
  */
32356
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
32484
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
32357
32485
  /**
32358
32486
  * Returns the current market price for the given symbol and context.
32359
32487
  *
@@ -32382,10 +32510,10 @@ class PriceMetaService {
32382
32510
  if (source.data) {
32383
32511
  return source.data;
32384
32512
  }
32385
- 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...`);
32513
+ 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...`);
32386
32514
  const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
32387
32515
  if (typeof currentPrice === "symbol") {
32388
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32516
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32389
32517
  }
32390
32518
  return currentPrice;
32391
32519
  };
@@ -32427,7 +32555,7 @@ class PriceMetaService {
32427
32555
  this.getSource.clear();
32428
32556
  return;
32429
32557
  }
32430
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32558
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32431
32559
  this.getSource.clear(key);
32432
32560
  };
32433
32561
  }
@@ -32445,7 +32573,7 @@ const LISTEN_TIMEOUT = 120000;
32445
32573
  * @param backtest - Whether running in backtest mode
32446
32574
  * @returns Unique string key for memoization
32447
32575
  */
32448
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32576
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32449
32577
  const parts = [symbol, strategyName, exchangeName];
32450
32578
  if (frameName)
32451
32579
  parts.push(frameName);
@@ -32488,7 +32616,7 @@ class TimeMetaService {
32488
32616
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
32489
32617
  * Instances are cached until clear() is called.
32490
32618
  */
32491
- this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
32619
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
32492
32620
  /**
32493
32621
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
32494
32622
  *
@@ -32516,10 +32644,10 @@ class TimeMetaService {
32516
32644
  if (source.data) {
32517
32645
  return source.data;
32518
32646
  }
32519
- 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...`);
32647
+ 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...`);
32520
32648
  const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
32521
32649
  if (typeof timestamp === "symbol") {
32522
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32650
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32523
32651
  }
32524
32652
  return timestamp;
32525
32653
  };
@@ -32561,7 +32689,7 @@ class TimeMetaService {
32561
32689
  this.getSource.clear();
32562
32690
  return;
32563
32691
  }
32564
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32692
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32565
32693
  this.getSource.clear(key);
32566
32694
  };
32567
32695
  }
@@ -32667,7 +32795,7 @@ class MaxDrawdownReportService {
32667
32795
  /**
32668
32796
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
32669
32797
  */
32670
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32798
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32671
32799
  const parts = [symbol, strategyName, exchangeName];
32672
32800
  if (frameName)
32673
32801
  parts.push(frameName);
@@ -32793,7 +32921,7 @@ class ReportStorage {
32793
32921
  class MaxDrawdownMarkdownService {
32794
32922
  constructor() {
32795
32923
  this.loggerService = inject(TYPES.loggerService);
32796
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32924
+ this.getStorage = 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));
32797
32925
  /**
32798
32926
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32799
32927
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32872,7 +33000,7 @@ class MaxDrawdownMarkdownService {
32872
33000
  this.clear = async (payload) => {
32873
33001
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32874
33002
  if (payload) {
32875
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
33003
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32876
33004
  this.getStorage.clear(key);
32877
33005
  }
32878
33006
  else {
@@ -32890,7 +33018,7 @@ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32890
33018
  * @param context - Execution context with strategyName, exchangeName, frameName
32891
33019
  * @returns Unique string key for memoization
32892
33020
  */
32893
- const CREATE_KEY_FN$6 = (context) => {
33021
+ const CREATE_KEY_FN$7 = (context) => {
32894
33022
  const parts = [context.strategyName, context.exchangeName];
32895
33023
  if (context.frameName)
32896
33024
  parts.push(context.frameName);
@@ -32923,7 +33051,7 @@ class NotificationHelperService {
32923
33051
  * @param context - Routing context: strategyName, exchangeName, frameName
32924
33052
  * @throws {Error} If any registered schema fails validation
32925
33053
  */
32926
- this.validate = memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
33054
+ this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), async (context) => {
32927
33055
  this.loggerService.log(METHOD_NAME_VALIDATE, {
32928
33056
  context,
32929
33057
  });
@@ -46467,7 +46595,7 @@ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapte
46467
46595
  * @param backtest - Flag indicating if the context is backtest or live
46468
46596
  * @returns Composite key string
46469
46597
  */
46470
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46598
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46471
46599
  const parts = [symbol, strategyName, exchangeName];
46472
46600
  if (frameName)
46473
46601
  parts.push(frameName);
@@ -46557,7 +46685,7 @@ class RecentMemoryBacktestUtils {
46557
46685
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
46558
46686
  signalId: event.data.id,
46559
46687
  });
46560
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46688
+ const key = CREATE_KEY_FN$6(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46561
46689
  this._signals.set(key, event.data);
46562
46690
  };
46563
46691
  /**
@@ -46570,7 +46698,7 @@ class RecentMemoryBacktestUtils {
46570
46698
  * @returns The latest signal or null if not found
46571
46699
  */
46572
46700
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46573
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46701
+ const key = CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest$1);
46574
46702
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46575
46703
  return this._signals.get(key) ?? null;
46576
46704
  };
@@ -46676,7 +46804,7 @@ class RecentMemoryLiveUtils {
46676
46804
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
46677
46805
  signalId: event.data.id,
46678
46806
  });
46679
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46807
+ const key = CREATE_KEY_FN$6(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46680
46808
  this._signals.set(key, event.data);
46681
46809
  };
46682
46810
  /**
@@ -46689,7 +46817,7 @@ class RecentMemoryLiveUtils {
46689
46817
  * @returns The latest signal or null if not found
46690
46818
  */
46691
46819
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46692
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46820
+ const key = CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest$1);
46693
46821
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46694
46822
  return this._signals.get(key) ?? null;
46695
46823
  };
@@ -47040,6 +47168,554 @@ const RecentLive = new RecentLiveAdapter();
47040
47168
  */
47041
47169
  const RecentBacktest = new RecentBacktestAdapter();
47042
47170
 
47171
+ const CREATE_KEY_FN$5 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47172
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_GET = "StateLocalInstance.getState";
47173
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_SET = "StateLocalInstance.setState";
47174
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT = "StatePersistInstance.waitForInit";
47175
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_GET = "StatePersistInstance.getState";
47176
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_SET = "StatePersistInstance.setState";
47177
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "StateBacktestAdapter.dispose";
47178
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_GET = "StateBacktestAdapter.getState";
47179
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_SET = "StateBacktestAdapter.setState";
47180
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "StateBacktestAdapter.useLocal";
47181
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "StateBacktestAdapter.usePersist";
47182
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "StateBacktestAdapter.useDummy";
47183
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateBacktestAdapter.useStateAdapter";
47184
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "StateBacktestAdapter.clear";
47185
+ const STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "StateLiveAdapter.dispose";
47186
+ const STATE_LIVE_ADAPTER_METHOD_NAME_GET = "StateLiveAdapter.getState";
47187
+ const STATE_LIVE_ADAPTER_METHOD_NAME_SET = "StateLiveAdapter.setState";
47188
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "StateLiveAdapter.useLocal";
47189
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "StateLiveAdapter.usePersist";
47190
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "StateLiveAdapter.useDummy";
47191
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateLiveAdapter.useStateAdapter";
47192
+ const STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR = "StateLiveAdapter.clear";
47193
+ const STATE_ADAPTER_METHOD_NAME_ENABLE = "StateAdapter.enable";
47194
+ const STATE_ADAPTER_METHOD_NAME_DISABLE = "StateAdapter.disable";
47195
+ const STATE_ADAPTER_METHOD_NAME_GET = "StateAdapter.getState";
47196
+ const STATE_ADAPTER_METHOD_NAME_SET = "StateAdapter.setState";
47197
+ /**
47198
+ * In-process state instance backed by a plain object reference.
47199
+ * All data lives in process memory only - no disk persistence.
47200
+ *
47201
+ * Features:
47202
+ * - Mutable in-memory state with functional dispatch support
47203
+ * - Scoped per (signalId, bucketName) pair
47204
+ *
47205
+ * Use for backtesting and unit tests where persistence between runs is not needed.
47206
+ * Tracks per-trade metrics such as peakPercent and minutesOpen to implement
47207
+ * the capitulation rule: exit when peak < threshold after N minutes open.
47208
+ */
47209
+ class StateLocalInstance {
47210
+ constructor(initialValue, signalId, bucketName) {
47211
+ this.initialValue = initialValue;
47212
+ this.signalId = signalId;
47213
+ this.bucketName = bucketName;
47214
+ /**
47215
+ * Initializes _value from initialValue - local state needs no async setup.
47216
+ * @returns Promise that resolves immediately
47217
+ */
47218
+ this.waitForInit = singleshot(async (_initial) => {
47219
+ this._value = this.initialValue;
47220
+ });
47221
+ /**
47222
+ * Update the in-memory state value.
47223
+ * @param dispatch - New value or updater function receiving current value
47224
+ * @returns Updated state value
47225
+ */
47226
+ this.setState = queued(async (dispatch) => {
47227
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
47228
+ signalId: this.signalId,
47229
+ bucketName: this.bucketName,
47230
+ });
47231
+ if (typeof dispatch === "function") {
47232
+ this._value = await dispatch(this._value);
47233
+ }
47234
+ else {
47235
+ this._value = dispatch;
47236
+ }
47237
+ return this._value;
47238
+ });
47239
+ }
47240
+ /**
47241
+ * Read the current in-memory state value.
47242
+ * @returns Current state value
47243
+ */
47244
+ async getState() {
47245
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
47246
+ signalId: this.signalId,
47247
+ bucketName: this.bucketName,
47248
+ });
47249
+ return this._value;
47250
+ }
47251
+ /** Releases resources held by this instance. */
47252
+ async dispose() {
47253
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47254
+ signalId: this.signalId,
47255
+ bucketName: this.bucketName,
47256
+ });
47257
+ }
47258
+ }
47259
+ /**
47260
+ * No-op state instance that discards all writes.
47261
+ * Used for disabling state in tests or dry-run scenarios.
47262
+ *
47263
+ * Useful when replaying historical candles without needing to accumulate
47264
+ * peakPercent/minutesOpen — the capitulation rule is simply never triggered.
47265
+ */
47266
+ class StateDummyInstance {
47267
+ constructor(initialValue, signalId, bucketName) {
47268
+ this.initialValue = initialValue;
47269
+ this.signalId = signalId;
47270
+ this.bucketName = bucketName;
47271
+ /**
47272
+ * No-op initialization.
47273
+ * @returns Promise that resolves immediately
47274
+ */
47275
+ this.waitForInit = singleshot(async (_initial) => {
47276
+ });
47277
+ }
47278
+ /**
47279
+ * No-op read - always returns initialValue.
47280
+ * @returns initialValue
47281
+ */
47282
+ async getState() {
47283
+ return this.initialValue;
47284
+ }
47285
+ /**
47286
+ * No-op write - discards the value and returns initialValue.
47287
+ * @returns initialValue
47288
+ */
47289
+ async setState(_dispatch) {
47290
+ return this.initialValue;
47291
+ }
47292
+ /** No-op. */
47293
+ async dispose() {
47294
+ }
47295
+ }
47296
+ /**
47297
+ * File-system backed state instance.
47298
+ * Data is persisted atomically to disk via PersistStateAdapter.
47299
+ * State is restored from disk on waitForInit.
47300
+ *
47301
+ * Features:
47302
+ * - Crash-safe atomic file writes
47303
+ * - Functional dispatch support
47304
+ * - Scoped per (signalId, bucketName) pair
47305
+ *
47306
+ * Use in live trading to survive process restarts mid-trade.
47307
+ * Preserves peakPercent and minutesOpen so the capitulation rule
47308
+ * (exit if peak < threshold after N minutes) continues correctly after a crash.
47309
+ */
47310
+ class StatePersistInstance {
47311
+ constructor(initialValue, signalId, bucketName) {
47312
+ this.initialValue = initialValue;
47313
+ this.signalId = signalId;
47314
+ this.bucketName = bucketName;
47315
+ /**
47316
+ * Initialize persistence storage and restore state from disk.
47317
+ * @param initial - Whether this is the first initialization
47318
+ */
47319
+ this.waitForInit = singleshot(async (initial) => {
47320
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT, {
47321
+ signalId: this.signalId,
47322
+ bucketName: this.bucketName,
47323
+ initial,
47324
+ });
47325
+ await PersistStateAdapter.waitForInit(this.signalId, this.bucketName, initial);
47326
+ const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
47327
+ if (data) {
47328
+ this._value = data.data;
47329
+ return;
47330
+ }
47331
+ this._value = this.initialValue;
47332
+ });
47333
+ /**
47334
+ * Update state and persist to disk atomically.
47335
+ * @param dispatch - New value or updater function receiving current value
47336
+ * @returns Updated state value
47337
+ */
47338
+ this.setState = queued(async (dispatch) => {
47339
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
47340
+ signalId: this.signalId,
47341
+ bucketName: this.bucketName,
47342
+ });
47343
+ if (typeof dispatch === "function") {
47344
+ this._value = await dispatch(this._value);
47345
+ }
47346
+ else {
47347
+ this._value = dispatch;
47348
+ }
47349
+ const id = CREATE_KEY_FN$5(this.signalId, this.bucketName);
47350
+ await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
47351
+ return this._value;
47352
+ });
47353
+ }
47354
+ /**
47355
+ * Read the current persisted state value.
47356
+ * @returns Current state value
47357
+ */
47358
+ async getState() {
47359
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
47360
+ signalId: this.signalId,
47361
+ bucketName: this.bucketName,
47362
+ });
47363
+ return this._value;
47364
+ }
47365
+ /** Releases resources held by this instance. */
47366
+ async dispose() {
47367
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47368
+ signalId: this.signalId,
47369
+ bucketName: this.bucketName,
47370
+ });
47371
+ await PersistStateAdapter.dispose(this.signalId, this.bucketName);
47372
+ }
47373
+ }
47374
+ /**
47375
+ * Backtest state adapter with pluggable storage backend.
47376
+ *
47377
+ * Features:
47378
+ * - Adapter pattern for swappable state instance implementations
47379
+ * - Default backend: StateLocalInstance (in-memory, no disk persistence)
47380
+ * - Alternative backends: StatePersistInstance, StateDummyInstance
47381
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47382
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47383
+ *
47384
+ * Primary use case — LLM-driven capitulation rule:
47385
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47386
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47387
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47388
+ * the LLM thesis was not confirmed by market — exit immediately.
47389
+ * State tracks `{ peakPercent, minutesOpen }` per signal across onActivePing ticks.
47390
+ */
47391
+ class StateBacktestAdapter {
47392
+ constructor() {
47393
+ this.StateFactory = StateLocalInstance;
47394
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$5(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47395
+ /**
47396
+ * Disposes all memoized instances for the given signalId.
47397
+ * Called by StateAdapter when a signal is cancelled or closed.
47398
+ * @param signalId - Signal identifier to dispose
47399
+ */
47400
+ this.disposeSignal = (signalId) => {
47401
+ const prefix = CREATE_KEY_FN$5(signalId, "");
47402
+ for (const key of this.getInstance.keys()) {
47403
+ if (key.startsWith(prefix)) {
47404
+ const instance = this.getInstance.get(key);
47405
+ instance && instance.dispose();
47406
+ this.getInstance.clear(key);
47407
+ }
47408
+ }
47409
+ };
47410
+ /**
47411
+ * Read the current state value for a signal.
47412
+ * @param dto.signalId - Signal identifier
47413
+ * @param dto.bucketName - Bucket name
47414
+ * @param dto.initialValue - Default value when no persisted state exists
47415
+ * @returns Current state value
47416
+ */
47417
+ this.getState = async (dto) => {
47418
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_GET, {
47419
+ signalId: dto.signalId,
47420
+ bucketName: dto.bucketName,
47421
+ });
47422
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47423
+ const isInitial = !this.getInstance.has(key);
47424
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47425
+ await instance.waitForInit(isInitial);
47426
+ return await instance.getState();
47427
+ };
47428
+ /**
47429
+ * Update the state value for a signal.
47430
+ * @param dispatch - New value or updater function receiving current value
47431
+ * @param dto.signalId - Signal identifier
47432
+ * @param dto.bucketName - Bucket name
47433
+ * @param dto.initialValue - Default value when no persisted state exists
47434
+ * @returns Updated state value
47435
+ */
47436
+ this.setState = async (dispatch, dto) => {
47437
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_SET, {
47438
+ signalId: dto.signalId,
47439
+ bucketName: dto.bucketName,
47440
+ });
47441
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47442
+ const isInitial = !this.getInstance.has(key);
47443
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47444
+ await instance.waitForInit(isInitial);
47445
+ return await instance.setState(dispatch);
47446
+ };
47447
+ /**
47448
+ * Switches to in-memory adapter (default).
47449
+ * All data lives in process memory only.
47450
+ */
47451
+ this.useLocal = () => {
47452
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
47453
+ this.StateFactory = StateLocalInstance;
47454
+ };
47455
+ /**
47456
+ * Switches to file-system backed adapter.
47457
+ * Data is persisted to disk via PersistStateAdapter.
47458
+ */
47459
+ this.usePersist = () => {
47460
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
47461
+ this.StateFactory = StatePersistInstance;
47462
+ };
47463
+ /**
47464
+ * Switches to dummy adapter that discards all writes.
47465
+ */
47466
+ this.useDummy = () => {
47467
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
47468
+ this.StateFactory = StateDummyInstance;
47469
+ };
47470
+ /**
47471
+ * Switches to a custom state adapter implementation.
47472
+ * @param Ctor - Constructor for the custom state instance
47473
+ */
47474
+ this.useStateAdapter = (Ctor) => {
47475
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47476
+ this.StateFactory = Ctor;
47477
+ };
47478
+ /**
47479
+ * Clears the memoized instance cache.
47480
+ * Call this when process.cwd() changes between strategy iterations
47481
+ * so new instances are created with the updated base path.
47482
+ */
47483
+ this.clear = () => {
47484
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
47485
+ this.getInstance.clear();
47486
+ };
47487
+ }
47488
+ }
47489
+ /**
47490
+ * Live trading state adapter with pluggable storage backend.
47491
+ *
47492
+ * Features:
47493
+ * - Adapter pattern for swappable state instance implementations
47494
+ * - Default backend: StatePersistInstance (file-system backed, survives restarts)
47495
+ * - Alternative backends: StateLocalInstance, StateDummyInstance
47496
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47497
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47498
+ *
47499
+ * Primary use case — LLM-driven capitulation rule:
47500
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47501
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47502
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47503
+ * the LLM thesis was not confirmed by market — exit immediately.
47504
+ * State persists `{ peakPercent, minutesOpen }` per signal across process restarts.
47505
+ */
47506
+ class StateLiveAdapter {
47507
+ constructor() {
47508
+ this.StateFactory = StatePersistInstance;
47509
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$5(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47510
+ /**
47511
+ * Disposes all memoized instances for the given signalId.
47512
+ * Called by StateAdapter when a signal is cancelled or closed.
47513
+ * @param signalId - Signal identifier to dispose
47514
+ */
47515
+ this.disposeSignal = (signalId) => {
47516
+ const prefix = CREATE_KEY_FN$5(signalId, "");
47517
+ for (const key of this.getInstance.keys()) {
47518
+ if (key.startsWith(prefix)) {
47519
+ const instance = this.getInstance.get(key);
47520
+ instance && instance.dispose();
47521
+ this.getInstance.clear(key);
47522
+ }
47523
+ }
47524
+ };
47525
+ /**
47526
+ * Read the current state value for a signal.
47527
+ * @param dto.signalId - Signal identifier
47528
+ * @param dto.bucketName - Bucket name
47529
+ * @param dto.initialValue - Default value when no persisted state exists
47530
+ * @returns Current state value
47531
+ */
47532
+ this.getState = async (dto) => {
47533
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_GET, {
47534
+ signalId: dto.signalId,
47535
+ bucketName: dto.bucketName,
47536
+ });
47537
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47538
+ const isInitial = !this.getInstance.has(key);
47539
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47540
+ await instance.waitForInit(isInitial);
47541
+ return await instance.getState();
47542
+ };
47543
+ /**
47544
+ * Update the state value for a signal.
47545
+ * @param dispatch - New value or updater function receiving current value
47546
+ * @param dto.signalId - Signal identifier
47547
+ * @param dto.bucketName - Bucket name
47548
+ * @param dto.initialValue - Default value when no persisted state exists
47549
+ * @returns Updated state value
47550
+ */
47551
+ this.setState = async (dispatch, dto) => {
47552
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_SET, {
47553
+ signalId: dto.signalId,
47554
+ bucketName: dto.bucketName,
47555
+ });
47556
+ const key = CREATE_KEY_FN$5(dto.signalId, dto.bucketName);
47557
+ const isInitial = !this.getInstance.has(key);
47558
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47559
+ await instance.waitForInit(isInitial);
47560
+ return await instance.setState(dispatch);
47561
+ };
47562
+ /**
47563
+ * Switches to in-memory adapter.
47564
+ * All data lives in process memory only.
47565
+ */
47566
+ this.useLocal = () => {
47567
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
47568
+ this.StateFactory = StateLocalInstance;
47569
+ };
47570
+ /**
47571
+ * Switches to file-system backed adapter (default).
47572
+ * Data is persisted to disk via PersistStateAdapter.
47573
+ */
47574
+ this.usePersist = () => {
47575
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
47576
+ this.StateFactory = StatePersistInstance;
47577
+ };
47578
+ /**
47579
+ * Switches to dummy adapter that discards all writes.
47580
+ */
47581
+ this.useDummy = () => {
47582
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
47583
+ this.StateFactory = StateDummyInstance;
47584
+ };
47585
+ /**
47586
+ * Switches to a custom state adapter implementation.
47587
+ * @param Ctor - Constructor for the custom state instance
47588
+ */
47589
+ this.useStateAdapter = (Ctor) => {
47590
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47591
+ this.StateFactory = Ctor;
47592
+ };
47593
+ /**
47594
+ * Clears the memoized instance cache.
47595
+ * Call this when process.cwd() changes between strategy iterations
47596
+ * so new instances are created with the updated base path.
47597
+ */
47598
+ this.clear = () => {
47599
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR);
47600
+ this.getInstance.clear();
47601
+ };
47602
+ }
47603
+ }
47604
+ /**
47605
+ * Main state adapter that manages both backtest and live state storage.
47606
+ *
47607
+ * Features:
47608
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
47609
+ * - Routes all operations to StateBacktest or StateLive based on dto.backtest
47610
+ * - Singleshot enable pattern prevents duplicate subscriptions
47611
+ * - Cleanup function for proper unsubscription
47612
+ */
47613
+ class StateAdapter {
47614
+ constructor() {
47615
+ /**
47616
+ * Enables state storage by subscribing to signal lifecycle events.
47617
+ * Clears memoized instances in StateBacktest and StateLive when a signal
47618
+ * is cancelled or closed, preventing stale instances from accumulating.
47619
+ * Uses singleshot to ensure one-time subscription.
47620
+ *
47621
+ * @returns Cleanup function that unsubscribes from all emitters
47622
+ */
47623
+ this.enable = singleshot(() => {
47624
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_ENABLE);
47625
+ const unCancel = signalEmitter
47626
+ .filter(({ action }) => action === "cancelled")
47627
+ .connect(({ signal }) => {
47628
+ StateBacktest.disposeSignal(signal.id);
47629
+ StateLive.disposeSignal(signal.id);
47630
+ });
47631
+ const unClose = signalEmitter
47632
+ .filter(({ action }) => action === "closed")
47633
+ .connect(({ signal }) => {
47634
+ StateBacktest.disposeSignal(signal.id);
47635
+ StateLive.disposeSignal(signal.id);
47636
+ });
47637
+ return compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47638
+ });
47639
+ /**
47640
+ * Disables state storage by unsubscribing from signal lifecycle events.
47641
+ * Safe to call multiple times.
47642
+ */
47643
+ this.disable = () => {
47644
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_DISABLE);
47645
+ if (this.enable.hasValue()) {
47646
+ const lastSubscription = this.enable();
47647
+ lastSubscription();
47648
+ }
47649
+ };
47650
+ /**
47651
+ * Read the current state value for a signal.
47652
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47653
+ * @param dto.signalId - Signal identifier
47654
+ * @param dto.bucketName - Bucket name
47655
+ * @param dto.initialValue - Default value when no persisted state exists
47656
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47657
+ * @returns Current state value
47658
+ * @throws Error if adapter is not enabled
47659
+ */
47660
+ this.getState = async (dto) => {
47661
+ if (!this.enable.hasValue()) {
47662
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47663
+ }
47664
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_GET, {
47665
+ signalId: dto.signalId,
47666
+ bucketName: dto.bucketName,
47667
+ backtest: dto.backtest,
47668
+ });
47669
+ if (dto.backtest) {
47670
+ return await StateBacktest.getState(dto);
47671
+ }
47672
+ return await StateLive.getState(dto);
47673
+ };
47674
+ /**
47675
+ * Update the state value for a signal.
47676
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47677
+ * @param dispatch - New value or updater function receiving current value
47678
+ * @param dto.signalId - Signal identifier
47679
+ * @param dto.bucketName - Bucket name
47680
+ * @param dto.initialValue - Default value when no persisted state exists
47681
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47682
+ * @returns Updated state value
47683
+ * @throws Error if adapter is not enabled
47684
+ */
47685
+ this.setState = async (dispatch, dto) => {
47686
+ if (!this.enable.hasValue()) {
47687
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47688
+ }
47689
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_SET, {
47690
+ signalId: dto.signalId,
47691
+ bucketName: dto.bucketName,
47692
+ backtest: dto.backtest,
47693
+ });
47694
+ if (dto.backtest) {
47695
+ return await StateBacktest.setState(dispatch, dto);
47696
+ }
47697
+ return await StateLive.setState(dispatch, dto);
47698
+ };
47699
+ }
47700
+ }
47701
+ /**
47702
+ * Global singleton instance of StateAdapter.
47703
+ * Provides unified state management for backtest and live trading.
47704
+ */
47705
+ const State = new StateAdapter();
47706
+ /**
47707
+ * Global singleton instance of StateLiveAdapter.
47708
+ * Provides live trading state storage with pluggable backends.
47709
+ */
47710
+ const StateLive = new StateLiveAdapter();
47711
+ /**
47712
+ * Global singleton instance of StateBacktestAdapter.
47713
+ * Provides backtest state storage with pluggable backends.
47714
+ */
47715
+ const StateBacktest = new StateBacktestAdapter();
47716
+
47717
+ const GET_SIGNAL_STATE_METHOD_NAME = "signal.getSignalState";
47718
+ const SET_SIGNAL_STATE_METHOD_NAME = "signal.setSignalState";
47043
47719
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
47044
47720
  const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
47045
47721
  /**
@@ -47115,6 +47791,243 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
47115
47791
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47116
47792
  return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
47117
47793
  }
47794
+ /**
47795
+ * Reads the state value scoped to the current active signal.
47796
+ *
47797
+ * Resolves the active pending signal automatically from execution context.
47798
+ * If no pending signal exists, logs a warning and returns the initialValue.
47799
+ *
47800
+ * Automatically detects backtest/live mode from execution context.
47801
+ *
47802
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47803
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47804
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47805
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47806
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47807
+ *
47808
+ * @param dto.bucketName - State bucket name
47809
+ * @param dto.initialValue - Default value when no persisted state exists
47810
+ * @returns Promise resolving to current state value, or initialValue if no signal
47811
+ *
47812
+ * @deprecated Better use `createSignalState().getState` with codestyle native syntax
47813
+ *
47814
+ * @example
47815
+ * ```typescript
47816
+ * import { getSignalState } from "backtest-kit";
47817
+ *
47818
+ * const { peakPercent, minutesOpen } = await getSignalState({
47819
+ * bucketName: "trade",
47820
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
47821
+ * });
47822
+ * if (minutesOpen >= 15 && peakPercent < 0.3) {
47823
+ * await commitMarketClose(symbol); // capitulate — LLM thesis not confirmed
47824
+ * }
47825
+ * ```
47826
+ */
47827
+ async function getSignalState(dto) {
47828
+ const { bucketName, initialValue } = dto;
47829
+ backtest.loggerService.info(GET_SIGNAL_STATE_METHOD_NAME, { bucketName });
47830
+ if (!ExecutionContextService.hasContext()) {
47831
+ throw new Error("getSignalState requires an execution context");
47832
+ }
47833
+ if (!MethodContextService.hasContext()) {
47834
+ throw new Error("getSignalState requires a method context");
47835
+ }
47836
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47837
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47838
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47839
+ let signal;
47840
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47841
+ return await State.getState({
47842
+ signalId: signal.id,
47843
+ bucketName,
47844
+ initialValue,
47845
+ backtest: isBacktest,
47846
+ });
47847
+ }
47848
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47849
+ return await State.getState({
47850
+ signalId: signal.id,
47851
+ bucketName,
47852
+ initialValue,
47853
+ backtest: isBacktest,
47854
+ });
47855
+ }
47856
+ throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47857
+ }
47858
+ /**
47859
+ * Updates the state value scoped to the current active signal.
47860
+ *
47861
+ * Resolves the active pending signal automatically from execution context.
47862
+ * If no pending signal exists, logs a warning and returns without writing.
47863
+ *
47864
+ * Automatically detects backtest/live mode from execution context.
47865
+ *
47866
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47867
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47868
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47869
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47870
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47871
+ *
47872
+ * @param dto.bucketName - State bucket name
47873
+ * @param dto.initialValue - Default value when no persisted state exists
47874
+ * @param dto.dispatch - New value or updater function receiving current value
47875
+ * @returns Promise resolving to updated state value, or initialValue if no signal
47876
+ *
47877
+ * @deprecated Better use `createSignalState().setState` with codestyle native syntax
47878
+ *
47879
+ * @example
47880
+ * ```typescript
47881
+ * import { setSignalState } from "backtest-kit";
47882
+ *
47883
+ * await setSignalState(
47884
+ * dispatch: (s) => ({
47885
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
47886
+ * minutesOpen: s.minutesOpen + 1,
47887
+ * }),
47888
+ * {
47889
+ * bucketName: "trade",
47890
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
47891
+ * }
47892
+ * );
47893
+ * ```
47894
+ */
47895
+ async function setSignalState(dispatch, dto) {
47896
+ const { bucketName, initialValue } = dto;
47897
+ backtest.loggerService.info(SET_SIGNAL_STATE_METHOD_NAME, { bucketName });
47898
+ if (!ExecutionContextService.hasContext()) {
47899
+ throw new Error("setSignalState requires an execution context");
47900
+ }
47901
+ if (!MethodContextService.hasContext()) {
47902
+ throw new Error("setSignalState requires a method context");
47903
+ }
47904
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47905
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47906
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47907
+ let signal;
47908
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47909
+ return await State.setState(dispatch, {
47910
+ signalId: signal.id,
47911
+ bucketName,
47912
+ initialValue,
47913
+ backtest: isBacktest,
47914
+ });
47915
+ }
47916
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47917
+ return await State.setState(dispatch, {
47918
+ signalId: signal.id,
47919
+ bucketName,
47920
+ initialValue,
47921
+ backtest: isBacktest,
47922
+ });
47923
+ }
47924
+ throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47925
+ }
47926
+
47927
+ const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
47928
+ const CREATE_SET_STATE_FN = (params) => async (dispatch) => {
47929
+ if (!ExecutionContextService.hasContext()) {
47930
+ throw new Error("createSignalState requires an execution context");
47931
+ }
47932
+ if (!MethodContextService.hasContext()) {
47933
+ throw new Error("createSignalState requires a method context");
47934
+ }
47935
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47936
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47937
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47938
+ let signal;
47939
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47940
+ return await State.setState(dispatch, {
47941
+ backtest: isBacktest,
47942
+ bucketName: params.bucketName,
47943
+ initialValue: params.initialValue,
47944
+ signalId: signal.id,
47945
+ });
47946
+ }
47947
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47948
+ return await State.setState(dispatch, {
47949
+ backtest: isBacktest,
47950
+ bucketName: params.bucketName,
47951
+ initialValue: params.initialValue,
47952
+ signalId: signal.id,
47953
+ });
47954
+ }
47955
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
47956
+ };
47957
+ const CREATE_GET_STATE_FN = (params) => async () => {
47958
+ if (!ExecutionContextService.hasContext()) {
47959
+ throw new Error("createSignalState requires an execution context");
47960
+ }
47961
+ if (!MethodContextService.hasContext()) {
47962
+ throw new Error("createSignalState requires a method context");
47963
+ }
47964
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47965
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47966
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47967
+ let signal;
47968
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47969
+ return await State.getState({
47970
+ backtest: isBacktest,
47971
+ bucketName: params.bucketName,
47972
+ initialValue: params.initialValue,
47973
+ signalId: signal.id,
47974
+ });
47975
+ }
47976
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
47977
+ return await State.getState({
47978
+ backtest: isBacktest,
47979
+ bucketName: params.bucketName,
47980
+ initialValue: params.initialValue,
47981
+ signalId: signal.id,
47982
+ });
47983
+ }
47984
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
47985
+ };
47986
+ /**
47987
+ * Creates a bound [getState, setState] tuple scoped to a bucket and initial value.
47988
+ *
47989
+ * Both returned functions resolve the active pending or scheduled signal and the
47990
+ * backtest/live flag automatically from execution context — no signalId argument required.
47991
+ *
47992
+ * Automatically detects backtest/live mode from execution context.
47993
+ *
47994
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47995
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47996
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47997
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47998
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47999
+ *
48000
+ * @param params.bucketName - Logical namespace for grouping state buckets within a signal
48001
+ * @param params.initialValue - Default value when no persisted state exists
48002
+ * @returns Tuple [getState, setState] bound to the bucket and initial value
48003
+ *
48004
+ * @example
48005
+ * ```typescript
48006
+ * import { createSignalState } from "backtest-kit";
48007
+ *
48008
+ * const [getTradeState, setTradeState] = createSignalState({
48009
+ * bucketName: "trade",
48010
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
48011
+ * });
48012
+ *
48013
+ * // in onActivePing:
48014
+ * await setTradeState((s) => ({
48015
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
48016
+ * minutesOpen: s.minutesOpen + 1,
48017
+ * }));
48018
+ * const { peakPercent, minutesOpen } = await getTradeState();
48019
+ * if (minutesOpen >= 15 && peakPercent < 0.3) await commitMarketClose(symbol);
48020
+ * ```
48021
+ */
48022
+ function createSignalState(params) {
48023
+ backtest.loggerService.info(CREATE_SIGNAL_STATE_METHOD_NAME, {
48024
+ bucketName: params.bucketName,
48025
+ });
48026
+ return [
48027
+ CREATE_GET_STATE_FN(params),
48028
+ CREATE_SET_STATE_FN(params),
48029
+ ];
48030
+ }
47118
48031
 
47119
48032
  const DEFAULT_BM25_K1 = 1.5;
47120
48033
  const DEFAULT_BM25_B = 0.75;
@@ -47201,7 +48114,7 @@ const createSearchIndex = () => {
47201
48114
  return { upsert, remove, list, search, read };
47202
48115
  };
47203
48116
 
47204
- const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
48117
+ const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47205
48118
  const LIST_MEMORY_FN = ({ id, content }) => ({
47206
48119
  memoryId: id,
47207
48120
  content: content,
@@ -47222,18 +48135,35 @@ const MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ = "MemoryPersistInstance.readMemo
47222
48135
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH = "MemoryPersistInstance.searchMemory";
47223
48136
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST = "MemoryPersistInstance.listMemory";
47224
48137
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE = "MemoryPersistInstance.removeMemory";
48138
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "MemoryBacktestAdapter.dispose";
48139
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE = "MemoryBacktestAdapter.writeMemory";
48140
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH = "MemoryBacktestAdapter.searchMemory";
48141
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST = "MemoryBacktestAdapter.listMemory";
48142
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE = "MemoryBacktestAdapter.removeMemory";
48143
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ = "MemoryBacktestAdapter.readMemory";
48144
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryBacktestAdapter.useLocal";
48145
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryBacktestAdapter.usePersist";
48146
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryBacktestAdapter.useDummy";
48147
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryBacktestAdapter.useMemoryAdapter";
48148
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "MemoryBacktestAdapter.clear";
48149
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "MemoryLiveAdapter.dispose";
48150
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE = "MemoryLiveAdapter.writeMemory";
48151
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH = "MemoryLiveAdapter.searchMemory";
48152
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST = "MemoryLiveAdapter.listMemory";
48153
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE = "MemoryLiveAdapter.removeMemory";
48154
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_READ = "MemoryLiveAdapter.readMemory";
48155
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryLiveAdapter.useLocal";
48156
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryLiveAdapter.usePersist";
48157
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryLiveAdapter.useDummy";
48158
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryLiveAdapter.useMemoryAdapter";
48159
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR = "MemoryLiveAdapter.clear";
47225
48160
  const MEMORY_ADAPTER_METHOD_NAME_ENABLE = "MemoryAdapter.enable";
47226
48161
  const MEMORY_ADAPTER_METHOD_NAME_DISABLE = "MemoryAdapter.disable";
47227
- const MEMORY_ADAPTER_METHOD_NAME_DISPOSE = "MemoryAdapter.dispose";
47228
48162
  const MEMORY_ADAPTER_METHOD_NAME_WRITE = "MemoryAdapter.writeMemory";
47229
48163
  const MEMORY_ADAPTER_METHOD_NAME_SEARCH = "MemoryAdapter.searchMemory";
47230
48164
  const MEMORY_ADAPTER_METHOD_NAME_LIST = "MemoryAdapter.listMemory";
47231
48165
  const MEMORY_ADAPTER_METHOD_NAME_REMOVE = "MemoryAdapter.removeMemory";
47232
48166
  const MEMORY_ADAPTER_METHOD_NAME_READ = "MemoryAdapter.readMemory";
47233
- const MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryAdapter.useLocal";
47234
- const MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryAdapter.usePersist";
47235
- const MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryAdapter.useDummy";
47236
- const MEMORY_ADAPTER_METHOD_NAME_CLEAR = "MemoryAdapter.clear";
47237
48167
  /**
47238
48168
  * In-memory BM25 search index backed instance.
47239
48169
  * All data lives in the process memory only - no disk persistence.
@@ -47258,7 +48188,7 @@ class MemoryLocalInstance {
47258
48188
  * Write a value into the BM25 index.
47259
48189
  * @param memoryId - Unique entry identifier
47260
48190
  * @param value - Value to store and index
47261
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
48191
+ * @param description - BM25 index string
47262
48192
  */
47263
48193
  async writeMemory(memoryId, value, description) {
47264
48194
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
@@ -47329,7 +48259,7 @@ class MemoryLocalInstance {
47329
48259
  }
47330
48260
  /** Releases resources held by this instance. */
47331
48261
  dispose() {
47332
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
48262
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47333
48263
  signalId: this.signalId,
47334
48264
  bucketName: this.bucketName,
47335
48265
  });
@@ -47378,7 +48308,7 @@ class MemoryPersistInstance {
47378
48308
  * Write a value to disk and update the BM25 index.
47379
48309
  * @param memoryId - Unique entry identifier
47380
48310
  * @param value - Value to persist and index
47381
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
48311
+ * @param index - BM25 index string; defaults to JSON.stringify(value)
47382
48312
  */
47383
48313
  async writeMemory(memoryId, value, index = JSON.stringify(value)) {
47384
48314
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
@@ -47452,7 +48382,7 @@ class MemoryPersistInstance {
47452
48382
  }
47453
48383
  /** Releases resources held by this instance. */
47454
48384
  dispose() {
47455
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
48385
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47456
48386
  signalId: this.signalId,
47457
48387
  bucketName: this.bucketName,
47458
48388
  });
@@ -47512,48 +48442,377 @@ class MemoryDummyInstance {
47512
48442
  }
47513
48443
  }
47514
48444
  /**
47515
- * Facade for memory instances scoped per (signalId, bucketName).
47516
- * Manages lazy initialization and instance lifecycle.
48445
+ * Backtest memory adapter with pluggable storage backend.
47517
48446
  *
47518
48447
  * Features:
47519
- * - Memoized instances per (signalId, bucketName) pair
47520
- * - Swappable backend via useLocal(), usePersist(), useDummy()
47521
- * - Default backend: MemoryPersistInstance (in-memory BM25 + persist storage)
48448
+ * - Adapter pattern for swappable memory instance implementations
48449
+ * - Default backend: MemoryLocalInstance (in-memory BM25, no disk persistence)
48450
+ * - Alternative backends: MemoryPersistInstance, MemoryDummyInstance
48451
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
48452
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
48453
+ *
48454
+ * Use this adapter for backtest memory storage.
47522
48455
  */
47523
- class MemoryAdapter {
48456
+ class MemoryBacktestAdapter {
48457
+ constructor() {
48458
+ this.MemoryFactory = MemoryLocalInstance;
48459
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
48460
+ /**
48461
+ * Disposes all memoized instances for the given signalId.
48462
+ * Called by MemoryAdapter when a signal is cancelled or closed.
48463
+ * @param signalId - Signal identifier to dispose
48464
+ */
48465
+ this.disposeSignal = (signalId) => {
48466
+ const prefix = CREATE_KEY_FN$4(signalId, "");
48467
+ for (const key of this.getInstance.keys()) {
48468
+ if (key.startsWith(prefix)) {
48469
+ const instance = this.getInstance.get(key);
48470
+ instance && instance.dispose();
48471
+ this.getInstance.clear(key);
48472
+ }
48473
+ }
48474
+ };
48475
+ /**
48476
+ * Write a value to memory.
48477
+ * @param dto.memoryId - Unique entry identifier
48478
+ * @param dto.value - Value to store
48479
+ * @param dto.signalId - Signal identifier
48480
+ * @param dto.bucketName - Bucket name
48481
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48482
+ */
48483
+ this.writeMemory = async (dto) => {
48484
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
48485
+ signalId: dto.signalId,
48486
+ bucketName: dto.bucketName,
48487
+ memoryId: dto.memoryId,
48488
+ });
48489
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48490
+ const isInitial = !this.getInstance.has(key);
48491
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48492
+ await instance.waitForInit(isInitial);
48493
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48494
+ };
48495
+ /**
48496
+ * Search memory using BM25 full-text scoring.
48497
+ * @param dto.query - Search query string
48498
+ * @param dto.signalId - Signal identifier
48499
+ * @param dto.bucketName - Bucket name
48500
+ * @returns Matching entries sorted by relevance score
48501
+ */
48502
+ this.searchMemory = async (dto) => {
48503
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH, {
48504
+ signalId: dto.signalId,
48505
+ bucketName: dto.bucketName,
48506
+ query: dto.query,
48507
+ });
48508
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48509
+ const isInitial = !this.getInstance.has(key);
48510
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48511
+ await instance.waitForInit(isInitial);
48512
+ return await instance.searchMemory(dto.query, dto.settings);
48513
+ };
48514
+ /**
48515
+ * List all entries in memory.
48516
+ * @param dto.signalId - Signal identifier
48517
+ * @param dto.bucketName - Bucket name
48518
+ * @returns Array of all stored entries
48519
+ */
48520
+ this.listMemory = async (dto) => {
48521
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST, {
48522
+ signalId: dto.signalId,
48523
+ bucketName: dto.bucketName,
48524
+ });
48525
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48526
+ const isInitial = !this.getInstance.has(key);
48527
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48528
+ await instance.waitForInit(isInitial);
48529
+ return await instance.listMemory();
48530
+ };
48531
+ /**
48532
+ * Remove an entry from memory.
48533
+ * @param dto.memoryId - Unique entry identifier
48534
+ * @param dto.signalId - Signal identifier
48535
+ * @param dto.bucketName - Bucket name
48536
+ */
48537
+ this.removeMemory = async (dto) => {
48538
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
48539
+ signalId: dto.signalId,
48540
+ bucketName: dto.bucketName,
48541
+ memoryId: dto.memoryId,
48542
+ });
48543
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48544
+ const isInitial = !this.getInstance.has(key);
48545
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48546
+ await instance.waitForInit(isInitial);
48547
+ return await instance.removeMemory(dto.memoryId);
48548
+ };
48549
+ /**
48550
+ * Read a single entry from memory.
48551
+ * @param dto.memoryId - Unique entry identifier
48552
+ * @param dto.signalId - Signal identifier
48553
+ * @param dto.bucketName - Bucket name
48554
+ * @returns Entry value
48555
+ * @throws Error if entry not found
48556
+ */
48557
+ this.readMemory = async (dto) => {
48558
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ, {
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.readMemory(dto.memoryId);
48568
+ };
48569
+ /**
48570
+ * Switches to in-memory BM25 adapter (default).
48571
+ * All data lives in process memory only.
48572
+ */
48573
+ this.useLocal = () => {
48574
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
48575
+ this.MemoryFactory = MemoryLocalInstance;
48576
+ };
48577
+ /**
48578
+ * Switches to file-system backed adapter.
48579
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
48580
+ */
48581
+ this.usePersist = () => {
48582
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
48583
+ this.MemoryFactory = MemoryPersistInstance;
48584
+ };
48585
+ /**
48586
+ * Switches to dummy adapter that discards all writes.
48587
+ */
48588
+ this.useDummy = () => {
48589
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
48590
+ this.MemoryFactory = MemoryDummyInstance;
48591
+ };
48592
+ /**
48593
+ * Switches to a custom memory adapter implementation.
48594
+ * @param Ctor - Constructor for the custom memory instance
48595
+ */
48596
+ this.useMemoryAdapter = (Ctor) => {
48597
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
48598
+ this.MemoryFactory = Ctor;
48599
+ };
48600
+ /**
48601
+ * Clears the memoized instance cache.
48602
+ * Call this when process.cwd() changes between strategy iterations
48603
+ * so new instances are created with the updated base path.
48604
+ */
48605
+ this.clear = () => {
48606
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
48607
+ this.getInstance.clear();
48608
+ };
48609
+ }
48610
+ }
48611
+ /**
48612
+ * Live trading memory adapter with pluggable storage backend.
48613
+ *
48614
+ * Features:
48615
+ * - Adapter pattern for swappable memory instance implementations
48616
+ * - Default backend: MemoryPersistInstance (file-system backed, survives restarts)
48617
+ * - Alternative backends: MemoryLocalInstance, MemoryDummyInstance
48618
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
48619
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
48620
+ *
48621
+ * Use this adapter for live trading memory storage.
48622
+ */
48623
+ class MemoryLiveAdapter {
47524
48624
  constructor() {
47525
48625
  this.MemoryFactory = MemoryPersistInstance;
47526
48626
  this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
47527
48627
  /**
47528
- * Activates the adapter by subscribing to signal lifecycle events.
47529
- * Clears memoized instances for a signalId when it is cancelled or closed,
47530
- * preventing stale instances from accumulating in memory.
47531
- * Idempotent — subsequent calls return the same subscription handle.
47532
- * Must be called before any memory method is used.
48628
+ * Disposes all memoized instances for the given signalId.
48629
+ * Called by MemoryAdapter when a signal is cancelled or closed.
48630
+ * @param signalId - Signal identifier to dispose
48631
+ */
48632
+ this.disposeSignal = (signalId) => {
48633
+ const prefix = CREATE_KEY_FN$4(signalId, "");
48634
+ for (const key of this.getInstance.keys()) {
48635
+ if (key.startsWith(prefix)) {
48636
+ const instance = this.getInstance.get(key);
48637
+ instance && instance.dispose();
48638
+ this.getInstance.clear(key);
48639
+ }
48640
+ }
48641
+ };
48642
+ /**
48643
+ * Write a value to memory.
48644
+ * @param dto.memoryId - Unique entry identifier
48645
+ * @param dto.value - Value to store
48646
+ * @param dto.signalId - Signal identifier
48647
+ * @param dto.bucketName - Bucket name
48648
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48649
+ */
48650
+ this.writeMemory = async (dto) => {
48651
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
48652
+ signalId: dto.signalId,
48653
+ bucketName: dto.bucketName,
48654
+ memoryId: dto.memoryId,
48655
+ });
48656
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48657
+ const isInitial = !this.getInstance.has(key);
48658
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48659
+ await instance.waitForInit(isInitial);
48660
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48661
+ };
48662
+ /**
48663
+ * Search memory using BM25 full-text scoring.
48664
+ * @param dto.query - Search query string
48665
+ * @param dto.signalId - Signal identifier
48666
+ * @param dto.bucketName - Bucket name
48667
+ * @returns Matching entries sorted by relevance score
48668
+ */
48669
+ this.searchMemory = async (dto) => {
48670
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH, {
48671
+ signalId: dto.signalId,
48672
+ bucketName: dto.bucketName,
48673
+ query: dto.query,
48674
+ });
48675
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48676
+ const isInitial = !this.getInstance.has(key);
48677
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48678
+ await instance.waitForInit(isInitial);
48679
+ return await instance.searchMemory(dto.query, dto.settings);
48680
+ };
48681
+ /**
48682
+ * List all entries in memory.
48683
+ * @param dto.signalId - Signal identifier
48684
+ * @param dto.bucketName - Bucket name
48685
+ * @returns Array of all stored entries
48686
+ */
48687
+ this.listMemory = async (dto) => {
48688
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST, {
48689
+ signalId: dto.signalId,
48690
+ bucketName: dto.bucketName,
48691
+ });
48692
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48693
+ const isInitial = !this.getInstance.has(key);
48694
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48695
+ await instance.waitForInit(isInitial);
48696
+ return await instance.listMemory();
48697
+ };
48698
+ /**
48699
+ * Remove an entry from memory.
48700
+ * @param dto.memoryId - Unique entry identifier
48701
+ * @param dto.signalId - Signal identifier
48702
+ * @param dto.bucketName - Bucket name
48703
+ */
48704
+ this.removeMemory = async (dto) => {
48705
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
48706
+ signalId: dto.signalId,
48707
+ bucketName: dto.bucketName,
48708
+ memoryId: dto.memoryId,
48709
+ });
48710
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
48711
+ const isInitial = !this.getInstance.has(key);
48712
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
48713
+ await instance.waitForInit(isInitial);
48714
+ return await instance.removeMemory(dto.memoryId);
48715
+ };
48716
+ /**
48717
+ * Read a single entry from memory.
48718
+ * @param dto.memoryId - Unique entry identifier
48719
+ * @param dto.signalId - Signal identifier
48720
+ * @param dto.bucketName - Bucket name
48721
+ * @returns Entry value
48722
+ * @throws Error if entry not found
48723
+ */
48724
+ this.readMemory = async (dto) => {
48725
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_READ, {
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.readMemory(dto.memoryId);
48735
+ };
48736
+ /**
48737
+ * Switches to in-memory BM25 adapter.
48738
+ * All data lives in process memory only.
48739
+ */
48740
+ this.useLocal = () => {
48741
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
48742
+ this.MemoryFactory = MemoryLocalInstance;
48743
+ };
48744
+ /**
48745
+ * Switches to file-system backed adapter (default).
48746
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
48747
+ */
48748
+ this.usePersist = () => {
48749
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
48750
+ this.MemoryFactory = MemoryPersistInstance;
48751
+ };
48752
+ /**
48753
+ * Switches to dummy adapter that discards all writes.
48754
+ */
48755
+ this.useDummy = () => {
48756
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
48757
+ this.MemoryFactory = MemoryDummyInstance;
48758
+ };
48759
+ /**
48760
+ * Switches to a custom memory adapter implementation.
48761
+ * @param Ctor - Constructor for the custom memory instance
48762
+ */
48763
+ this.useMemoryAdapter = (Ctor) => {
48764
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
48765
+ this.MemoryFactory = Ctor;
48766
+ };
48767
+ /**
48768
+ * Clears the memoized instance cache.
48769
+ * Call this when process.cwd() changes between strategy iterations
48770
+ * so new instances are created with the updated base path.
48771
+ */
48772
+ this.clear = () => {
48773
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR);
48774
+ this.getInstance.clear();
48775
+ };
48776
+ }
48777
+ }
48778
+ /**
48779
+ * Main memory adapter that manages both backtest and live memory storage.
48780
+ *
48781
+ * Features:
48782
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
48783
+ * - Routes all operations to MemoryBacktest or MemoryLive based on dto.backtest
48784
+ * - Singleshot enable pattern prevents duplicate subscriptions
48785
+ * - Cleanup function for proper unsubscription
48786
+ */
48787
+ class MemoryAdapter {
48788
+ constructor() {
48789
+ /**
48790
+ * Enables memory storage by subscribing to signal lifecycle events.
48791
+ * Clears memoized instances in MemoryBacktest and MemoryLive when a signal
48792
+ * is cancelled or closed, preventing stale instances from accumulating.
48793
+ * Uses singleshot to ensure one-time subscription.
48794
+ *
48795
+ * @returns Cleanup function that unsubscribes from all emitters
47533
48796
  */
47534
48797
  this.enable = singleshot(() => {
47535
48798
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
47536
- const handleDispose = (signalId) => {
47537
- const prefix = CREATE_KEY_FN$4(signalId, "");
47538
- for (const key of this.getInstance.keys()) {
47539
- if (key.startsWith(prefix)) {
47540
- const instance = this.getInstance.get(key);
47541
- instance && instance.dispose();
47542
- this.getInstance.clear(key);
47543
- }
47544
- }
47545
- };
47546
48799
  const unCancel = signalEmitter
47547
48800
  .filter(({ action }) => action === "cancelled")
47548
- .connect(({ signal }) => handleDispose(signal.id));
48801
+ .connect(({ signal }) => {
48802
+ MemoryBacktest.disposeSignal(signal.id);
48803
+ MemoryLive.disposeSignal(signal.id);
48804
+ });
47549
48805
  const unClose = signalEmitter
47550
48806
  .filter(({ action }) => action === "closed")
47551
- .connect(({ signal }) => handleDispose(signal.id));
47552
- return compose(() => unCancel(), () => unClose());
48807
+ .connect(({ signal }) => {
48808
+ MemoryBacktest.disposeSignal(signal.id);
48809
+ MemoryLive.disposeSignal(signal.id);
48810
+ });
48811
+ return compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47553
48812
  });
47554
48813
  /**
47555
- * Deactivates the adapter by unsubscribing from signal lifecycle events.
47556
- * No-op if enable() was never called.
48814
+ * Disables memory storage by unsubscribing from signal lifecycle events.
48815
+ * Safe to call multiple times.
47557
48816
  */
47558
48817
  this.disable = () => {
47559
48818
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISABLE);
@@ -47564,11 +48823,13 @@ class MemoryAdapter {
47564
48823
  };
47565
48824
  /**
47566
48825
  * Write a value to memory.
48826
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47567
48827
  * @param dto.memoryId - Unique entry identifier
47568
48828
  * @param dto.value - Value to store
47569
48829
  * @param dto.signalId - Signal identifier
47570
48830
  * @param dto.bucketName - Bucket name
47571
- * @param dto.description - Optional BM25 index string; defaults to JSON.stringify(value)
48831
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
48832
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47572
48833
  */
47573
48834
  this.writeMemory = async (dto) => {
47574
48835
  if (!this.enable.hasValue()) {
@@ -47578,18 +48839,20 @@ class MemoryAdapter {
47578
48839
  signalId: dto.signalId,
47579
48840
  bucketName: dto.bucketName,
47580
48841
  memoryId: dto.memoryId,
48842
+ backtest: dto.backtest,
47581
48843
  });
47582
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47583
- const isInitial = !this.getInstance.has(key);
47584
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47585
- await instance.waitForInit(isInitial);
47586
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
48844
+ if (dto.backtest) {
48845
+ return await MemoryBacktest.writeMemory(dto);
48846
+ }
48847
+ return await MemoryLive.writeMemory(dto);
47587
48848
  };
47588
48849
  /**
47589
48850
  * Search memory using BM25 full-text scoring.
48851
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47590
48852
  * @param dto.query - Search query string
47591
48853
  * @param dto.signalId - Signal identifier
47592
48854
  * @param dto.bucketName - Bucket name
48855
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47593
48856
  * @returns Matching entries sorted by relevance score
47594
48857
  */
47595
48858
  this.searchMemory = async (dto) => {
@@ -47600,17 +48863,19 @@ class MemoryAdapter {
47600
48863
  signalId: dto.signalId,
47601
48864
  bucketName: dto.bucketName,
47602
48865
  query: dto.query,
48866
+ backtest: dto.backtest,
47603
48867
  });
47604
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47605
- const isInitial = !this.getInstance.has(key);
47606
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47607
- await instance.waitForInit(isInitial);
47608
- return await instance.searchMemory(dto.query, dto.settings);
48868
+ if (dto.backtest) {
48869
+ return await MemoryBacktest.searchMemory(dto);
48870
+ }
48871
+ return await MemoryLive.searchMemory(dto);
47609
48872
  };
47610
48873
  /**
47611
48874
  * List all entries in memory.
48875
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47612
48876
  * @param dto.signalId - Signal identifier
47613
48877
  * @param dto.bucketName - Bucket name
48878
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47614
48879
  * @returns Array of all stored entries
47615
48880
  */
47616
48881
  this.listMemory = async (dto) => {
@@ -47620,18 +48885,20 @@ class MemoryAdapter {
47620
48885
  backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_LIST, {
47621
48886
  signalId: dto.signalId,
47622
48887
  bucketName: dto.bucketName,
48888
+ backtest: dto.backtest,
47623
48889
  });
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.listMemory();
48890
+ if (dto.backtest) {
48891
+ return await MemoryBacktest.listMemory(dto);
48892
+ }
48893
+ return await MemoryLive.listMemory(dto);
47629
48894
  };
47630
48895
  /**
47631
48896
  * Remove an entry from memory.
48897
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47632
48898
  * @param dto.memoryId - Unique entry identifier
47633
48899
  * @param dto.signalId - Signal identifier
47634
48900
  * @param dto.bucketName - Bucket name
48901
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47635
48902
  */
47636
48903
  this.removeMemory = async (dto) => {
47637
48904
  if (!this.enable.hasValue()) {
@@ -47641,18 +48908,20 @@ class MemoryAdapter {
47641
48908
  signalId: dto.signalId,
47642
48909
  bucketName: dto.bucketName,
47643
48910
  memoryId: dto.memoryId,
48911
+ backtest: dto.backtest,
47644
48912
  });
47645
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47646
- const isInitial = !this.getInstance.has(key);
47647
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47648
- await instance.waitForInit(isInitial);
47649
- return await instance.removeMemory(dto.memoryId);
48913
+ if (dto.backtest) {
48914
+ return await MemoryBacktest.removeMemory(dto);
48915
+ }
48916
+ return await MemoryLive.removeMemory(dto);
47650
48917
  };
47651
48918
  /**
47652
48919
  * Read a single entry from memory.
48920
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47653
48921
  * @param dto.memoryId - Unique entry identifier
47654
48922
  * @param dto.signalId - Signal identifier
47655
48923
  * @param dto.bucketName - Bucket name
48924
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47656
48925
  * @returns Entry value
47657
48926
  * @throws Error if entry not found
47658
48927
  */
@@ -47664,56 +48933,30 @@ class MemoryAdapter {
47664
48933
  signalId: dto.signalId,
47665
48934
  bucketName: dto.bucketName,
47666
48935
  memoryId: dto.memoryId,
48936
+ backtest: dto.backtest,
47667
48937
  });
47668
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47669
- const isInitial = !this.getInstance.has(key);
47670
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47671
- await instance.waitForInit(isInitial);
47672
- return await instance.readMemory(dto.memoryId);
47673
- };
47674
- /**
47675
- * Switches to in-memory BM25 adapter (default).
47676
- * All data lives in process memory only.
47677
- */
47678
- this.useLocal = () => {
47679
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL);
47680
- this.MemoryFactory = MemoryLocalInstance;
47681
- };
47682
- /**
47683
- * Switches to file-system backed adapter.
47684
- * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
47685
- */
47686
- this.usePersist = () => {
47687
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST);
47688
- this.MemoryFactory = MemoryPersistInstance;
47689
- };
47690
- /**
47691
- * Switches to dummy adapter that discards all writes.
47692
- */
47693
- this.useDummy = () => {
47694
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY);
47695
- this.MemoryFactory = MemoryDummyInstance;
47696
- };
47697
- /**
47698
- * Clears the memoized instance cache.
47699
- * Call this when process.cwd() changes between strategy iterations
47700
- * so new instances are created with the updated base path.
47701
- */
47702
- this.clear = () => {
47703
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_CLEAR);
47704
- this.getInstance.clear();
47705
- };
47706
- /**
47707
- * Releases resources held by this adapter.
47708
- * Delegates to disable() to unsubscribe from signal lifecycle events.
47709
- */
47710
- this.dispose = () => {
47711
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISPOSE);
47712
- this.disable();
48938
+ if (dto.backtest) {
48939
+ return await MemoryBacktest.readMemory(dto);
48940
+ }
48941
+ return await MemoryLive.readMemory(dto);
47713
48942
  };
47714
48943
  }
47715
48944
  }
48945
+ /**
48946
+ * Global singleton instance of MemoryAdapter.
48947
+ * Provides unified memory management for backtest and live trading.
48948
+ */
47716
48949
  const Memory = new MemoryAdapter();
48950
+ /**
48951
+ * Global singleton instance of MemoryLiveAdapter.
48952
+ * Provides live trading memory storage with pluggable backends.
48953
+ */
48954
+ const MemoryLive = new MemoryLiveAdapter();
48955
+ /**
48956
+ * Global singleton instance of MemoryBacktestAdapter.
48957
+ * Provides backtest memory storage with pluggable backends.
48958
+ */
48959
+ const MemoryBacktest = new MemoryBacktestAdapter();
47717
48960
 
47718
48961
  const WRITE_MEMORY_METHOD_NAME = "memory.writeMemory";
47719
48962
  const READ_MEMORY_METHOD_NAME = "memory.readMemory";
@@ -47723,23 +48966,20 @@ const REMOVE_MEMORY_METHOD_NAME = "memory.removeMemory";
47723
48966
  /**
47724
48967
  * Writes a value to memory scoped to the current signal.
47725
48968
  *
47726
- * Reads symbol from execution context and signalId from the active pending signal.
47727
- * If no pending signal exists, logs a warning and returns without writing.
47728
- *
48969
+ * Resolves the active pending or scheduled signal automatically from execution context.
47729
48970
  * Automatically detects backtest/live mode from execution context.
47730
48971
  *
47731
48972
  * @param dto.bucketName - Memory bucket name
47732
48973
  * @param dto.memoryId - Unique memory entry identifier
47733
48974
  * @param dto.value - Value to store
48975
+ * @param dto.description - BM25 index string for contextual search
47734
48976
  * @returns Promise that resolves when write is complete
47735
48977
  *
47736
- * @deprecated Better use Memory.writeMemory with manual signalId argument
47737
- *
47738
48978
  * @example
47739
48979
  * ```typescript
47740
48980
  * import { writeMemory } from "backtest-kit";
47741
48981
  *
47742
- * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 } });
48982
+ * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 }, description: "Signal context at entry" });
47743
48983
  * ```
47744
48984
  */
47745
48985
  async function writeMemory(dto) {
@@ -47757,33 +48997,41 @@ async function writeMemory(dto) {
47757
48997
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47758
48998
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47759
48999
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47760
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47761
- if (!signal) {
47762
- console.warn(`backtest-kit writeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49000
+ let signal;
49001
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49002
+ await Memory.writeMemory({
49003
+ memoryId,
49004
+ value,
49005
+ signalId: signal.id,
49006
+ bucketName,
49007
+ description,
49008
+ backtest: isBacktest,
49009
+ });
47763
49010
  return;
47764
49011
  }
47765
- await Memory.writeMemory({
47766
- memoryId,
47767
- value,
47768
- signalId: signal.id,
47769
- bucketName,
47770
- description,
47771
- });
49012
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49013
+ await Memory.writeMemory({
49014
+ memoryId,
49015
+ value,
49016
+ signalId: signal.id,
49017
+ bucketName,
49018
+ description,
49019
+ backtest: isBacktest,
49020
+ });
49021
+ return;
49022
+ }
49023
+ throw new Error(`writeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47772
49024
  }
47773
49025
  /**
47774
49026
  * Reads a value from memory scoped to the current signal.
47775
49027
  *
47776
- * Reads symbol from execution context and signalId from the active pending signal.
47777
- * If no pending signal exists, logs a warning and returns null.
47778
- *
49028
+ * Resolves the active pending or scheduled signal automatically from execution context.
47779
49029
  * Automatically detects backtest/live mode from execution context.
47780
49030
  *
47781
49031
  * @param dto.bucketName - Memory bucket name
47782
49032
  * @param dto.memoryId - Unique memory entry identifier
47783
- * @returns Promise resolving to stored value or null if no signal
47784
- * @throws Error if entry not found within an active signal
47785
- *
47786
- * @deprecated Better use Memory.readMemory with manual signalId argument
49033
+ * @returns Promise resolving to stored value
49034
+ * @throws Error if no pending or scheduled signal exists, or if entry not found
47787
49035
  *
47788
49036
  * @example
47789
49037
  * ```typescript
@@ -47807,30 +49055,35 @@ async function readMemory(dto) {
47807
49055
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47808
49056
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47809
49057
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47810
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47811
- if (!signal) {
47812
- console.warn(`backtest-kit readMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
47813
- return null;
49058
+ let signal;
49059
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49060
+ return await Memory.readMemory({
49061
+ memoryId,
49062
+ signalId: signal.id,
49063
+ bucketName,
49064
+ backtest: isBacktest,
49065
+ });
47814
49066
  }
47815
- return await Memory.readMemory({
47816
- memoryId,
47817
- signalId: signal.id,
47818
- bucketName,
47819
- });
49067
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49068
+ return await Memory.readMemory({
49069
+ memoryId,
49070
+ signalId: signal.id,
49071
+ bucketName,
49072
+ backtest: isBacktest,
49073
+ });
49074
+ }
49075
+ throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47820
49076
  }
47821
49077
  /**
47822
49078
  * Searches memory entries for the current signal using BM25 full-text scoring.
47823
49079
  *
47824
- * Reads symbol from execution context and signalId from the active pending signal.
47825
- * If no pending signal exists, logs a warning and returns an empty array.
47826
- *
49080
+ * Resolves the active pending or scheduled signal automatically from execution context.
47827
49081
  * Automatically detects backtest/live mode from execution context.
47828
49082
  *
47829
49083
  * @param dto.bucketName - Memory bucket name
47830
49084
  * @param dto.query - Search query string
47831
- * @returns Promise resolving to matching entries sorted by relevance, or empty array if no signal
47832
- *
47833
- * @deprecated Better use Memory.searchMemory with manual signalId argument
49085
+ * @returns Promise resolving to matching entries sorted by relevance
49086
+ * @throws Error if no pending or scheduled signal exists
47834
49087
  *
47835
49088
  * @example
47836
49089
  * ```typescript
@@ -47854,29 +49107,34 @@ async function searchMemory(dto) {
47854
49107
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47855
49108
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47856
49109
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47857
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47858
- if (!signal) {
47859
- console.warn(`backtest-kit searchMemory no pending signal for symbol=${symbol} query=${query}`);
47860
- return [];
49110
+ let signal;
49111
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49112
+ return await Memory.searchMemory({
49113
+ query,
49114
+ signalId: signal.id,
49115
+ bucketName,
49116
+ backtest: isBacktest,
49117
+ });
47861
49118
  }
47862
- return await Memory.searchMemory({
47863
- query,
47864
- signalId: signal.id,
47865
- bucketName,
47866
- });
49119
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49120
+ return await Memory.searchMemory({
49121
+ query,
49122
+ signalId: signal.id,
49123
+ bucketName,
49124
+ backtest: isBacktest,
49125
+ });
49126
+ }
49127
+ throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
47867
49128
  }
47868
49129
  /**
47869
49130
  * Lists all memory entries for the current signal.
47870
49131
  *
47871
- * Reads symbol from execution context and signalId from the active pending signal.
47872
- * If no pending signal exists, logs a warning and returns an empty array.
47873
- *
49132
+ * Resolves the active pending or scheduled signal automatically from execution context.
47874
49133
  * Automatically detects backtest/live mode from execution context.
47875
49134
  *
47876
49135
  * @param dto.bucketName - Memory bucket name
47877
- * @returns Promise resolving to all stored entries, or empty array if no signal
47878
- *
47879
- * @deprecated Better use Memory.listMemory with manual signalId argument
49136
+ * @returns Promise resolving to all stored entries
49137
+ * @throws Error if no pending or scheduled signal exists
47880
49138
  *
47881
49139
  * @example
47882
49140
  * ```typescript
@@ -47899,29 +49157,33 @@ async function listMemory(dto) {
47899
49157
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47900
49158
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47901
49159
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47902
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47903
- if (!signal) {
47904
- console.warn(`backtest-kit listMemory no pending signal for symbol=${symbol}`);
47905
- return [];
49160
+ let signal;
49161
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49162
+ return await Memory.listMemory({
49163
+ signalId: signal.id,
49164
+ bucketName,
49165
+ backtest: isBacktest,
49166
+ });
47906
49167
  }
47907
- return await Memory.listMemory({
47908
- signalId: signal.id,
47909
- bucketName,
47910
- });
49168
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49169
+ return await Memory.listMemory({
49170
+ signalId: signal.id,
49171
+ bucketName,
49172
+ backtest: isBacktest,
49173
+ });
49174
+ }
49175
+ throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47911
49176
  }
47912
49177
  /**
47913
49178
  * Removes a memory entry for the current signal.
47914
49179
  *
47915
- * Reads symbol from execution context and signalId from the active pending signal.
47916
- * If no pending signal exists, logs a warning and returns without removing.
47917
- *
49180
+ * Resolves the active pending or scheduled signal automatically from execution context.
47918
49181
  * Automatically detects backtest/live mode from execution context.
47919
49182
  *
47920
49183
  * @param dto.bucketName - Memory bucket name
47921
49184
  * @param dto.memoryId - Unique memory entry identifier
47922
49185
  * @returns Promise that resolves when removal is complete
47923
- *
47924
- * @deprecated Better use Memory.removeMemory with manual signalId argument
49186
+ * @throws Error if no pending or scheduled signal exists
47925
49187
  *
47926
49188
  * @example
47927
49189
  * ```typescript
@@ -47945,16 +49207,26 @@ async function removeMemory(dto) {
47945
49207
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47946
49208
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47947
49209
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47948
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47949
- if (!signal) {
47950
- console.warn(`backtest-kit removeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49210
+ let signal;
49211
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49212
+ await Memory.removeMemory({
49213
+ memoryId,
49214
+ signalId: signal.id,
49215
+ bucketName,
49216
+ backtest: isBacktest,
49217
+ });
47951
49218
  return;
47952
49219
  }
47953
- await Memory.removeMemory({
47954
- memoryId,
47955
- signalId: signal.id,
47956
- bucketName,
47957
- });
49220
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49221
+ await Memory.removeMemory({
49222
+ memoryId,
49223
+ signalId: signal.id,
49224
+ bucketName,
49225
+ backtest: isBacktest,
49226
+ });
49227
+ return;
49228
+ }
49229
+ throw new Error(`removeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47958
49230
  }
47959
49231
 
47960
49232
  const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
@@ -48049,11 +49321,12 @@ const RENDER_TABLE_FN = (rows) => {
48049
49321
  * Scoped to (signalId, bucketName) via constructor.
48050
49322
  */
48051
49323
  class DumpBothInstance {
48052
- constructor(signalId, bucketName) {
49324
+ constructor(signalId, bucketName, backtest) {
48053
49325
  this.signalId = signalId;
48054
49326
  this.bucketName = bucketName;
48055
- this._memory = new DumpMemoryInstance(signalId, bucketName);
48056
- this._markdown = new DumpMarkdownInstance(signalId, bucketName);
49327
+ this.backtest = backtest;
49328
+ this._memory = new DumpMemoryInstance(signalId, bucketName, backtest);
49329
+ this._markdown = new DumpMarkdownInstance(signalId, bucketName, backtest);
48057
49330
  }
48058
49331
  /** Releases resources held by both backends. */
48059
49332
  dispose() {
@@ -48185,9 +49458,10 @@ class DumpBothInstance {
48185
49458
  * Useful for downstream LLM retrieval via Memory.searchMemory.
48186
49459
  */
48187
49460
  class DumpMemoryInstance {
48188
- constructor(signalId, bucketName) {
49461
+ constructor(signalId, bucketName, backtest) {
48189
49462
  this.signalId = signalId;
48190
49463
  this.bucketName = bucketName;
49464
+ this.backtest = backtest;
48191
49465
  }
48192
49466
  /**
48193
49467
  * Stores the full agent message history in Memory as a `{ messages }` object.
@@ -48213,6 +49487,7 @@ class DumpMemoryInstance {
48213
49487
  signalId: this.signalId,
48214
49488
  value: { messages },
48215
49489
  description,
49490
+ backtest: this.backtest,
48216
49491
  });
48217
49492
  }
48218
49493
  /**
@@ -48234,6 +49509,7 @@ class DumpMemoryInstance {
48234
49509
  signalId: this.signalId,
48235
49510
  value: record,
48236
49511
  description,
49512
+ backtest: this.backtest,
48237
49513
  });
48238
49514
  }
48239
49515
  /**
@@ -48256,6 +49532,7 @@ class DumpMemoryInstance {
48256
49532
  signalId: this.signalId,
48257
49533
  value: { rows },
48258
49534
  description,
49535
+ backtest: this.backtest,
48259
49536
  });
48260
49537
  }
48261
49538
  /**
@@ -48277,6 +49554,7 @@ class DumpMemoryInstance {
48277
49554
  signalId: this.signalId,
48278
49555
  value: { content },
48279
49556
  description,
49557
+ backtest: this.backtest,
48280
49558
  });
48281
49559
  }
48282
49560
  /**
@@ -48298,6 +49576,7 @@ class DumpMemoryInstance {
48298
49576
  signalId: this.signalId,
48299
49577
  value: { content },
48300
49578
  description,
49579
+ backtest: this.backtest,
48301
49580
  });
48302
49581
  }
48303
49582
  /**
@@ -48320,6 +49599,7 @@ class DumpMemoryInstance {
48320
49599
  signalId: this.signalId,
48321
49600
  value: json,
48322
49601
  description,
49602
+ backtest: this.backtest,
48323
49603
  });
48324
49604
  }
48325
49605
  /** Releases resources held by this instance. */
@@ -48341,9 +49621,10 @@ class DumpMemoryInstance {
48341
49621
  * If the file already exists, the call is skipped (idempotent).
48342
49622
  */
48343
49623
  class DumpMarkdownInstance {
48344
- constructor(signalId, bucketName) {
49624
+ constructor(signalId, bucketName, backtest) {
48345
49625
  this.signalId = signalId;
48346
49626
  this.bucketName = bucketName;
49627
+ this.backtest = backtest;
48347
49628
  }
48348
49629
  getFilePath(dumpId) {
48349
49630
  return join("./dump/agent", this.signalId, this.bucketName, `${dumpId}.md`);
@@ -48532,9 +49813,10 @@ class DumpMarkdownInstance {
48532
49813
  * Used for disabling dumps in tests or dry-run scenarios.
48533
49814
  */
48534
49815
  class DumpDummyInstance {
48535
- constructor(signalId, bucketName) {
49816
+ constructor(signalId, bucketName, backtest) {
48536
49817
  this.signalId = signalId;
48537
49818
  this.bucketName = bucketName;
49819
+ this.backtest = backtest;
48538
49820
  }
48539
49821
  /** No-op. */
48540
49822
  async dumpAgentAnswer() {
@@ -48577,7 +49859,7 @@ class DumpDummyInstance {
48577
49859
  class DumpAdapter {
48578
49860
  constructor() {
48579
49861
  this.DumpFactory = DumpMarkdownInstance;
48580
- this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
49862
+ this.getInstance = memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName, backtest) => Reflect.construct(this.DumpFactory, [signalId, bucketName, backtest]));
48581
49863
  /**
48582
49864
  * Activates the adapter by subscribing to signal lifecycle events.
48583
49865
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -48628,7 +49910,7 @@ class DumpAdapter {
48628
49910
  bucketName: context.bucketName,
48629
49911
  dumpId: context.dumpId,
48630
49912
  });
48631
- const instance = this.getInstance(context.signalId, context.bucketName);
49913
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48632
49914
  return await instance.dumpAgentAnswer(messages, context.dumpId, context.description);
48633
49915
  };
48634
49916
  /**
@@ -48643,7 +49925,7 @@ class DumpAdapter {
48643
49925
  bucketName: context.bucketName,
48644
49926
  dumpId: context.dumpId,
48645
49927
  });
48646
- const instance = this.getInstance(context.signalId, context.bucketName);
49928
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48647
49929
  return await instance.dumpRecord(record, context.dumpId, context.description);
48648
49930
  };
48649
49931
  /**
@@ -48658,7 +49940,7 @@ class DumpAdapter {
48658
49940
  bucketName: context.bucketName,
48659
49941
  dumpId: context.dumpId,
48660
49942
  });
48661
- const instance = this.getInstance(context.signalId, context.bucketName);
49943
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48662
49944
  return await instance.dumpTable(rows, context.dumpId, context.description);
48663
49945
  };
48664
49946
  /**
@@ -48673,7 +49955,7 @@ class DumpAdapter {
48673
49955
  bucketName: context.bucketName,
48674
49956
  dumpId: context.dumpId,
48675
49957
  });
48676
- const instance = this.getInstance(context.signalId, context.bucketName);
49958
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48677
49959
  return await instance.dumpText(content, context.dumpId, context.description);
48678
49960
  };
48679
49961
  /**
@@ -48688,7 +49970,7 @@ class DumpAdapter {
48688
49970
  bucketName: context.bucketName,
48689
49971
  dumpId: context.dumpId,
48690
49972
  });
48691
- const instance = this.getInstance(context.signalId, context.bucketName);
49973
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48692
49974
  return await instance.dumpError(content, context.dumpId, context.description);
48693
49975
  };
48694
49976
  /**
@@ -48704,7 +49986,7 @@ class DumpAdapter {
48704
49986
  bucketName: context.bucketName,
48705
49987
  dumpId: context.dumpId,
48706
49988
  });
48707
- const instance = this.getInstance(context.signalId, context.bucketName);
49989
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48708
49990
  return await instance.dumpJson(json, context.dumpId, context.description);
48709
49991
  };
48710
49992
  /**
@@ -48770,16 +50052,15 @@ const DUMP_JSON_METHOD_NAME = "dump.dumpJson";
48770
50052
  /**
48771
50053
  * Dumps the full agent message history scoped to the current signal.
48772
50054
  *
48773
- * Reads signalId from the active pending signal via execution and method context.
48774
- * If no pending signal exists, logs a warning and returns without writing.
50055
+ * Resolves the active pending or scheduled signal automatically from execution context.
50056
+ * Automatically detects backtest/live mode from execution context.
48775
50057
  *
48776
50058
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48777
50059
  * @param dto.dumpId - Unique identifier for this agent invocation
48778
50060
  * @param dto.messages - Full chat history (system, user, assistant, tool)
48779
50061
  * @param dto.description - Human-readable label describing the agent invocation context; included in the BM25 index for Memory search
48780
50062
  * @returns Promise that resolves when the dump is complete
48781
- *
48782
- * @deprecated Better use Dump.dumpAgentAnswer with manual signalId argument
50063
+ * @throws Error if no pending or scheduled signal exists
48783
50064
  *
48784
50065
  * @example
48785
50066
  * ```typescript
@@ -48804,31 +50085,41 @@ async function dumpAgentAnswer(dto) {
48804
50085
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48805
50086
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48806
50087
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48807
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48808
- if (!signal) {
48809
- console.warn(`backtest-kit dumpAgentAnswer no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50088
+ let signal;
50089
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50090
+ await Dump.dumpAgentAnswer(messages, {
50091
+ dumpId,
50092
+ bucketName,
50093
+ signalId: signal.id,
50094
+ description,
50095
+ backtest: isBacktest,
50096
+ });
48810
50097
  return;
48811
50098
  }
48812
- await Dump.dumpAgentAnswer(messages, {
48813
- dumpId,
48814
- bucketName,
48815
- signalId: signal.id,
48816
- description,
48817
- });
50099
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50100
+ await Dump.dumpAgentAnswer(messages, {
50101
+ dumpId,
50102
+ bucketName,
50103
+ signalId: signal.id,
50104
+ description,
50105
+ backtest: isBacktest,
50106
+ });
50107
+ return;
50108
+ }
50109
+ throw new Error(`dumpAgentAnswer requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48818
50110
  }
48819
50111
  /**
48820
50112
  * Dumps a flat key-value record scoped to the current signal.
48821
50113
  *
48822
- * Reads signalId from the active pending signal via execution and method context.
48823
- * If no pending signal exists, logs a warning and returns without writing.
50114
+ * Resolves the active pending or scheduled signal automatically from execution context.
50115
+ * Automatically detects backtest/live mode from execution context.
48824
50116
  *
48825
50117
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48826
50118
  * @param dto.dumpId - Unique identifier for this dump entry
48827
50119
  * @param dto.record - Arbitrary flat object to persist
48828
50120
  * @param dto.description - Human-readable label describing the record contents; included in the BM25 index for Memory search
48829
50121
  * @returns Promise that resolves when the dump is complete
48830
- *
48831
- * @deprecated Better use Dump.dumpRecord with manual signalId argument
50122
+ * @throws Error if no pending or scheduled signal exists
48832
50123
  *
48833
50124
  * @example
48834
50125
  * ```typescript
@@ -48852,23 +50143,34 @@ async function dumpRecord(dto) {
48852
50143
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48853
50144
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48854
50145
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48855
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48856
- if (!signal) {
48857
- console.warn(`backtest-kit dumpRecord no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50146
+ let signal;
50147
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50148
+ await Dump.dumpRecord(record, {
50149
+ dumpId,
50150
+ bucketName,
50151
+ signalId: signal.id,
50152
+ description,
50153
+ backtest: isBacktest,
50154
+ });
48858
50155
  return;
48859
50156
  }
48860
- await Dump.dumpRecord(record, {
48861
- dumpId,
48862
- bucketName,
48863
- signalId: signal.id,
48864
- description,
48865
- });
50157
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50158
+ await Dump.dumpRecord(record, {
50159
+ dumpId,
50160
+ bucketName,
50161
+ signalId: signal.id,
50162
+ description,
50163
+ backtest: isBacktest,
50164
+ });
50165
+ return;
50166
+ }
50167
+ throw new Error(`dumpRecord requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48866
50168
  }
48867
50169
  /**
48868
50170
  * Dumps an array of objects as a table scoped to the current signal.
48869
50171
  *
48870
- * Reads signalId from the active pending signal via execution and method context.
48871
- * If no pending signal exists, logs a warning and returns without writing.
50172
+ * Resolves the active pending or scheduled signal automatically from execution context.
50173
+ * Automatically detects backtest/live mode from execution context.
48872
50174
  *
48873
50175
  * Column headers are derived from the union of all keys across all rows.
48874
50176
  *
@@ -48877,8 +50179,7 @@ async function dumpRecord(dto) {
48877
50179
  * @param dto.rows - Array of arbitrary objects to render as a table
48878
50180
  * @param dto.description - Human-readable label describing the table contents; included in the BM25 index for Memory search
48879
50181
  * @returns Promise that resolves when the dump is complete
48880
- *
48881
- * @deprecated Better use Dump.dumpTable with manual signalId argument
50182
+ * @throws Error if no pending or scheduled signal exists
48882
50183
  *
48883
50184
  * @example
48884
50185
  * ```typescript
@@ -48903,31 +50204,41 @@ async function dumpTable(dto) {
48903
50204
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48904
50205
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48905
50206
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48906
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48907
- if (!signal) {
48908
- console.warn(`backtest-kit dumpTable no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50207
+ let signal;
50208
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50209
+ await Dump.dumpTable(rows, {
50210
+ dumpId,
50211
+ bucketName,
50212
+ signalId: signal.id,
50213
+ description,
50214
+ backtest: isBacktest,
50215
+ });
48909
50216
  return;
48910
50217
  }
48911
- await Dump.dumpTable(rows, {
48912
- dumpId,
48913
- bucketName,
48914
- signalId: signal.id,
48915
- description,
48916
- });
50218
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50219
+ await Dump.dumpTable(rows, {
50220
+ dumpId,
50221
+ bucketName,
50222
+ signalId: signal.id,
50223
+ description,
50224
+ backtest: isBacktest,
50225
+ });
50226
+ return;
50227
+ }
50228
+ throw new Error(`dumpTable requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48917
50229
  }
48918
50230
  /**
48919
50231
  * Dumps raw text content scoped to the current signal.
48920
50232
  *
48921
- * Reads signalId from the active pending signal via execution and method context.
48922
- * If no pending signal exists, logs a warning and returns without writing.
50233
+ * Resolves the active pending or scheduled signal automatically from execution context.
50234
+ * Automatically detects backtest/live mode from execution context.
48923
50235
  *
48924
50236
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48925
50237
  * @param dto.dumpId - Unique identifier for this dump entry
48926
50238
  * @param dto.content - Arbitrary text content to persist
48927
50239
  * @param dto.description - Human-readable label describing the content; included in the BM25 index for Memory search
48928
50240
  * @returns Promise that resolves when the dump is complete
48929
- *
48930
- * @deprecated Better use Dump.dumpText with manual signalId argument
50241
+ * @throws Error if no pending or scheduled signal exists
48931
50242
  *
48932
50243
  * @example
48933
50244
  * ```typescript
@@ -48951,31 +50262,41 @@ async function dumpText(dto) {
48951
50262
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48952
50263
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48953
50264
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48954
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48955
- if (!signal) {
48956
- console.warn(`backtest-kit dumpText no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50265
+ let signal;
50266
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50267
+ await Dump.dumpText(content, {
50268
+ dumpId,
50269
+ bucketName,
50270
+ signalId: signal.id,
50271
+ description,
50272
+ backtest: isBacktest,
50273
+ });
48957
50274
  return;
48958
50275
  }
48959
- await Dump.dumpText(content, {
48960
- dumpId,
48961
- bucketName,
48962
- signalId: signal.id,
48963
- description,
48964
- });
50276
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50277
+ await Dump.dumpText(content, {
50278
+ dumpId,
50279
+ bucketName,
50280
+ signalId: signal.id,
50281
+ description,
50282
+ backtest: isBacktest,
50283
+ });
50284
+ return;
50285
+ }
50286
+ throw new Error(`dumpText requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48965
50287
  }
48966
50288
  /**
48967
50289
  * Dumps an error description scoped to the current signal.
48968
50290
  *
48969
- * Reads signalId from the active pending signal via execution and method context.
48970
- * If no pending signal exists, logs a warning and returns without writing.
50291
+ * Resolves the active pending or scheduled signal automatically from execution context.
50292
+ * Automatically detects backtest/live mode from execution context.
48971
50293
  *
48972
50294
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48973
50295
  * @param dto.dumpId - Unique identifier for this dump entry
48974
50296
  * @param dto.content - Error message or description to persist
48975
50297
  * @param dto.description - Human-readable label describing the error context; included in the BM25 index for Memory search
48976
50298
  * @returns Promise that resolves when the dump is complete
48977
- *
48978
- * @deprecated Better use Dump.dumpError with manual signalId argument
50299
+ * @throws Error if no pending or scheduled signal exists
48979
50300
  *
48980
50301
  * @example
48981
50302
  * ```typescript
@@ -48999,29 +50320,41 @@ async function dumpError(dto) {
48999
50320
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49000
50321
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49001
50322
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49002
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49003
- if (!signal) {
49004
- console.warn(`backtest-kit dumpError no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50323
+ let signal;
50324
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50325
+ await Dump.dumpError(content, {
50326
+ dumpId,
50327
+ bucketName,
50328
+ signalId: signal.id,
50329
+ description,
50330
+ backtest: isBacktest,
50331
+ });
49005
50332
  return;
49006
50333
  }
49007
- await Dump.dumpError(content, {
49008
- dumpId,
49009
- bucketName,
49010
- signalId: signal.id,
49011
- description,
49012
- });
50334
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50335
+ await Dump.dumpError(content, {
50336
+ dumpId,
50337
+ bucketName,
50338
+ signalId: signal.id,
50339
+ description,
50340
+ backtest: isBacktest,
50341
+ });
50342
+ return;
50343
+ }
50344
+ throw new Error(`dumpError requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49013
50345
  }
49014
50346
  /**
49015
50347
  * Dumps an arbitrary nested object as a fenced JSON block scoped to the current signal.
49016
50348
  *
49017
- * Reads signalId from the active pending signal via execution and method context.
49018
- * If no pending signal exists, logs a warning and returns without writing.
50349
+ * Resolves the active pending or scheduled signal automatically from execution context.
50350
+ * Automatically detects backtest/live mode from execution context.
49019
50351
  *
49020
50352
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
49021
50353
  * @param dto.dumpId - Unique identifier for this dump entry
49022
50354
  * @param dto.json - Arbitrary nested object to serialize with JSON.stringify
49023
50355
  * @param dto.description - Human-readable label describing the object contents; included in the BM25 index for Memory search
49024
50356
  * @returns Promise that resolves when the dump is complete
50357
+ * @throws Error if no pending or scheduled signal exists
49025
50358
  *
49026
50359
  * @deprecated Prefer dumpRecord — flat key-value structure maps naturally to markdown tables and SQL storage
49027
50360
  *
@@ -49047,17 +50380,28 @@ async function dumpJson(dto) {
49047
50380
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49048
50381
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49049
50382
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49050
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49051
- if (!signal) {
49052
- console.warn(`backtest-kit dumpJson no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50383
+ let signal;
50384
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50385
+ await Dump.dumpJson(json, {
50386
+ dumpId,
50387
+ bucketName,
50388
+ signalId: signal.id,
50389
+ description,
50390
+ backtest: isBacktest,
50391
+ });
49053
50392
  return;
49054
50393
  }
49055
- await Dump.dumpJson(json, {
49056
- dumpId,
49057
- bucketName,
49058
- signalId: signal.id,
49059
- description,
49060
- });
50394
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50395
+ await Dump.dumpJson(json, {
50396
+ dumpId,
50397
+ bucketName,
50398
+ signalId: signal.id,
50399
+ description,
50400
+ backtest: isBacktest,
50401
+ });
50402
+ return;
50403
+ }
50404
+ throw new Error(`dumpJson requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49061
50405
  }
49062
50406
 
49063
50407
  /**
@@ -59524,4 +60868,4 @@ const validateSignal = (signal, currentPrice) => {
59524
60868
  return !errors.length;
59525
60869
  };
59526
60870
 
59527
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
60871
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStateAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };