backtest-kit 7.4.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -1017,6 +1017,22 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
1017
1017
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
1018
1018
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
1019
1019
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1020
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER = "PersistStateUtils.usePersistStateAdapter";
1021
+ const PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA = "PersistStateUtils.readStateData";
1022
+ const PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA = "PersistStateUtils.writeStateData";
1023
+ const PERSIST_STATE_UTILS_METHOD_NAME_CLEAR = "PersistStateUtils.clear";
1024
+ const PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE = "PersistStateUtils.dispose";
1025
+ const PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT = "PersistStateUtils.waitForInit";
1026
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY = "PersistStateUtils.useDummy";
1027
+ const PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON = "PersistStateUtils.useJson";
1028
+ const PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER = "PersistSessionUtils.usePersistSessionAdapter";
1029
+ const PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA = "PersistSessionUtils.readSessionData";
1030
+ const PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA = "PersistSessionUtils.writeSessionData";
1031
+ const PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR = "PersistSessionUtils.clear";
1032
+ const PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE = "PersistSessionUtils.dispose";
1033
+ const PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT = "PersistSessionUtils.waitForInit";
1034
+ const PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY = "PersistSessionUtils.useDummy";
1035
+ const PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON = "PersistSessionUtils.useJson";
1020
1036
  const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1021
1037
  const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1022
1038
  const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
@@ -2978,6 +2994,271 @@ class PersistRecentUtils {
2978
2994
  * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2979
2995
  */
2980
2996
  const PersistRecentAdapter = new PersistRecentUtils();
2997
+ /**
2998
+ * Utility class for managing state persistence.
2999
+ *
3000
+ * Features:
3001
+ * - Memoized storage instances per (signalId, bucketName) pair
3002
+ * - Custom adapter support
3003
+ * - Atomic read/write operations
3004
+ *
3005
+ * Storage layout: ./dump/state/<signalId>/<bucketName>.json
3006
+ *
3007
+ * Used by StatePersistInstance for crash-safe state persistence.
3008
+ */
3009
+ class PersistStateUtils {
3010
+ constructor() {
3011
+ this.PersistStateFactory = PersistBase;
3012
+ this.getStateStorage = functoolsKit.memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistStateFactory, [
3013
+ bucketName,
3014
+ `./dump/state/${signalId}/`,
3015
+ ]));
3016
+ /**
3017
+ * Initializes the storage for a given (signalId, bucketName) pair.
3018
+ *
3019
+ * @param signalId - Signal identifier
3020
+ * @param bucketName - Bucket name
3021
+ * @param initial - Whether this is the first initialization
3022
+ */
3023
+ this.waitForInit = async (signalId, bucketName, initial) => {
3024
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
3025
+ signalId,
3026
+ bucketName,
3027
+ initial,
3028
+ });
3029
+ const key = `${signalId}:${bucketName}`;
3030
+ const isInitial = initial && !this.getStateStorage.has(key);
3031
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3032
+ await stateStorage.waitForInit(isInitial);
3033
+ };
3034
+ /**
3035
+ * Reads a state entry from persistence storage.
3036
+ *
3037
+ * @param signalId - Signal identifier
3038
+ * @param bucketName - Bucket name
3039
+ * @returns Promise resolving to entry data or null if not found
3040
+ */
3041
+ this.readStateData = async (signalId, bucketName) => {
3042
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, {
3043
+ signalId,
3044
+ bucketName,
3045
+ });
3046
+ const key = `${signalId}:${bucketName}`;
3047
+ const isInitial = !this.getStateStorage.has(key);
3048
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3049
+ await stateStorage.waitForInit(isInitial);
3050
+ if (await stateStorage.hasValue(bucketName)) {
3051
+ return await stateStorage.readValue(bucketName);
3052
+ }
3053
+ return null;
3054
+ };
3055
+ /**
3056
+ * Writes a state entry to disk with atomic file writes.
3057
+ *
3058
+ * @param data - Entry data to persist
3059
+ * @param signalId - Signal identifier
3060
+ * @param bucketName - Bucket name
3061
+ */
3062
+ this.writeStateData = async (data, signalId, bucketName) => {
3063
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, {
3064
+ signalId,
3065
+ bucketName,
3066
+ });
3067
+ const key = `${signalId}:${bucketName}`;
3068
+ const isInitial = !this.getStateStorage.has(key);
3069
+ const stateStorage = this.getStateStorage(signalId, bucketName);
3070
+ await stateStorage.waitForInit(isInitial);
3071
+ await stateStorage.writeValue(bucketName, data);
3072
+ };
3073
+ /**
3074
+ * Switches to a dummy persist adapter that discards all writes.
3075
+ * All future persistence writes will be no-ops.
3076
+ */
3077
+ this.useDummy = () => {
3078
+ LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
3079
+ this.usePersistStateAdapter(PersistDummy);
3080
+ };
3081
+ /**
3082
+ * Switches to the default JSON persist adapter.
3083
+ * All future persistence writes will use JSON storage.
3084
+ */
3085
+ this.useJson = () => {
3086
+ LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
3087
+ this.usePersistStateAdapter(PersistBase);
3088
+ };
3089
+ /**
3090
+ * Clears the memoized storage cache.
3091
+ * Call this when process.cwd() changes between strategy iterations
3092
+ * so new storage instances are created with the updated base path.
3093
+ */
3094
+ this.clear = () => {
3095
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
3096
+ this.getStateStorage.clear();
3097
+ };
3098
+ /**
3099
+ * Disposes of the state adapter and releases any resources.
3100
+ * Call this when a signal is removed to clean up its associated storage.
3101
+ *
3102
+ * @param signalId - Signal identifier
3103
+ * @param bucketName - Bucket name
3104
+ */
3105
+ this.dispose = (signalId, bucketName) => {
3106
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_DISPOSE);
3107
+ const key = `${signalId}:${bucketName}`;
3108
+ this.getStateStorage.clear(key);
3109
+ };
3110
+ }
3111
+ /**
3112
+ * Registers a custom persistence adapter.
3113
+ *
3114
+ * @param Ctor - Custom PersistBase constructor
3115
+ */
3116
+ usePersistStateAdapter(Ctor) {
3117
+ LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
3118
+ this.PersistStateFactory = Ctor;
3119
+ }
3120
+ }
3121
+ /**
3122
+ * Global singleton instance of PersistStateUtils.
3123
+ * Used by StatePersistInstance for crash-safe state persistence.
3124
+ */
3125
+ const PersistStateAdapter = new PersistStateUtils();
3126
+ /**
3127
+ * Utility class for managing session persistence.
3128
+ *
3129
+ * Features:
3130
+ * - Memoized storage instances per (strategyName, exchangeName, frameName) key
3131
+ * - Custom adapter support
3132
+ * - Atomic read/write operations
3133
+ *
3134
+ * Storage layout: ./dump/session/<strategyName>/<exchangeName>/<frameName>.json
3135
+ *
3136
+ * Used by SessionPersistInstance for crash-safe session persistence.
3137
+ */
3138
+ class PersistSessionUtils {
3139
+ constructor() {
3140
+ this.PersistSessionFactory = PersistBase;
3141
+ this.getSessionStorage = functoolsKit.memoize(([strategyName, exchangeName, frameName]) => `${strategyName}:${exchangeName}:${frameName}`, (strategyName, exchangeName, frameName) => Reflect.construct(this.PersistSessionFactory, [
3142
+ frameName,
3143
+ `./dump/session/${strategyName}/${exchangeName}/`,
3144
+ ]));
3145
+ /**
3146
+ * Initializes the storage for a given (strategyName, exchangeName, frameName) triple.
3147
+ *
3148
+ * @param strategyName - Strategy identifier
3149
+ * @param exchangeName - Exchange identifier
3150
+ * @param frameName - Frame identifier
3151
+ * @param initial - Whether this is the first initialization
3152
+ */
3153
+ this.waitForInit = async (strategyName, exchangeName, frameName, initial) => {
3154
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
3155
+ strategyName,
3156
+ exchangeName,
3157
+ frameName,
3158
+ initial,
3159
+ });
3160
+ const key = `${strategyName}:${exchangeName}:${frameName}`;
3161
+ const isInitial = initial && !this.getSessionStorage.has(key);
3162
+ const sessionStorage = this.getSessionStorage(strategyName, exchangeName, frameName);
3163
+ await sessionStorage.waitForInit(isInitial);
3164
+ };
3165
+ /**
3166
+ * Reads a session entry from persistence storage.
3167
+ *
3168
+ * @param strategyName - Strategy identifier
3169
+ * @param exchangeName - Exchange identifier
3170
+ * @param frameName - Frame identifier
3171
+ * @returns Promise resolving to entry data or null if not found
3172
+ */
3173
+ this.readSessionData = async (strategyName, exchangeName, frameName) => {
3174
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, {
3175
+ strategyName,
3176
+ exchangeName,
3177
+ frameName,
3178
+ });
3179
+ const key = `${strategyName}:${exchangeName}:${frameName}`;
3180
+ const isInitial = !this.getSessionStorage.has(key);
3181
+ const sessionStorage = this.getSessionStorage(strategyName, exchangeName, frameName);
3182
+ await sessionStorage.waitForInit(isInitial);
3183
+ if (await sessionStorage.hasValue(frameName)) {
3184
+ return await sessionStorage.readValue(frameName);
3185
+ }
3186
+ return null;
3187
+ };
3188
+ /**
3189
+ * Writes a session entry to disk with atomic file writes.
3190
+ *
3191
+ * @param data - Entry data to persist
3192
+ * @param strategyName - Strategy identifier
3193
+ * @param exchangeName - Exchange identifier
3194
+ * @param frameName - Frame identifier
3195
+ */
3196
+ this.writeSessionData = async (data, strategyName, exchangeName, frameName) => {
3197
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, {
3198
+ strategyName,
3199
+ exchangeName,
3200
+ frameName,
3201
+ });
3202
+ const key = `${strategyName}:${exchangeName}:${frameName}`;
3203
+ const isInitial = !this.getSessionStorage.has(key);
3204
+ const sessionStorage = this.getSessionStorage(strategyName, exchangeName, frameName);
3205
+ await sessionStorage.waitForInit(isInitial);
3206
+ await sessionStorage.writeValue(frameName, data);
3207
+ };
3208
+ /**
3209
+ * Switches to a dummy persist adapter that discards all writes.
3210
+ * All future persistence writes will be no-ops.
3211
+ */
3212
+ this.useDummy = () => {
3213
+ LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
3214
+ this.usePersistSessionAdapter(PersistDummy);
3215
+ };
3216
+ /**
3217
+ * Switches to the default JSON persist adapter.
3218
+ * All future persistence writes will use JSON storage.
3219
+ */
3220
+ this.useJson = () => {
3221
+ LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
3222
+ this.usePersistSessionAdapter(PersistBase);
3223
+ };
3224
+ /**
3225
+ * Clears the memoized storage cache.
3226
+ * Call this when process.cwd() changes between strategy iterations
3227
+ * so new storage instances are created with the updated base path.
3228
+ */
3229
+ this.clear = () => {
3230
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
3231
+ this.getSessionStorage.clear();
3232
+ };
3233
+ /**
3234
+ * Disposes of the session adapter and releases any resources.
3235
+ * Call this when a session is removed to clean up its associated storage.
3236
+ *
3237
+ * @param strategyName - Strategy identifier
3238
+ * @param exchangeName - Exchange identifier
3239
+ * @param frameName - Frame identifier
3240
+ */
3241
+ this.dispose = (strategyName, exchangeName, frameName) => {
3242
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_DISPOSE);
3243
+ const key = `${strategyName}:${exchangeName}:${frameName}`;
3244
+ this.getSessionStorage.clear(key);
3245
+ };
3246
+ }
3247
+ /**
3248
+ * Registers a custom persistence adapter.
3249
+ *
3250
+ * @param Ctor - Custom PersistBase constructor
3251
+ */
3252
+ usePersistSessionAdapter(Ctor) {
3253
+ LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
3254
+ this.PersistSessionFactory = Ctor;
3255
+ }
3256
+ }
3257
+ /**
3258
+ * Global singleton instance of PersistSessionUtils.
3259
+ * Used by SessionPersistInstance for crash-safe session persistence.
3260
+ */
3261
+ const PersistSessionAdapter = new PersistSessionUtils();
2981
3262
 
2982
3263
  var _a$2, _b$2;
2983
3264
  const BUSY_DELAY = 100;
@@ -10443,7 +10724,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10443
10724
  * @param backtest - Whether running in backtest mode
10444
10725
  * @returns Unique string key for memoization
10445
10726
  */
10446
- const CREATE_KEY_FN$v = (symbol, strategyName, exchangeName, frameName, backtest) => {
10727
+ const CREATE_KEY_FN$x = (symbol, strategyName, exchangeName, frameName, backtest) => {
10447
10728
  const parts = [symbol, strategyName, exchangeName];
10448
10729
  if (frameName)
10449
10730
  parts.push(frameName);
@@ -10743,7 +11024,7 @@ class StrategyConnectionService {
10743
11024
  * @param backtest - Whether running in backtest mode
10744
11025
  * @returns Configured ClientStrategy instance
10745
11026
  */
10746
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
11027
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$x(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10747
11028
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10748
11029
  return new ClientStrategy({
10749
11030
  symbol,
@@ -11705,7 +11986,7 @@ class StrategyConnectionService {
11705
11986
  }
11706
11987
  return;
11707
11988
  }
11708
- const key = CREATE_KEY_FN$v(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11989
+ const key = CREATE_KEY_FN$x(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11709
11990
  if (!this.getStrategy.has(key)) {
11710
11991
  return;
11711
11992
  }
@@ -12879,7 +13160,7 @@ class ClientRisk {
12879
13160
  * @param backtest - Whether running in backtest mode
12880
13161
  * @returns Unique string key for memoization
12881
13162
  */
12882
- const CREATE_KEY_FN$u = (riskName, exchangeName, frameName, backtest) => {
13163
+ const CREATE_KEY_FN$w = (riskName, exchangeName, frameName, backtest) => {
12883
13164
  const parts = [riskName, exchangeName];
12884
13165
  if (frameName)
12885
13166
  parts.push(frameName);
@@ -12979,7 +13260,7 @@ class RiskConnectionService {
12979
13260
  * @param backtest - True if backtest mode, false if live mode
12980
13261
  * @returns Configured ClientRisk instance
12981
13262
  */
12982
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
13263
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$w(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12983
13264
  const schema = this.riskSchemaService.get(riskName);
12984
13265
  return new ClientRisk({
12985
13266
  ...schema,
@@ -13048,7 +13329,7 @@ class RiskConnectionService {
13048
13329
  payload,
13049
13330
  });
13050
13331
  if (payload) {
13051
- const key = CREATE_KEY_FN$u(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13332
+ const key = CREATE_KEY_FN$w(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
13052
13333
  this.getRisk.clear(key);
13053
13334
  }
13054
13335
  else {
@@ -14167,7 +14448,7 @@ class ClientAction {
14167
14448
  * @param backtest - Whether running in backtest mode
14168
14449
  * @returns Unique string key for memoization
14169
14450
  */
14170
- const CREATE_KEY_FN$t = (actionName, strategyName, exchangeName, frameName, backtest) => {
14451
+ const CREATE_KEY_FN$v = (actionName, strategyName, exchangeName, frameName, backtest) => {
14171
14452
  const parts = [actionName, strategyName, exchangeName];
14172
14453
  if (frameName)
14173
14454
  parts.push(frameName);
@@ -14219,7 +14500,7 @@ class ActionConnectionService {
14219
14500
  * @param backtest - True if backtest mode, false if live mode
14220
14501
  * @returns Configured ClientAction instance
14221
14502
  */
14222
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14503
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$v(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
14223
14504
  const schema = this.actionSchemaService.get(actionName);
14224
14505
  return new ClientAction({
14225
14506
  ...schema,
@@ -14445,7 +14726,7 @@ class ActionConnectionService {
14445
14726
  await Promise.all(actions.map(async (action) => await action.dispose()));
14446
14727
  return;
14447
14728
  }
14448
- const key = CREATE_KEY_FN$t(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14729
+ const key = CREATE_KEY_FN$v(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14449
14730
  if (!this.getAction.has(key)) {
14450
14731
  return;
14451
14732
  }
@@ -14463,7 +14744,7 @@ const METHOD_NAME_VALIDATE$3 = "exchangeCoreService validate";
14463
14744
  * @param exchangeName - Exchange name
14464
14745
  * @returns Unique string key for memoization
14465
14746
  */
14466
- const CREATE_KEY_FN$s = (exchangeName) => {
14747
+ const CREATE_KEY_FN$u = (exchangeName) => {
14467
14748
  return exchangeName;
14468
14749
  };
14469
14750
  /**
@@ -14487,7 +14768,7 @@ class ExchangeCoreService {
14487
14768
  * @param exchangeName - Name of the exchange to validate
14488
14769
  * @returns Promise that resolves when validation is complete
14489
14770
  */
14490
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$s(exchangeName), async (exchangeName) => {
14771
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$u(exchangeName), async (exchangeName) => {
14491
14772
  this.loggerService.log(METHOD_NAME_VALIDATE$3, {
14492
14773
  exchangeName,
14493
14774
  });
@@ -14739,7 +15020,7 @@ const METHOD_NAME_VALIDATE$2 = "strategyCoreService validate";
14739
15020
  * @param context - Execution context with strategyName, exchangeName, frameName
14740
15021
  * @returns Unique string key for memoization
14741
15022
  */
14742
- const CREATE_KEY_FN$r = (context) => {
15023
+ const CREATE_KEY_FN$t = (context) => {
14743
15024
  const parts = [context.strategyName, context.exchangeName];
14744
15025
  if (context.frameName)
14745
15026
  parts.push(context.frameName);
@@ -14771,7 +15052,7 @@ class StrategyCoreService {
14771
15052
  * @param context - Execution context with strategyName, exchangeName, frameName
14772
15053
  * @returns Promise that resolves when validation is complete
14773
15054
  */
14774
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
15055
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$t(context), async (context) => {
14775
15056
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
14776
15057
  context,
14777
15058
  });
@@ -16141,7 +16422,7 @@ class SizingGlobalService {
16141
16422
  * @param context - Context with riskName, exchangeName, frameName
16142
16423
  * @returns Unique string key for memoization
16143
16424
  */
16144
- const CREATE_KEY_FN$q = (context) => {
16425
+ const CREATE_KEY_FN$s = (context) => {
16145
16426
  const parts = [context.riskName, context.exchangeName];
16146
16427
  if (context.frameName)
16147
16428
  parts.push(context.frameName);
@@ -16167,7 +16448,7 @@ class RiskGlobalService {
16167
16448
  * @param payload - Payload with riskName, exchangeName and frameName
16168
16449
  * @returns Promise that resolves when validation is complete
16169
16450
  */
16170
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
16451
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$s(context), async (context) => {
16171
16452
  this.loggerService.log("riskGlobalService validate", {
16172
16453
  context,
16173
16454
  });
@@ -16245,7 +16526,7 @@ const METHOD_NAME_VALIDATE$1 = "actionCoreService validate";
16245
16526
  * @param context - Execution context with strategyName, exchangeName, frameName
16246
16527
  * @returns Unique string key for memoization
16247
16528
  */
16248
- const CREATE_KEY_FN$p = (context) => {
16529
+ const CREATE_KEY_FN$r = (context) => {
16249
16530
  const parts = [context.strategyName, context.exchangeName];
16250
16531
  if (context.frameName)
16251
16532
  parts.push(context.frameName);
@@ -16289,7 +16570,7 @@ class ActionCoreService {
16289
16570
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
16290
16571
  * @returns Promise that resolves when all validations complete
16291
16572
  */
16292
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
16573
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$r(context), async (context) => {
16293
16574
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
16294
16575
  context,
16295
16576
  });
@@ -21359,7 +21640,7 @@ const ReportWriter = new ReportWriterAdapter();
21359
21640
  * @param backtest - Whether running in backtest mode
21360
21641
  * @returns Unique string key for memoization
21361
21642
  */
21362
- const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
21643
+ const CREATE_KEY_FN$q = (symbol, strategyName, exchangeName, frameName, backtest) => {
21363
21644
  const parts = [symbol, strategyName, exchangeName];
21364
21645
  if (frameName)
21365
21646
  parts.push(frameName);
@@ -21605,7 +21886,7 @@ class BacktestMarkdownService {
21605
21886
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21606
21887
  * Each combination gets its own isolated storage instance.
21607
21888
  */
21608
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21889
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$q(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21609
21890
  /**
21610
21891
  * Processes tick events and accumulates closed signals.
21611
21892
  * Should be called from IStrategyCallbacks.onTick.
@@ -21762,7 +22043,7 @@ class BacktestMarkdownService {
21762
22043
  payload,
21763
22044
  });
21764
22045
  if (payload) {
21765
- const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22046
+ const key = CREATE_KEY_FN$q(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21766
22047
  this.getStorage.clear(key);
21767
22048
  }
21768
22049
  else {
@@ -21824,7 +22105,7 @@ class BacktestMarkdownService {
21824
22105
  * @param backtest - Whether running in backtest mode
21825
22106
  * @returns Unique string key for memoization
21826
22107
  */
21827
- const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
22108
+ const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
21828
22109
  const parts = [symbol, strategyName, exchangeName];
21829
22110
  if (frameName)
21830
22111
  parts.push(frameName);
@@ -22319,7 +22600,7 @@ class LiveMarkdownService {
22319
22600
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22320
22601
  * Each combination gets its own isolated storage instance.
22321
22602
  */
22322
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22603
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
22323
22604
  /**
22324
22605
  * Subscribes to live signal emitter to receive tick events.
22325
22606
  * Protected against multiple subscriptions.
@@ -22537,7 +22818,7 @@ class LiveMarkdownService {
22537
22818
  payload,
22538
22819
  });
22539
22820
  if (payload) {
22540
- const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22821
+ const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22541
22822
  this.getStorage.clear(key);
22542
22823
  }
22543
22824
  else {
@@ -22557,7 +22838,7 @@ class LiveMarkdownService {
22557
22838
  * @param backtest - Whether running in backtest mode
22558
22839
  * @returns Unique string key for memoization
22559
22840
  */
22560
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
22841
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
22561
22842
  const parts = [symbol, strategyName, exchangeName];
22562
22843
  if (frameName)
22563
22844
  parts.push(frameName);
@@ -22846,7 +23127,7 @@ class ScheduleMarkdownService {
22846
23127
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22847
23128
  * Each combination gets its own isolated storage instance.
22848
23129
  */
22849
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
23130
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22850
23131
  /**
22851
23132
  * Subscribes to signal emitter to receive scheduled signal events.
22852
23133
  * Protected against multiple subscriptions.
@@ -23049,7 +23330,7 @@ class ScheduleMarkdownService {
23049
23330
  payload,
23050
23331
  });
23051
23332
  if (payload) {
23052
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23333
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23053
23334
  this.getStorage.clear(key);
23054
23335
  }
23055
23336
  else {
@@ -23069,7 +23350,7 @@ class ScheduleMarkdownService {
23069
23350
  * @param backtest - Whether running in backtest mode
23070
23351
  * @returns Unique string key for memoization
23071
23352
  */
23072
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
23353
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
23073
23354
  const parts = [symbol, strategyName, exchangeName];
23074
23355
  if (frameName)
23075
23356
  parts.push(frameName);
@@ -23314,7 +23595,7 @@ class PerformanceMarkdownService {
23314
23595
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
23315
23596
  * Each combination gets its own isolated storage instance.
23316
23597
  */
23317
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23598
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
23318
23599
  /**
23319
23600
  * Subscribes to performance emitter to receive performance events.
23320
23601
  * Protected against multiple subscriptions.
@@ -23481,7 +23762,7 @@ class PerformanceMarkdownService {
23481
23762
  payload,
23482
23763
  });
23483
23764
  if (payload) {
23484
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23765
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23485
23766
  this.getStorage.clear(key);
23486
23767
  }
23487
23768
  else {
@@ -23960,7 +24241,7 @@ class WalkerMarkdownService {
23960
24241
  * @param backtest - Whether running in backtest mode
23961
24242
  * @returns Unique string key for memoization
23962
24243
  */
23963
- const CREATE_KEY_FN$k = (exchangeName, frameName, backtest) => {
24244
+ const CREATE_KEY_FN$m = (exchangeName, frameName, backtest) => {
23964
24245
  const parts = [exchangeName];
23965
24246
  if (frameName)
23966
24247
  parts.push(frameName);
@@ -24407,7 +24688,7 @@ class HeatMarkdownService {
24407
24688
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
24408
24689
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
24409
24690
  */
24410
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24691
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24411
24692
  /**
24412
24693
  * Subscribes to signal emitter to receive tick events.
24413
24694
  * Protected against multiple subscriptions.
@@ -24625,7 +24906,7 @@ class HeatMarkdownService {
24625
24906
  payload,
24626
24907
  });
24627
24908
  if (payload) {
24628
- const key = CREATE_KEY_FN$k(payload.exchangeName, payload.frameName, payload.backtest);
24909
+ const key = CREATE_KEY_FN$m(payload.exchangeName, payload.frameName, payload.backtest);
24629
24910
  this.getStorage.clear(key);
24630
24911
  }
24631
24912
  else {
@@ -25656,7 +25937,7 @@ class ClientPartial {
25656
25937
  * @param backtest - Whether running in backtest mode
25657
25938
  * @returns Unique string key for memoization
25658
25939
  */
25659
- const CREATE_KEY_FN$j = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25940
+ const CREATE_KEY_FN$l = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25660
25941
  /**
25661
25942
  * Creates a callback function for emitting profit events to partialProfitSubject.
25662
25943
  *
@@ -25778,7 +26059,7 @@ class PartialConnectionService {
25778
26059
  * Key format: "signalId:backtest" or "signalId:live"
25779
26060
  * Value: ClientPartial instance with logger and event emitters
25780
26061
  */
25781
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$j(signalId, backtest), (signalId, backtest) => {
26062
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$l(signalId, backtest), (signalId, backtest) => {
25782
26063
  return new ClientPartial({
25783
26064
  signalId,
25784
26065
  logger: this.loggerService,
@@ -25868,7 +26149,7 @@ class PartialConnectionService {
25868
26149
  const partial = this.getPartial(data.id, backtest);
25869
26150
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25870
26151
  await partial.clear(symbol, data, priceClose, backtest);
25871
- const key = CREATE_KEY_FN$j(data.id, backtest);
26152
+ const key = CREATE_KEY_FN$l(data.id, backtest);
25872
26153
  this.getPartial.clear(key);
25873
26154
  };
25874
26155
  }
@@ -25884,7 +26165,7 @@ class PartialConnectionService {
25884
26165
  * @param backtest - Whether running in backtest mode
25885
26166
  * @returns Unique string key for memoization
25886
26167
  */
25887
- const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
26168
+ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
25888
26169
  const parts = [symbol, strategyName, exchangeName];
25889
26170
  if (frameName)
25890
26171
  parts.push(frameName);
@@ -26107,7 +26388,7 @@ class PartialMarkdownService {
26107
26388
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26108
26389
  * Each combination gets its own isolated storage instance.
26109
26390
  */
26110
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
26391
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
26111
26392
  /**
26112
26393
  * Subscribes to partial profit/loss signal emitters to receive events.
26113
26394
  * Protected against multiple subscriptions.
@@ -26317,7 +26598,7 @@ class PartialMarkdownService {
26317
26598
  payload,
26318
26599
  });
26319
26600
  if (payload) {
26320
- const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26601
+ const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26321
26602
  this.getStorage.clear(key);
26322
26603
  }
26323
26604
  else {
@@ -26333,7 +26614,7 @@ class PartialMarkdownService {
26333
26614
  * @param context - Context with strategyName, exchangeName, frameName
26334
26615
  * @returns Unique string key for memoization
26335
26616
  */
26336
- const CREATE_KEY_FN$h = (context) => {
26617
+ const CREATE_KEY_FN$j = (context) => {
26337
26618
  const parts = [context.strategyName, context.exchangeName];
26338
26619
  if (context.frameName)
26339
26620
  parts.push(context.frameName);
@@ -26407,7 +26688,7 @@ class PartialGlobalService {
26407
26688
  * @param context - Context with strategyName, exchangeName and frameName
26408
26689
  * @param methodName - Name of the calling method for error tracking
26409
26690
  */
26410
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), (context, methodName) => {
26691
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), (context, methodName) => {
26411
26692
  this.loggerService.log("partialGlobalService validate", {
26412
26693
  context,
26413
26694
  methodName,
@@ -26862,7 +27143,7 @@ class ClientBreakeven {
26862
27143
  * @param backtest - Whether running in backtest mode
26863
27144
  * @returns Unique string key for memoization
26864
27145
  */
26865
- const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
27146
+ const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26866
27147
  /**
26867
27148
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26868
27149
  *
@@ -26948,7 +27229,7 @@ class BreakevenConnectionService {
26948
27229
  * Key format: "signalId:backtest" or "signalId:live"
26949
27230
  * Value: ClientBreakeven instance with logger and event emitter
26950
27231
  */
26951
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
27232
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
26952
27233
  return new ClientBreakeven({
26953
27234
  signalId,
26954
27235
  logger: this.loggerService,
@@ -27009,7 +27290,7 @@ class BreakevenConnectionService {
27009
27290
  const breakeven = this.getBreakeven(data.id, backtest);
27010
27291
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
27011
27292
  await breakeven.clear(symbol, data, priceClose, backtest);
27012
- const key = CREATE_KEY_FN$g(data.id, backtest);
27293
+ const key = CREATE_KEY_FN$i(data.id, backtest);
27013
27294
  this.getBreakeven.clear(key);
27014
27295
  };
27015
27296
  }
@@ -27025,7 +27306,7 @@ class BreakevenConnectionService {
27025
27306
  * @param backtest - Whether running in backtest mode
27026
27307
  * @returns Unique string key for memoization
27027
27308
  */
27028
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
27309
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
27029
27310
  const parts = [symbol, strategyName, exchangeName];
27030
27311
  if (frameName)
27031
27312
  parts.push(frameName);
@@ -27200,7 +27481,7 @@ class BreakevenMarkdownService {
27200
27481
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27201
27482
  * Each combination gets its own isolated storage instance.
27202
27483
  */
27203
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
27484
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
27204
27485
  /**
27205
27486
  * Subscribes to breakeven signal emitter to receive events.
27206
27487
  * Protected against multiple subscriptions.
@@ -27389,7 +27670,7 @@ class BreakevenMarkdownService {
27389
27670
  payload,
27390
27671
  });
27391
27672
  if (payload) {
27392
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27673
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27393
27674
  this.getStorage.clear(key);
27394
27675
  }
27395
27676
  else {
@@ -27405,7 +27686,7 @@ class BreakevenMarkdownService {
27405
27686
  * @param context - Context with strategyName, exchangeName, frameName
27406
27687
  * @returns Unique string key for memoization
27407
27688
  */
27408
- const CREATE_KEY_FN$e = (context) => {
27689
+ const CREATE_KEY_FN$g = (context) => {
27409
27690
  const parts = [context.strategyName, context.exchangeName];
27410
27691
  if (context.frameName)
27411
27692
  parts.push(context.frameName);
@@ -27479,7 +27760,7 @@ class BreakevenGlobalService {
27479
27760
  * @param context - Context with strategyName, exchangeName and frameName
27480
27761
  * @param methodName - Name of the calling method for error tracking
27481
27762
  */
27482
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
27763
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
27483
27764
  this.loggerService.log("breakevenGlobalService validate", {
27484
27765
  context,
27485
27766
  methodName,
@@ -27700,7 +27981,7 @@ class ConfigValidationService {
27700
27981
  * @param backtest - Whether running in backtest mode
27701
27982
  * @returns Unique string key for memoization
27702
27983
  */
27703
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
27984
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
27704
27985
  const parts = [symbol, strategyName, exchangeName];
27705
27986
  if (frameName)
27706
27987
  parts.push(frameName);
@@ -27867,7 +28148,7 @@ class RiskMarkdownService {
27867
28148
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27868
28149
  * Each combination gets its own isolated storage instance.
27869
28150
  */
27870
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
28151
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
27871
28152
  /**
27872
28153
  * Subscribes to risk rejection emitter to receive rejection events.
27873
28154
  * Protected against multiple subscriptions.
@@ -28056,7 +28337,7 @@ class RiskMarkdownService {
28056
28337
  payload,
28057
28338
  });
28058
28339
  if (payload) {
28059
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28340
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28060
28341
  this.getStorage.clear(key);
28061
28342
  }
28062
28343
  else {
@@ -30636,7 +30917,7 @@ class HighestProfitReportService {
30636
30917
  * @returns Colon-separated key string for memoization
30637
30918
  * @internal
30638
30919
  */
30639
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
30920
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
30640
30921
  const parts = [symbol, strategyName, exchangeName];
30641
30922
  if (frameName)
30642
30923
  parts.push(frameName);
@@ -30878,7 +31159,7 @@ class StrategyMarkdownService {
30878
31159
  *
30879
31160
  * @internal
30880
31161
  */
30881
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
31162
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30882
31163
  /**
30883
31164
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30884
31165
  *
@@ -31452,7 +31733,7 @@ class StrategyMarkdownService {
31452
31733
  this.clear = async (payload) => {
31453
31734
  this.loggerService.log("strategyMarkdownService clear", { payload });
31454
31735
  if (payload) {
31455
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31736
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31456
31737
  this.getStorage.clear(key);
31457
31738
  }
31458
31739
  else {
@@ -31560,7 +31841,7 @@ class StrategyMarkdownService {
31560
31841
  * Creates a unique key for memoizing ReportStorage instances.
31561
31842
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
31562
31843
  */
31563
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
31844
+ const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
31564
31845
  const parts = [symbol, strategyName, exchangeName];
31565
31846
  if (frameName)
31566
31847
  parts.push(frameName);
@@ -31753,7 +32034,7 @@ let ReportStorage$2 = class ReportStorage {
31753
32034
  class SyncMarkdownService {
31754
32035
  constructor() {
31755
32036
  this.loggerService = inject(TYPES.loggerService);
31756
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
32037
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
31757
32038
  /**
31758
32039
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
31759
32040
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31951,7 +32232,7 @@ class SyncMarkdownService {
31951
32232
  this.clear = async (payload) => {
31952
32233
  this.loggerService.log("syncMarkdownService clear", { payload });
31953
32234
  if (payload) {
31954
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32235
+ const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31955
32236
  this.getStorage.clear(key);
31956
32237
  }
31957
32238
  else {
@@ -31964,7 +32245,7 @@ class SyncMarkdownService {
31964
32245
  /**
31965
32246
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31966
32247
  */
31967
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32248
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
31968
32249
  const parts = [symbol, strategyName, exchangeName];
31969
32250
  if (frameName)
31970
32251
  parts.push(frameName);
@@ -32142,7 +32423,7 @@ let ReportStorage$1 = class ReportStorage {
32142
32423
  class HighestProfitMarkdownService {
32143
32424
  constructor() {
32144
32425
  this.loggerService = inject(TYPES.loggerService);
32145
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
32426
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
32146
32427
  /**
32147
32428
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
32148
32429
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -32308,7 +32589,7 @@ class HighestProfitMarkdownService {
32308
32589
  this.clear = async (payload) => {
32309
32590
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
32310
32591
  if (payload) {
32311
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32592
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32312
32593
  this.getStorage.clear(key);
32313
32594
  }
32314
32595
  else {
@@ -32330,7 +32611,7 @@ const LISTEN_TIMEOUT$1 = 120000;
32330
32611
  * @param backtest - Whether running in backtest mode
32331
32612
  * @returns Unique string key for memoization
32332
32613
  */
32333
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32614
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
32334
32615
  const parts = [symbol, strategyName, exchangeName];
32335
32616
  if (frameName)
32336
32617
  parts.push(frameName);
@@ -32373,7 +32654,7 @@ class PriceMetaService {
32373
32654
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
32374
32655
  * Instances are cached until clear() is called.
32375
32656
  */
32376
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32657
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32377
32658
  /**
32378
32659
  * Returns the current market price for the given symbol and context.
32379
32660
  *
@@ -32402,10 +32683,10 @@ class PriceMetaService {
32402
32683
  if (source.data) {
32403
32684
  return source.data;
32404
32685
  }
32405
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32686
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$b(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32406
32687
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
32407
32688
  if (typeof currentPrice === "symbol") {
32408
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$9(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32689
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$b(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32409
32690
  }
32410
32691
  return currentPrice;
32411
32692
  };
@@ -32447,7 +32728,7 @@ class PriceMetaService {
32447
32728
  this.getSource.clear();
32448
32729
  return;
32449
32730
  }
32450
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32731
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32451
32732
  this.getSource.clear(key);
32452
32733
  };
32453
32734
  }
@@ -32465,7 +32746,7 @@ const LISTEN_TIMEOUT = 120000;
32465
32746
  * @param backtest - Whether running in backtest mode
32466
32747
  * @returns Unique string key for memoization
32467
32748
  */
32468
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32749
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
32469
32750
  const parts = [symbol, strategyName, exchangeName];
32470
32751
  if (frameName)
32471
32752
  parts.push(frameName);
@@ -32508,7 +32789,7 @@ class TimeMetaService {
32508
32789
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
32509
32790
  * Instances are cached until clear() is called.
32510
32791
  */
32511
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32792
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
32512
32793
  /**
32513
32794
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
32514
32795
  *
@@ -32536,10 +32817,10 @@ class TimeMetaService {
32536
32817
  if (source.data) {
32537
32818
  return source.data;
32538
32819
  }
32539
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32820
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
32540
32821
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
32541
32822
  if (typeof timestamp === "symbol") {
32542
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32823
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
32543
32824
  }
32544
32825
  return timestamp;
32545
32826
  };
@@ -32581,7 +32862,7 @@ class TimeMetaService {
32581
32862
  this.getSource.clear();
32582
32863
  return;
32583
32864
  }
32584
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32865
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32585
32866
  this.getSource.clear(key);
32586
32867
  };
32587
32868
  }
@@ -32687,7 +32968,7 @@ class MaxDrawdownReportService {
32687
32968
  /**
32688
32969
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
32689
32970
  */
32690
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32971
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32691
32972
  const parts = [symbol, strategyName, exchangeName];
32692
32973
  if (frameName)
32693
32974
  parts.push(frameName);
@@ -32813,7 +33094,7 @@ class ReportStorage {
32813
33094
  class MaxDrawdownMarkdownService {
32814
33095
  constructor() {
32815
33096
  this.loggerService = inject(TYPES.loggerService);
32816
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
33097
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32817
33098
  /**
32818
33099
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
32819
33100
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32892,7 +33173,7 @@ class MaxDrawdownMarkdownService {
32892
33173
  this.clear = async (payload) => {
32893
33174
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32894
33175
  if (payload) {
32895
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
33176
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32896
33177
  this.getStorage.clear(key);
32897
33178
  }
32898
33179
  else {
@@ -32910,7 +33191,7 @@ const METHOD_NAME_VALIDATE = "notificationHelperService.validate";
32910
33191
  * @param context - Execution context with strategyName, exchangeName, frameName
32911
33192
  * @returns Unique string key for memoization
32912
33193
  */
32913
- const CREATE_KEY_FN$6 = (context) => {
33194
+ const CREATE_KEY_FN$8 = (context) => {
32914
33195
  const parts = [context.strategyName, context.exchangeName];
32915
33196
  if (context.frameName)
32916
33197
  parts.push(context.frameName);
@@ -32943,7 +33224,7 @@ class NotificationHelperService {
32943
33224
  * @param context - Routing context: strategyName, exchangeName, frameName
32944
33225
  * @throws {Error} If any registered schema fails validation
32945
33226
  */
32946
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$6(context), async (context) => {
33227
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$8(context), async (context) => {
32947
33228
  this.loggerService.log(METHOD_NAME_VALIDATE, {
32948
33229
  context,
32949
33230
  });
@@ -46487,7 +46768,7 @@ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapte
46487
46768
  * @param backtest - Flag indicating if the context is backtest or live
46488
46769
  * @returns Composite key string
46489
46770
  */
46490
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46771
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
46491
46772
  const parts = [symbol, strategyName, exchangeName];
46492
46773
  if (frameName)
46493
46774
  parts.push(frameName);
@@ -46577,7 +46858,7 @@ class RecentMemoryBacktestUtils {
46577
46858
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
46578
46859
  signalId: event.data.id,
46579
46860
  });
46580
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46861
+ const key = CREATE_KEY_FN$7(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46581
46862
  this._signals.set(key, event.data);
46582
46863
  };
46583
46864
  /**
@@ -46590,7 +46871,7 @@ class RecentMemoryBacktestUtils {
46590
46871
  * @returns The latest signal or null if not found
46591
46872
  */
46592
46873
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46593
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46874
+ const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
46594
46875
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46595
46876
  return this._signals.get(key) ?? null;
46596
46877
  };
@@ -46696,7 +46977,7 @@ class RecentMemoryLiveUtils {
46696
46977
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
46697
46978
  signalId: event.data.id,
46698
46979
  });
46699
- const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46980
+ const key = CREATE_KEY_FN$7(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
46700
46981
  this._signals.set(key, event.data);
46701
46982
  };
46702
46983
  /**
@@ -46709,7 +46990,7 @@ class RecentMemoryLiveUtils {
46709
46990
  * @returns The latest signal or null if not found
46710
46991
  */
46711
46992
  this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
46712
- const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
46993
+ const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
46713
46994
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46714
46995
  return this._signals.get(key) ?? null;
46715
46996
  };
@@ -47060,6 +47341,554 @@ const RecentLive = new RecentLiveAdapter();
47060
47341
  */
47061
47342
  const RecentBacktest = new RecentBacktestAdapter();
47062
47343
 
47344
+ const CREATE_KEY_FN$6 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47345
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_GET = "StateLocalInstance.getState";
47346
+ const STATE_LOCAL_INSTANCE_METHOD_NAME_SET = "StateLocalInstance.setState";
47347
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT = "StatePersistInstance.waitForInit";
47348
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_GET = "StatePersistInstance.getState";
47349
+ const STATE_PERSIST_INSTANCE_METHOD_NAME_SET = "StatePersistInstance.setState";
47350
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "StateBacktestAdapter.dispose";
47351
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_GET = "StateBacktestAdapter.getState";
47352
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_SET = "StateBacktestAdapter.setState";
47353
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "StateBacktestAdapter.useLocal";
47354
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "StateBacktestAdapter.usePersist";
47355
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "StateBacktestAdapter.useDummy";
47356
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateBacktestAdapter.useStateAdapter";
47357
+ const STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "StateBacktestAdapter.clear";
47358
+ const STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "StateLiveAdapter.dispose";
47359
+ const STATE_LIVE_ADAPTER_METHOD_NAME_GET = "StateLiveAdapter.getState";
47360
+ const STATE_LIVE_ADAPTER_METHOD_NAME_SET = "StateLiveAdapter.setState";
47361
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "StateLiveAdapter.useLocal";
47362
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "StateLiveAdapter.usePersist";
47363
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "StateLiveAdapter.useDummy";
47364
+ const STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER = "StateLiveAdapter.useStateAdapter";
47365
+ const STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR = "StateLiveAdapter.clear";
47366
+ const STATE_ADAPTER_METHOD_NAME_ENABLE = "StateAdapter.enable";
47367
+ const STATE_ADAPTER_METHOD_NAME_DISABLE = "StateAdapter.disable";
47368
+ const STATE_ADAPTER_METHOD_NAME_GET = "StateAdapter.getState";
47369
+ const STATE_ADAPTER_METHOD_NAME_SET = "StateAdapter.setState";
47370
+ /**
47371
+ * In-process state instance backed by a plain object reference.
47372
+ * All data lives in process memory only - no disk persistence.
47373
+ *
47374
+ * Features:
47375
+ * - Mutable in-memory state with functional dispatch support
47376
+ * - Scoped per (signalId, bucketName) pair
47377
+ *
47378
+ * Use for backtesting and unit tests where persistence between runs is not needed.
47379
+ * Tracks per-trade metrics such as peakPercent and minutesOpen to implement
47380
+ * the capitulation rule: exit when peak < threshold after N minutes open.
47381
+ */
47382
+ class StateLocalInstance {
47383
+ constructor(initialValue, signalId, bucketName) {
47384
+ this.initialValue = initialValue;
47385
+ this.signalId = signalId;
47386
+ this.bucketName = bucketName;
47387
+ /**
47388
+ * Initializes _value from initialValue - local state needs no async setup.
47389
+ * @returns Promise that resolves immediately
47390
+ */
47391
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
47392
+ this._value = this.initialValue;
47393
+ });
47394
+ /**
47395
+ * Update the in-memory state value.
47396
+ * @param dispatch - New value or updater function receiving current value
47397
+ * @returns Updated state value
47398
+ */
47399
+ this.setState = functoolsKit.queued(async (dispatch) => {
47400
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
47401
+ signalId: this.signalId,
47402
+ bucketName: this.bucketName,
47403
+ });
47404
+ if (typeof dispatch === "function") {
47405
+ this._value = await dispatch(this._value);
47406
+ }
47407
+ else {
47408
+ this._value = dispatch;
47409
+ }
47410
+ return this._value;
47411
+ });
47412
+ }
47413
+ /**
47414
+ * Read the current in-memory state value.
47415
+ * @returns Current state value
47416
+ */
47417
+ async getState() {
47418
+ backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
47419
+ signalId: this.signalId,
47420
+ bucketName: this.bucketName,
47421
+ });
47422
+ return this._value;
47423
+ }
47424
+ /** Releases resources held by this instance. */
47425
+ async dispose() {
47426
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47427
+ signalId: this.signalId,
47428
+ bucketName: this.bucketName,
47429
+ });
47430
+ }
47431
+ }
47432
+ /**
47433
+ * No-op state instance that discards all writes.
47434
+ * Used for disabling state in tests or dry-run scenarios.
47435
+ *
47436
+ * Useful when replaying historical candles without needing to accumulate
47437
+ * peakPercent/minutesOpen — the capitulation rule is simply never triggered.
47438
+ */
47439
+ class StateDummyInstance {
47440
+ constructor(initialValue, signalId, bucketName) {
47441
+ this.initialValue = initialValue;
47442
+ this.signalId = signalId;
47443
+ this.bucketName = bucketName;
47444
+ /**
47445
+ * No-op initialization.
47446
+ * @returns Promise that resolves immediately
47447
+ */
47448
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
47449
+ });
47450
+ }
47451
+ /**
47452
+ * No-op read - always returns initialValue.
47453
+ * @returns initialValue
47454
+ */
47455
+ async getState() {
47456
+ return this.initialValue;
47457
+ }
47458
+ /**
47459
+ * No-op write - discards the value and returns initialValue.
47460
+ * @returns initialValue
47461
+ */
47462
+ async setState(_dispatch) {
47463
+ return this.initialValue;
47464
+ }
47465
+ /** No-op. */
47466
+ async dispose() {
47467
+ }
47468
+ }
47469
+ /**
47470
+ * File-system backed state instance.
47471
+ * Data is persisted atomically to disk via PersistStateAdapter.
47472
+ * State is restored from disk on waitForInit.
47473
+ *
47474
+ * Features:
47475
+ * - Crash-safe atomic file writes
47476
+ * - Functional dispatch support
47477
+ * - Scoped per (signalId, bucketName) pair
47478
+ *
47479
+ * Use in live trading to survive process restarts mid-trade.
47480
+ * Preserves peakPercent and minutesOpen so the capitulation rule
47481
+ * (exit if peak < threshold after N minutes) continues correctly after a crash.
47482
+ */
47483
+ class StatePersistInstance {
47484
+ constructor(initialValue, signalId, bucketName) {
47485
+ this.initialValue = initialValue;
47486
+ this.signalId = signalId;
47487
+ this.bucketName = bucketName;
47488
+ /**
47489
+ * Initialize persistence storage and restore state from disk.
47490
+ * @param initial - Whether this is the first initialization
47491
+ */
47492
+ this.waitForInit = functoolsKit.singleshot(async (initial) => {
47493
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT, {
47494
+ signalId: this.signalId,
47495
+ bucketName: this.bucketName,
47496
+ initial,
47497
+ });
47498
+ await PersistStateAdapter.waitForInit(this.signalId, this.bucketName, initial);
47499
+ const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
47500
+ if (data) {
47501
+ this._value = data.data;
47502
+ return;
47503
+ }
47504
+ this._value = this.initialValue;
47505
+ });
47506
+ /**
47507
+ * Update state and persist to disk atomically.
47508
+ * @param dispatch - New value or updater function receiving current value
47509
+ * @returns Updated state value
47510
+ */
47511
+ this.setState = functoolsKit.queued(async (dispatch) => {
47512
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
47513
+ signalId: this.signalId,
47514
+ bucketName: this.bucketName,
47515
+ });
47516
+ if (typeof dispatch === "function") {
47517
+ this._value = await dispatch(this._value);
47518
+ }
47519
+ else {
47520
+ this._value = dispatch;
47521
+ }
47522
+ const id = CREATE_KEY_FN$6(this.signalId, this.bucketName);
47523
+ await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
47524
+ return this._value;
47525
+ });
47526
+ }
47527
+ /**
47528
+ * Read the current persisted state value.
47529
+ * @returns Current state value
47530
+ */
47531
+ async getState() {
47532
+ backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
47533
+ signalId: this.signalId,
47534
+ bucketName: this.bucketName,
47535
+ });
47536
+ return this._value;
47537
+ }
47538
+ /** Releases resources held by this instance. */
47539
+ async dispose() {
47540
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47541
+ signalId: this.signalId,
47542
+ bucketName: this.bucketName,
47543
+ });
47544
+ await PersistStateAdapter.dispose(this.signalId, this.bucketName);
47545
+ }
47546
+ }
47547
+ /**
47548
+ * Backtest state adapter with pluggable storage backend.
47549
+ *
47550
+ * Features:
47551
+ * - Adapter pattern for swappable state instance implementations
47552
+ * - Default backend: StateLocalInstance (in-memory, no disk persistence)
47553
+ * - Alternative backends: StatePersistInstance, StateDummyInstance
47554
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47555
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47556
+ *
47557
+ * Primary use case — LLM-driven capitulation rule:
47558
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47559
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47560
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47561
+ * the LLM thesis was not confirmed by market — exit immediately.
47562
+ * State tracks `{ peakPercent, minutesOpen }` per signal across onActivePing ticks.
47563
+ */
47564
+ class StateBacktestAdapter {
47565
+ constructor() {
47566
+ this.StateFactory = StateLocalInstance;
47567
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$6(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47568
+ /**
47569
+ * Disposes all memoized instances for the given signalId.
47570
+ * Called by StateAdapter when a signal is cancelled or closed.
47571
+ * @param signalId - Signal identifier to dispose
47572
+ */
47573
+ this.disposeSignal = (signalId) => {
47574
+ const prefix = CREATE_KEY_FN$6(signalId, "");
47575
+ for (const key of this.getInstance.keys()) {
47576
+ if (key.startsWith(prefix)) {
47577
+ const instance = this.getInstance.get(key);
47578
+ instance && instance.dispose();
47579
+ this.getInstance.clear(key);
47580
+ }
47581
+ }
47582
+ };
47583
+ /**
47584
+ * Read the current state value for a signal.
47585
+ * @param dto.signalId - Signal identifier
47586
+ * @param dto.bucketName - Bucket name
47587
+ * @param dto.initialValue - Default value when no persisted state exists
47588
+ * @returns Current state value
47589
+ */
47590
+ this.getState = async (dto) => {
47591
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_GET, {
47592
+ signalId: dto.signalId,
47593
+ bucketName: dto.bucketName,
47594
+ });
47595
+ const key = CREATE_KEY_FN$6(dto.signalId, dto.bucketName);
47596
+ const isInitial = !this.getInstance.has(key);
47597
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47598
+ await instance.waitForInit(isInitial);
47599
+ return await instance.getState();
47600
+ };
47601
+ /**
47602
+ * Update the state value for a signal.
47603
+ * @param dispatch - New value or updater function receiving current value
47604
+ * @param dto.signalId - Signal identifier
47605
+ * @param dto.bucketName - Bucket name
47606
+ * @param dto.initialValue - Default value when no persisted state exists
47607
+ * @returns Updated state value
47608
+ */
47609
+ this.setState = async (dispatch, dto) => {
47610
+ backtest.loggerService.debug(STATE_BACKTEST_ADAPTER_METHOD_NAME_SET, {
47611
+ signalId: dto.signalId,
47612
+ bucketName: dto.bucketName,
47613
+ });
47614
+ const key = CREATE_KEY_FN$6(dto.signalId, dto.bucketName);
47615
+ const isInitial = !this.getInstance.has(key);
47616
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47617
+ await instance.waitForInit(isInitial);
47618
+ return await instance.setState(dispatch);
47619
+ };
47620
+ /**
47621
+ * Switches to in-memory adapter (default).
47622
+ * All data lives in process memory only.
47623
+ */
47624
+ this.useLocal = () => {
47625
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
47626
+ this.StateFactory = StateLocalInstance;
47627
+ };
47628
+ /**
47629
+ * Switches to file-system backed adapter.
47630
+ * Data is persisted to disk via PersistStateAdapter.
47631
+ */
47632
+ this.usePersist = () => {
47633
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
47634
+ this.StateFactory = StatePersistInstance;
47635
+ };
47636
+ /**
47637
+ * Switches to dummy adapter that discards all writes.
47638
+ */
47639
+ this.useDummy = () => {
47640
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
47641
+ this.StateFactory = StateDummyInstance;
47642
+ };
47643
+ /**
47644
+ * Switches to a custom state adapter implementation.
47645
+ * @param Ctor - Constructor for the custom state instance
47646
+ */
47647
+ this.useStateAdapter = (Ctor) => {
47648
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47649
+ this.StateFactory = Ctor;
47650
+ };
47651
+ /**
47652
+ * Clears the memoized instance cache.
47653
+ * Call this when process.cwd() changes between strategy iterations
47654
+ * so new instances are created with the updated base path.
47655
+ */
47656
+ this.clear = () => {
47657
+ backtest.loggerService.info(STATE_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
47658
+ this.getInstance.clear();
47659
+ };
47660
+ }
47661
+ }
47662
+ /**
47663
+ * Live trading state adapter with pluggable storage backend.
47664
+ *
47665
+ * Features:
47666
+ * - Adapter pattern for swappable state instance implementations
47667
+ * - Default backend: StatePersistInstance (file-system backed, survives restarts)
47668
+ * - Alternative backends: StateLocalInstance, StateDummyInstance
47669
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useStateAdapter()
47670
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from StateAdapter
47671
+ *
47672
+ * Primary use case — LLM-driven capitulation rule:
47673
+ * Profitable trades endure -0.5–2.5% drawdown and still reach peak 2–3%+.
47674
+ * SL trades never go positive (Feb25) or show peak < 0.15% (Feb08, Feb13).
47675
+ * Rule: if position open >= N minutes and peakPercent < threshold (e.g. 0.3%),
47676
+ * the LLM thesis was not confirmed by market — exit immediately.
47677
+ * State persists `{ peakPercent, minutesOpen }` per signal across process restarts.
47678
+ */
47679
+ class StateLiveAdapter {
47680
+ constructor() {
47681
+ this.StateFactory = StatePersistInstance;
47682
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$6(signalId, bucketName), (signalId, bucketName, initialValue) => Reflect.construct(this.StateFactory, [initialValue, signalId, bucketName]));
47683
+ /**
47684
+ * Disposes all memoized instances for the given signalId.
47685
+ * Called by StateAdapter when a signal is cancelled or closed.
47686
+ * @param signalId - Signal identifier to dispose
47687
+ */
47688
+ this.disposeSignal = (signalId) => {
47689
+ const prefix = CREATE_KEY_FN$6(signalId, "");
47690
+ for (const key of this.getInstance.keys()) {
47691
+ if (key.startsWith(prefix)) {
47692
+ const instance = this.getInstance.get(key);
47693
+ instance && instance.dispose();
47694
+ this.getInstance.clear(key);
47695
+ }
47696
+ }
47697
+ };
47698
+ /**
47699
+ * Read the current state value for a signal.
47700
+ * @param dto.signalId - Signal identifier
47701
+ * @param dto.bucketName - Bucket name
47702
+ * @param dto.initialValue - Default value when no persisted state exists
47703
+ * @returns Current state value
47704
+ */
47705
+ this.getState = async (dto) => {
47706
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_GET, {
47707
+ signalId: dto.signalId,
47708
+ bucketName: dto.bucketName,
47709
+ });
47710
+ const key = CREATE_KEY_FN$6(dto.signalId, dto.bucketName);
47711
+ const isInitial = !this.getInstance.has(key);
47712
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47713
+ await instance.waitForInit(isInitial);
47714
+ return await instance.getState();
47715
+ };
47716
+ /**
47717
+ * Update the state value for a signal.
47718
+ * @param dispatch - New value or updater function receiving current value
47719
+ * @param dto.signalId - Signal identifier
47720
+ * @param dto.bucketName - Bucket name
47721
+ * @param dto.initialValue - Default value when no persisted state exists
47722
+ * @returns Updated state value
47723
+ */
47724
+ this.setState = async (dispatch, dto) => {
47725
+ backtest.loggerService.debug(STATE_LIVE_ADAPTER_METHOD_NAME_SET, {
47726
+ signalId: dto.signalId,
47727
+ bucketName: dto.bucketName,
47728
+ });
47729
+ const key = CREATE_KEY_FN$6(dto.signalId, dto.bucketName);
47730
+ const isInitial = !this.getInstance.has(key);
47731
+ const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
47732
+ await instance.waitForInit(isInitial);
47733
+ return await instance.setState(dispatch);
47734
+ };
47735
+ /**
47736
+ * Switches to in-memory adapter.
47737
+ * All data lives in process memory only.
47738
+ */
47739
+ this.useLocal = () => {
47740
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
47741
+ this.StateFactory = StateLocalInstance;
47742
+ };
47743
+ /**
47744
+ * Switches to file-system backed adapter (default).
47745
+ * Data is persisted to disk via PersistStateAdapter.
47746
+ */
47747
+ this.usePersist = () => {
47748
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
47749
+ this.StateFactory = StatePersistInstance;
47750
+ };
47751
+ /**
47752
+ * Switches to dummy adapter that discards all writes.
47753
+ */
47754
+ this.useDummy = () => {
47755
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
47756
+ this.StateFactory = StateDummyInstance;
47757
+ };
47758
+ /**
47759
+ * Switches to a custom state adapter implementation.
47760
+ * @param Ctor - Constructor for the custom state instance
47761
+ */
47762
+ this.useStateAdapter = (Ctor) => {
47763
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_USE_STATE_ADAPTER);
47764
+ this.StateFactory = Ctor;
47765
+ };
47766
+ /**
47767
+ * Clears the memoized instance cache.
47768
+ * Call this when process.cwd() changes between strategy iterations
47769
+ * so new instances are created with the updated base path.
47770
+ */
47771
+ this.clear = () => {
47772
+ backtest.loggerService.info(STATE_LIVE_ADAPTER_METHOD_NAME_CLEAR);
47773
+ this.getInstance.clear();
47774
+ };
47775
+ }
47776
+ }
47777
+ /**
47778
+ * Main state adapter that manages both backtest and live state storage.
47779
+ *
47780
+ * Features:
47781
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
47782
+ * - Routes all operations to StateBacktest or StateLive based on dto.backtest
47783
+ * - Singleshot enable pattern prevents duplicate subscriptions
47784
+ * - Cleanup function for proper unsubscription
47785
+ */
47786
+ class StateAdapter {
47787
+ constructor() {
47788
+ /**
47789
+ * Enables state storage by subscribing to signal lifecycle events.
47790
+ * Clears memoized instances in StateBacktest and StateLive when a signal
47791
+ * is cancelled or closed, preventing stale instances from accumulating.
47792
+ * Uses singleshot to ensure one-time subscription.
47793
+ *
47794
+ * @returns Cleanup function that unsubscribes from all emitters
47795
+ */
47796
+ this.enable = functoolsKit.singleshot(() => {
47797
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_ENABLE);
47798
+ const unCancel = signalEmitter
47799
+ .filter(({ action }) => action === "cancelled")
47800
+ .connect(({ signal }) => {
47801
+ StateBacktest.disposeSignal(signal.id);
47802
+ StateLive.disposeSignal(signal.id);
47803
+ });
47804
+ const unClose = signalEmitter
47805
+ .filter(({ action }) => action === "closed")
47806
+ .connect(({ signal }) => {
47807
+ StateBacktest.disposeSignal(signal.id);
47808
+ StateLive.disposeSignal(signal.id);
47809
+ });
47810
+ return functoolsKit.compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47811
+ });
47812
+ /**
47813
+ * Disables state storage by unsubscribing from signal lifecycle events.
47814
+ * Safe to call multiple times.
47815
+ */
47816
+ this.disable = () => {
47817
+ backtest.loggerService.info(STATE_ADAPTER_METHOD_NAME_DISABLE);
47818
+ if (this.enable.hasValue()) {
47819
+ const lastSubscription = this.enable();
47820
+ lastSubscription();
47821
+ }
47822
+ };
47823
+ /**
47824
+ * Read the current state value for a signal.
47825
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47826
+ * @param dto.signalId - Signal identifier
47827
+ * @param dto.bucketName - Bucket name
47828
+ * @param dto.initialValue - Default value when no persisted state exists
47829
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47830
+ * @returns Current state value
47831
+ * @throws Error if adapter is not enabled
47832
+ */
47833
+ this.getState = async (dto) => {
47834
+ if (!this.enable.hasValue()) {
47835
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47836
+ }
47837
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_GET, {
47838
+ signalId: dto.signalId,
47839
+ bucketName: dto.bucketName,
47840
+ backtest: dto.backtest,
47841
+ });
47842
+ if (dto.backtest) {
47843
+ return await StateBacktest.getState(dto);
47844
+ }
47845
+ return await StateLive.getState(dto);
47846
+ };
47847
+ /**
47848
+ * Update the state value for a signal.
47849
+ * Routes to StateBacktest or StateLive based on dto.backtest.
47850
+ * @param dispatch - New value or updater function receiving current value
47851
+ * @param dto.signalId - Signal identifier
47852
+ * @param dto.bucketName - Bucket name
47853
+ * @param dto.initialValue - Default value when no persisted state exists
47854
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47855
+ * @returns Updated state value
47856
+ * @throws Error if adapter is not enabled
47857
+ */
47858
+ this.setState = async (dispatch, dto) => {
47859
+ if (!this.enable.hasValue()) {
47860
+ throw new Error("StateAdapter is not enabled. Call enable() first.");
47861
+ }
47862
+ backtest.loggerService.debug(STATE_ADAPTER_METHOD_NAME_SET, {
47863
+ signalId: dto.signalId,
47864
+ bucketName: dto.bucketName,
47865
+ backtest: dto.backtest,
47866
+ });
47867
+ if (dto.backtest) {
47868
+ return await StateBacktest.setState(dispatch, dto);
47869
+ }
47870
+ return await StateLive.setState(dispatch, dto);
47871
+ };
47872
+ }
47873
+ }
47874
+ /**
47875
+ * Global singleton instance of StateAdapter.
47876
+ * Provides unified state management for backtest and live trading.
47877
+ */
47878
+ const State = new StateAdapter();
47879
+ /**
47880
+ * Global singleton instance of StateLiveAdapter.
47881
+ * Provides live trading state storage with pluggable backends.
47882
+ */
47883
+ const StateLive = new StateLiveAdapter();
47884
+ /**
47885
+ * Global singleton instance of StateBacktestAdapter.
47886
+ * Provides backtest state storage with pluggable backends.
47887
+ */
47888
+ const StateBacktest = new StateBacktestAdapter();
47889
+
47890
+ const GET_SIGNAL_STATE_METHOD_NAME = "signal.getSignalState";
47891
+ const SET_SIGNAL_STATE_METHOD_NAME = "signal.setSignalState";
47063
47892
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
47064
47893
  const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
47065
47894
  /**
@@ -47135,6 +47964,777 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
47135
47964
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47136
47965
  return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
47137
47966
  }
47967
+ /**
47968
+ * Reads the state value scoped to the current active signal.
47969
+ *
47970
+ * Resolves the active pending signal automatically from execution context.
47971
+ * If no pending signal exists, logs a warning and returns the initialValue.
47972
+ *
47973
+ * Automatically detects backtest/live mode from execution context.
47974
+ *
47975
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
47976
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
47977
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
47978
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
47979
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
47980
+ *
47981
+ * @param dto.bucketName - State bucket name
47982
+ * @param dto.initialValue - Default value when no persisted state exists
47983
+ * @returns Promise resolving to current state value, or initialValue if no signal
47984
+ *
47985
+ * @deprecated Better use `createSignalState().getState` with codestyle native syntax
47986
+ *
47987
+ * @example
47988
+ * ```typescript
47989
+ * import { getSignalState } from "backtest-kit";
47990
+ *
47991
+ * const { peakPercent, minutesOpen } = await getSignalState({
47992
+ * bucketName: "trade",
47993
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
47994
+ * });
47995
+ * if (minutesOpen >= 15 && peakPercent < 0.3) {
47996
+ * await commitMarketClose(symbol); // capitulate — LLM thesis not confirmed
47997
+ * }
47998
+ * ```
47999
+ */
48000
+ async function getSignalState(dto) {
48001
+ const { bucketName, initialValue } = dto;
48002
+ backtest.loggerService.info(GET_SIGNAL_STATE_METHOD_NAME, { bucketName });
48003
+ if (!ExecutionContextService.hasContext()) {
48004
+ throw new Error("getSignalState requires an execution context");
48005
+ }
48006
+ if (!MethodContextService.hasContext()) {
48007
+ throw new Error("getSignalState requires a method context");
48008
+ }
48009
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48010
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48011
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48012
+ let signal;
48013
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48014
+ return await State.getState({
48015
+ signalId: signal.id,
48016
+ bucketName,
48017
+ initialValue,
48018
+ backtest: isBacktest,
48019
+ });
48020
+ }
48021
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48022
+ return await State.getState({
48023
+ signalId: signal.id,
48024
+ bucketName,
48025
+ initialValue,
48026
+ backtest: isBacktest,
48027
+ });
48028
+ }
48029
+ throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
48030
+ }
48031
+ /**
48032
+ * Updates the state value scoped to the current active signal.
48033
+ *
48034
+ * Resolves the active pending signal automatically from execution context.
48035
+ * If no pending signal exists, logs a warning and returns without writing.
48036
+ *
48037
+ * Automatically detects backtest/live mode from execution context.
48038
+ *
48039
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
48040
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
48041
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
48042
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
48043
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
48044
+ *
48045
+ * @param dto.bucketName - State bucket name
48046
+ * @param dto.initialValue - Default value when no persisted state exists
48047
+ * @param dto.dispatch - New value or updater function receiving current value
48048
+ * @returns Promise resolving to updated state value, or initialValue if no signal
48049
+ *
48050
+ * @deprecated Better use `createSignalState().setState` with codestyle native syntax
48051
+ *
48052
+ * @example
48053
+ * ```typescript
48054
+ * import { setSignalState } from "backtest-kit";
48055
+ *
48056
+ * await setSignalState(
48057
+ * dispatch: (s) => ({
48058
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
48059
+ * minutesOpen: s.minutesOpen + 1,
48060
+ * }),
48061
+ * {
48062
+ * bucketName: "trade",
48063
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
48064
+ * }
48065
+ * );
48066
+ * ```
48067
+ */
48068
+ async function setSignalState(dispatch, dto) {
48069
+ const { bucketName, initialValue } = dto;
48070
+ backtest.loggerService.info(SET_SIGNAL_STATE_METHOD_NAME, { bucketName });
48071
+ if (!ExecutionContextService.hasContext()) {
48072
+ throw new Error("setSignalState requires an execution context");
48073
+ }
48074
+ if (!MethodContextService.hasContext()) {
48075
+ throw new Error("setSignalState requires a method context");
48076
+ }
48077
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48078
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48079
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48080
+ let signal;
48081
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48082
+ return await State.setState(dispatch, {
48083
+ signalId: signal.id,
48084
+ bucketName,
48085
+ initialValue,
48086
+ backtest: isBacktest,
48087
+ });
48088
+ }
48089
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48090
+ return await State.setState(dispatch, {
48091
+ signalId: signal.id,
48092
+ bucketName,
48093
+ initialValue,
48094
+ backtest: isBacktest,
48095
+ });
48096
+ }
48097
+ throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
48098
+ }
48099
+
48100
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
48101
+ const parts = [symbol, strategyName, exchangeName];
48102
+ if (frameName)
48103
+ parts.push(frameName);
48104
+ parts.push(backtest ? "backtest" : "live");
48105
+ return parts.join(":");
48106
+ };
48107
+ const SESSION_LOCAL_INSTANCE_METHOD_NAME_GET = "SessionLocalInstance.getData";
48108
+ const SESSION_LOCAL_INSTANCE_METHOD_NAME_SET = "SessionLocalInstance.setData";
48109
+ const SESSION_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT = "SessionPersistInstance.waitForInit";
48110
+ const SESSION_PERSIST_INSTANCE_METHOD_NAME_GET = "SessionPersistInstance.getData";
48111
+ const SESSION_PERSIST_INSTANCE_METHOD_NAME_SET = "SessionPersistInstance.setData";
48112
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "SessionBacktestAdapter.dispose";
48113
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET = "SessionBacktestAdapter.getData";
48114
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET = "SessionBacktestAdapter.setData";
48115
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "SessionBacktestAdapter.useLocal";
48116
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "SessionBacktestAdapter.usePersist";
48117
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "SessionBacktestAdapter.useDummy";
48118
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_SESSION_ADAPTER = "SessionBacktestAdapter.useSessionAdapter";
48119
+ const SESSION_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "SessionBacktestAdapter.clear";
48120
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "SessionLiveAdapter.dispose";
48121
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_GET = "SessionLiveAdapter.getData";
48122
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_SET = "SessionLiveAdapter.setData";
48123
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "SessionLiveAdapter.useLocal";
48124
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "SessionLiveAdapter.usePersist";
48125
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "SessionLiveAdapter.useDummy";
48126
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_USE_SESSION_ADAPTER = "SessionLiveAdapter.useSessionAdapter";
48127
+ const SESSION_LIVE_ADAPTER_METHOD_NAME_CLEAR = "SessionLiveAdapter.clear";
48128
+ const SESSION_ADAPTER_METHOD_NAME_GET = "SessionAdapter.getData";
48129
+ const SESSION_ADAPTER_METHOD_NAME_SET = "SessionAdapter.setData";
48130
+ /**
48131
+ * In-process session instance backed by a plain object reference.
48132
+ * All data lives in process memory only — no disk persistence.
48133
+ *
48134
+ * Features:
48135
+ * - Mutable in-memory session data
48136
+ * - Scoped per (symbol, strategyName, exchangeName, frameName) tuple
48137
+ *
48138
+ * Use for backtesting and unit tests where persistence between runs is not needed.
48139
+ */
48140
+ class SessionLocalInstance {
48141
+ constructor(symbol, strategyName, exchangeName, frameName, backtest$1) {
48142
+ this.symbol = symbol;
48143
+ this.strategyName = strategyName;
48144
+ this.exchangeName = exchangeName;
48145
+ this.frameName = frameName;
48146
+ this.backtest = backtest$1;
48147
+ this._data = null;
48148
+ /**
48149
+ * Initializes _data to null — local session needs no async setup.
48150
+ * @returns Promise that resolves immediately
48151
+ */
48152
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
48153
+ this._data = null;
48154
+ });
48155
+ /**
48156
+ * Read the current in-memory session value.
48157
+ * @returns Current session value, or null if not set
48158
+ */
48159
+ this.getData = async () => {
48160
+ backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_GET, {
48161
+ strategyName: this.strategyName,
48162
+ exchangeName: this.exchangeName,
48163
+ frameName: this.frameName,
48164
+ });
48165
+ return this._data;
48166
+ };
48167
+ /**
48168
+ * Update the in-memory session value.
48169
+ * @param value - New value or null to clear
48170
+ */
48171
+ this.setData = async (value) => {
48172
+ backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_SET, {
48173
+ strategyName: this.strategyName,
48174
+ exchangeName: this.exchangeName,
48175
+ frameName: this.frameName,
48176
+ });
48177
+ this._data = value;
48178
+ };
48179
+ }
48180
+ /** Releases resources held by this instance. */
48181
+ async dispose() {
48182
+ backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
48183
+ strategyName: this.strategyName,
48184
+ exchangeName: this.exchangeName,
48185
+ frameName: this.frameName,
48186
+ });
48187
+ }
48188
+ }
48189
+ /**
48190
+ * No-op session instance that discards all writes.
48191
+ * Used for disabling session storage in tests or dry-run scenarios.
48192
+ *
48193
+ * Useful when replaying historical candles without needing to accumulate
48194
+ * cross-candle session state — getData always returns null.
48195
+ */
48196
+ class SessionDummyInstance {
48197
+ constructor(symbol, strategyName, exchangeName, frameName, backtest) {
48198
+ this.symbol = symbol;
48199
+ this.strategyName = strategyName;
48200
+ this.exchangeName = exchangeName;
48201
+ this.frameName = frameName;
48202
+ this.backtest = backtest;
48203
+ /**
48204
+ * No-op initialization.
48205
+ * @returns Promise that resolves immediately
48206
+ */
48207
+ this.waitForInit = functoolsKit.singleshot(async (_initial) => {
48208
+ });
48209
+ /**
48210
+ * No-op read — always returns null.
48211
+ * @returns null
48212
+ */
48213
+ this.getData = async () => {
48214
+ return null;
48215
+ };
48216
+ /**
48217
+ * No-op write — discards the value.
48218
+ */
48219
+ this.setData = async (_value) => {
48220
+ };
48221
+ }
48222
+ /** No-op. */
48223
+ async dispose() {
48224
+ }
48225
+ }
48226
+ /**
48227
+ * File-system backed session instance.
48228
+ * Data is persisted atomically to disk via PersistSessionAdapter.
48229
+ * Session is restored from disk on waitForInit.
48230
+ *
48231
+ * Features:
48232
+ * - Crash-safe atomic file writes
48233
+ * - Scoped per (symbol, strategyName, exchangeName, frameName) tuple
48234
+ *
48235
+ * Use in live trading to survive process restarts mid-session.
48236
+ */
48237
+ class SessionPersistInstance {
48238
+ constructor(symbol, strategyName, exchangeName, frameName, backtest$1) {
48239
+ this.symbol = symbol;
48240
+ this.strategyName = strategyName;
48241
+ this.exchangeName = exchangeName;
48242
+ this.frameName = frameName;
48243
+ this.backtest = backtest$1;
48244
+ this._data = null;
48245
+ /**
48246
+ * Initialize persistence storage and restore session from disk.
48247
+ * @param initial - Whether this is the first initialization
48248
+ */
48249
+ this.waitForInit = functoolsKit.singleshot(async (initial) => {
48250
+ backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_WAIT_FOR_INIT, {
48251
+ strategyName: this.strategyName,
48252
+ exchangeName: this.exchangeName,
48253
+ frameName: this.frameName,
48254
+ initial,
48255
+ });
48256
+ await PersistSessionAdapter.waitForInit(this.strategyName, this.exchangeName, this.frameName, initial);
48257
+ const data = await PersistSessionAdapter.readSessionData(this.strategyName, this.exchangeName, this.frameName);
48258
+ if (data) {
48259
+ this._data = data.data;
48260
+ return;
48261
+ }
48262
+ this._data = null;
48263
+ });
48264
+ /**
48265
+ * Read the current persisted session value.
48266
+ * @returns Current session value, or null if not set
48267
+ */
48268
+ this.getData = async () => {
48269
+ backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_GET, {
48270
+ strategyName: this.strategyName,
48271
+ exchangeName: this.exchangeName,
48272
+ frameName: this.frameName,
48273
+ });
48274
+ return this._data;
48275
+ };
48276
+ /**
48277
+ * Update session value and persist to disk atomically.
48278
+ * @param value - New value or null to clear
48279
+ */
48280
+ this.setData = async (value) => {
48281
+ backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_SET, {
48282
+ strategyName: this.strategyName,
48283
+ exchangeName: this.exchangeName,
48284
+ frameName: this.frameName,
48285
+ });
48286
+ this._data = value;
48287
+ const id = CREATE_KEY_FN$5(this.symbol, this.strategyName, this.exchangeName, this.frameName, this.backtest);
48288
+ await PersistSessionAdapter.writeSessionData({ id, data: value }, this.strategyName, this.exchangeName, this.frameName);
48289
+ };
48290
+ }
48291
+ /** Releases resources held by this instance. */
48292
+ async dispose() {
48293
+ backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
48294
+ strategyName: this.strategyName,
48295
+ exchangeName: this.exchangeName,
48296
+ frameName: this.frameName,
48297
+ });
48298
+ await PersistSessionAdapter.dispose(this.strategyName, this.exchangeName, this.frameName);
48299
+ }
48300
+ }
48301
+ /**
48302
+ * Backtest session adapter with pluggable storage backend.
48303
+ *
48304
+ * Features:
48305
+ * - Adapter pattern for swappable session instance implementations
48306
+ * - Default backend: SessionLocalInstance (in-memory, no disk persistence)
48307
+ * - Alternative backends: SessionPersistInstance, SessionDummyInstance
48308
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useSessionAdapter()
48309
+ * - Memoized instances per (symbol, strategyName, exchangeName, frameName) tuple
48310
+ */
48311
+ class SessionBacktestAdapter {
48312
+ constructor() {
48313
+ this.SessionFactory = SessionLocalInstance;
48314
+ this.getInstance = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.SessionFactory, [symbol, strategyName, exchangeName, frameName, backtest]));
48315
+ /**
48316
+ * Read the current session value for a backtest run.
48317
+ * @param symbol - Trading pair symbol
48318
+ * @param context.strategyName - Strategy identifier
48319
+ * @param context.exchangeName - Exchange identifier
48320
+ * @param context.frameName - Frame identifier
48321
+ * @returns Current session value, or null if not set
48322
+ */
48323
+ this.getData = async (symbol, context) => {
48324
+ backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET, {
48325
+ strategyName: context.strategyName,
48326
+ exchangeName: context.exchangeName,
48327
+ frameName: context.frameName,
48328
+ });
48329
+ const key = CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, true);
48330
+ const isInitial = !this.getInstance.has(key);
48331
+ const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
48332
+ await instance.waitForInit(isInitial);
48333
+ return await instance.getData();
48334
+ };
48335
+ /**
48336
+ * Update the session value for a backtest run.
48337
+ * @param symbol - Trading pair symbol
48338
+ * @param value - New value or null to clear
48339
+ * @param context.strategyName - Strategy identifier
48340
+ * @param context.exchangeName - Exchange identifier
48341
+ * @param context.frameName - Frame identifier
48342
+ */
48343
+ this.setData = async (symbol, value, context) => {
48344
+ backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET, {
48345
+ strategyName: context.strategyName,
48346
+ exchangeName: context.exchangeName,
48347
+ frameName: context.frameName,
48348
+ });
48349
+ const key = CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, true);
48350
+ const isInitial = !this.getInstance.has(key);
48351
+ const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
48352
+ await instance.waitForInit(isInitial);
48353
+ return await instance.setData(value);
48354
+ };
48355
+ /**
48356
+ * Switches to in-memory adapter (default).
48357
+ * All data lives in process memory only.
48358
+ */
48359
+ this.useLocal = () => {
48360
+ backtest.loggerService.info(SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
48361
+ this.SessionFactory = SessionLocalInstance;
48362
+ };
48363
+ /**
48364
+ * Switches to file-system backed adapter.
48365
+ * Data is persisted to disk via PersistSessionAdapter.
48366
+ */
48367
+ this.usePersist = () => {
48368
+ backtest.loggerService.info(SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
48369
+ this.SessionFactory = SessionPersistInstance;
48370
+ };
48371
+ /**
48372
+ * Switches to dummy adapter that discards all writes.
48373
+ */
48374
+ this.useDummy = () => {
48375
+ backtest.loggerService.info(SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
48376
+ this.SessionFactory = SessionDummyInstance;
48377
+ };
48378
+ /**
48379
+ * Switches to a custom session adapter implementation.
48380
+ * @param Ctor - Constructor for the custom session instance
48381
+ */
48382
+ this.useSessionAdapter = (Ctor) => {
48383
+ backtest.loggerService.info(SESSION_BACKTEST_ADAPTER_METHOD_NAME_USE_SESSION_ADAPTER);
48384
+ this.SessionFactory = Ctor;
48385
+ };
48386
+ /**
48387
+ * Clears the memoized instance cache.
48388
+ * Call this when process.cwd() changes between strategy iterations
48389
+ * so new instances are created with the updated base path.
48390
+ */
48391
+ this.clear = () => {
48392
+ backtest.loggerService.info(SESSION_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
48393
+ this.getInstance.clear();
48394
+ };
48395
+ }
48396
+ }
48397
+ /**
48398
+ * Live trading session adapter with pluggable storage backend.
48399
+ *
48400
+ * Features:
48401
+ * - Adapter pattern for swappable session instance implementations
48402
+ * - Default backend: SessionPersistInstance (file-system backed, survives restarts)
48403
+ * - Alternative backends: SessionLocalInstance, SessionDummyInstance
48404
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useSessionAdapter()
48405
+ * - Memoized instances per (symbol, strategyName, exchangeName, frameName) tuple
48406
+ */
48407
+ class SessionLiveAdapter {
48408
+ constructor() {
48409
+ this.SessionFactory = SessionPersistInstance;
48410
+ this.getInstance = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.SessionFactory, [symbol, strategyName, exchangeName, frameName, backtest]));
48411
+ /**
48412
+ * Read the current session value for a live run.
48413
+ * @param symbol - Trading pair symbol
48414
+ * @param context.strategyName - Strategy identifier
48415
+ * @param context.exchangeName - Exchange identifier
48416
+ * @param context.frameName - Frame identifier
48417
+ * @returns Current session value, or null if not set
48418
+ */
48419
+ this.getData = async (symbol, context) => {
48420
+ backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_GET, {
48421
+ strategyName: context.strategyName,
48422
+ exchangeName: context.exchangeName,
48423
+ frameName: context.frameName,
48424
+ });
48425
+ const key = CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, false);
48426
+ const isInitial = !this.getInstance.has(key);
48427
+ const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
48428
+ await instance.waitForInit(isInitial);
48429
+ return await instance.getData();
48430
+ };
48431
+ /**
48432
+ * Update the session value for a live run.
48433
+ * @param symbol - Trading pair symbol
48434
+ * @param value - New value or null to clear
48435
+ * @param context.strategyName - Strategy identifier
48436
+ * @param context.exchangeName - Exchange identifier
48437
+ * @param context.frameName - Frame identifier
48438
+ */
48439
+ this.setData = async (symbol, value, context) => {
48440
+ backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_SET, {
48441
+ strategyName: context.strategyName,
48442
+ exchangeName: context.exchangeName,
48443
+ frameName: context.frameName,
48444
+ });
48445
+ const key = CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, false);
48446
+ const isInitial = !this.getInstance.has(key);
48447
+ const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
48448
+ await instance.waitForInit(isInitial);
48449
+ return await instance.setData(value);
48450
+ };
48451
+ /**
48452
+ * Switches to in-memory adapter.
48453
+ * All data lives in process memory only.
48454
+ */
48455
+ this.useLocal = () => {
48456
+ backtest.loggerService.info(SESSION_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
48457
+ this.SessionFactory = SessionLocalInstance;
48458
+ };
48459
+ /**
48460
+ * Switches to file-system backed adapter (default).
48461
+ * Data is persisted to disk via PersistSessionAdapter.
48462
+ */
48463
+ this.usePersist = () => {
48464
+ backtest.loggerService.info(SESSION_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
48465
+ this.SessionFactory = SessionPersistInstance;
48466
+ };
48467
+ /**
48468
+ * Switches to dummy adapter that discards all writes.
48469
+ */
48470
+ this.useDummy = () => {
48471
+ backtest.loggerService.info(SESSION_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
48472
+ this.SessionFactory = SessionDummyInstance;
48473
+ };
48474
+ /**
48475
+ * Switches to a custom session adapter implementation.
48476
+ * @param Ctor - Constructor for the custom session instance
48477
+ */
48478
+ this.useSessionAdapter = (Ctor) => {
48479
+ backtest.loggerService.info(SESSION_LIVE_ADAPTER_METHOD_NAME_USE_SESSION_ADAPTER);
48480
+ this.SessionFactory = Ctor;
48481
+ };
48482
+ /**
48483
+ * Clears the memoized instance cache.
48484
+ * Call this when process.cwd() changes between strategy iterations
48485
+ * so new instances are created with the updated base path.
48486
+ */
48487
+ this.clear = () => {
48488
+ backtest.loggerService.info(SESSION_LIVE_ADAPTER_METHOD_NAME_CLEAR);
48489
+ this.getInstance.clear();
48490
+ };
48491
+ }
48492
+ }
48493
+ /**
48494
+ * Main session adapter that manages both backtest and live session storage.
48495
+ *
48496
+ * Features:
48497
+ * - Routes all operations to SessionBacktest or SessionLive based on the backtest flag
48498
+ */
48499
+ class SessionAdapter {
48500
+ constructor() {
48501
+ /**
48502
+ * Read the current session value for a signal.
48503
+ * Routes to SessionBacktest or SessionLive based on backtest.
48504
+ * @param symbol - Trading pair symbol
48505
+ * @param context.strategyName - Strategy identifier
48506
+ * @param context.exchangeName - Exchange identifier
48507
+ * @param context.frameName - Frame identifier
48508
+ * @param backtest - Flag indicating if the context is backtest or live
48509
+ * @returns Current session value, or null if not set
48510
+ */
48511
+ this.getData = async (symbol, context, backtest$1) => {
48512
+ backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_GET, {
48513
+ strategyName: context.strategyName,
48514
+ exchangeName: context.exchangeName,
48515
+ frameName: context.frameName,
48516
+ backtest: backtest$1,
48517
+ });
48518
+ if (backtest$1) {
48519
+ return await SessionBacktest.getData(symbol, context);
48520
+ }
48521
+ return await SessionLive.getData(symbol, context);
48522
+ };
48523
+ /**
48524
+ * Update the session value for a signal.
48525
+ * Routes to SessionBacktest or SessionLive based on backtest.
48526
+ * @param symbol - Trading pair symbol
48527
+ * @param value - New value or null to clear
48528
+ * @param context.strategyName - Strategy identifier
48529
+ * @param context.exchangeName - Exchange identifier
48530
+ * @param context.frameName - Frame identifier
48531
+ * @param backtest - Flag indicating if the context is backtest or live
48532
+ */
48533
+ this.setData = async (symbol, value, context, backtest$1) => {
48534
+ backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_SET, {
48535
+ strategyName: context.strategyName,
48536
+ exchangeName: context.exchangeName,
48537
+ frameName: context.frameName,
48538
+ backtest: backtest$1,
48539
+ });
48540
+ if (backtest$1) {
48541
+ return await SessionBacktest.setData(symbol, value, context);
48542
+ }
48543
+ return await SessionLive.setData(symbol, value, context);
48544
+ };
48545
+ }
48546
+ }
48547
+ /**
48548
+ * Global singleton instance of SessionAdapter.
48549
+ * Provides unified session management for backtest and live trading.
48550
+ */
48551
+ const Session = new SessionAdapter();
48552
+ /**
48553
+ * Global singleton instance of SessionLiveAdapter.
48554
+ * Provides live trading session storage with pluggable backends.
48555
+ */
48556
+ const SessionLive = new SessionLiveAdapter();
48557
+ /**
48558
+ * Global singleton instance of SessionBacktestAdapter.
48559
+ * Provides backtest session storage with pluggable backends.
48560
+ */
48561
+ const SessionBacktest = new SessionBacktestAdapter();
48562
+
48563
+ const GET_SESSION_METHOD_NAME = "session.getSession";
48564
+ const SET_SESSION_METHOD_NAME = "session.setSession";
48565
+ /**
48566
+ * Reads the session value scoped to the current (symbol, strategy, exchange, frame) context.
48567
+ *
48568
+ * Session data persists across candles within a single run and can survive process
48569
+ * restarts in live mode — useful for caching LLM inference results, intermediate
48570
+ * indicator state, or any cross-candle accumulator that is not tied to a specific signal.
48571
+ *
48572
+ * Automatically detects backtest/live mode from execution context.
48573
+ *
48574
+ * @param symbol - Trading pair symbol
48575
+ * @returns Promise resolving to current session value, or null if not set
48576
+ *
48577
+ * @example
48578
+ * ```typescript
48579
+ * import { getSession } from "backtest-kit";
48580
+ *
48581
+ * const session = await getSession<{ lastLlmSignal: string }>("BTCUSDT");
48582
+ * if (session?.lastLlmSignal === "buy") {
48583
+ * // reuse cached LLM result instead of calling the model again
48584
+ * }
48585
+ * ```
48586
+ */
48587
+ async function getSessionData(symbol) {
48588
+ backtest.loggerService.info(GET_SESSION_METHOD_NAME, { symbol });
48589
+ if (!ExecutionContextService.hasContext()) {
48590
+ throw new Error("getSession requires an execution context");
48591
+ }
48592
+ if (!MethodContextService.hasContext()) {
48593
+ throw new Error("getSession requires a method context");
48594
+ }
48595
+ const { backtest: isBacktest } = backtest.executionContextService.context;
48596
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48597
+ return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest);
48598
+ }
48599
+ /**
48600
+ * Writes a session value scoped to the current (symbol, strategy, exchange, frame) context.
48601
+ *
48602
+ * Session data persists across candles within a single run and can survive process
48603
+ * restarts in live mode — useful for caching LLM inference results, intermediate
48604
+ * indicator state, or any cross-candle accumulator that is not tied to a specific signal.
48605
+ *
48606
+ * Pass null to clear the session.
48607
+ *
48608
+ * Automatically detects backtest/live mode from execution context.
48609
+ *
48610
+ * @param symbol - Trading pair symbol
48611
+ * @param value - New value or null to clear
48612
+ * @returns Promise that resolves when the session has been written
48613
+ *
48614
+ * @example
48615
+ * ```typescript
48616
+ * import { setSession } from "backtest-kit";
48617
+ *
48618
+ * await setSession("BTCUSDT", { lastLlmSignal: "buy" });
48619
+ * ```
48620
+ */
48621
+ async function setSessionData(symbol, value) {
48622
+ backtest.loggerService.info(SET_SESSION_METHOD_NAME, { symbol });
48623
+ if (!ExecutionContextService.hasContext()) {
48624
+ throw new Error("setSession requires an execution context");
48625
+ }
48626
+ if (!MethodContextService.hasContext()) {
48627
+ throw new Error("setSession requires a method context");
48628
+ }
48629
+ const { backtest: isBacktest } = backtest.executionContextService.context;
48630
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48631
+ await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest);
48632
+ }
48633
+
48634
+ const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
48635
+ const CREATE_SET_STATE_FN = (params) => async (dispatch) => {
48636
+ if (!ExecutionContextService.hasContext()) {
48637
+ throw new Error("createSignalState requires an execution context");
48638
+ }
48639
+ if (!MethodContextService.hasContext()) {
48640
+ throw new Error("createSignalState requires a method context");
48641
+ }
48642
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48643
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48644
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48645
+ let signal;
48646
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48647
+ return await State.setState(dispatch, {
48648
+ backtest: isBacktest,
48649
+ bucketName: params.bucketName,
48650
+ initialValue: params.initialValue,
48651
+ signalId: signal.id,
48652
+ });
48653
+ }
48654
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48655
+ return await State.setState(dispatch, {
48656
+ backtest: isBacktest,
48657
+ bucketName: params.bucketName,
48658
+ initialValue: params.initialValue,
48659
+ signalId: signal.id,
48660
+ });
48661
+ }
48662
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
48663
+ };
48664
+ const CREATE_GET_STATE_FN = (params) => async () => {
48665
+ if (!ExecutionContextService.hasContext()) {
48666
+ throw new Error("createSignalState requires an execution context");
48667
+ }
48668
+ if (!MethodContextService.hasContext()) {
48669
+ throw new Error("createSignalState requires a method context");
48670
+ }
48671
+ const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48672
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48673
+ const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48674
+ let signal;
48675
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48676
+ return await State.getState({
48677
+ backtest: isBacktest,
48678
+ bucketName: params.bucketName,
48679
+ initialValue: params.initialValue,
48680
+ signalId: signal.id,
48681
+ });
48682
+ }
48683
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
48684
+ return await State.getState({
48685
+ backtest: isBacktest,
48686
+ bucketName: params.bucketName,
48687
+ initialValue: params.initialValue,
48688
+ signalId: signal.id,
48689
+ });
48690
+ }
48691
+ throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
48692
+ };
48693
+ /**
48694
+ * Creates a bound [getState, setState] tuple scoped to a bucket and initial value.
48695
+ *
48696
+ * Both returned functions resolve the active pending or scheduled signal and the
48697
+ * backtest/live flag automatically from execution context — no signalId argument required.
48698
+ *
48699
+ * Automatically detects backtest/live mode from execution context.
48700
+ *
48701
+ * Intended for LLM-driven capitulation strategies that accumulate per-trade
48702
+ * metrics (e.g. peakPercent, minutesOpen) across onActivePing ticks.
48703
+ * Profitable trades endure -0.5–2.5% drawdown and reach peak 2–3%+.
48704
+ * SL trades show peak < 0.15% (Feb08, Feb13) or never go positive (Feb25).
48705
+ * Rule: if minutesOpen >= N and peakPercent < threshold (e.g. 0.3%) — exit.
48706
+ *
48707
+ * @param params.bucketName - Logical namespace for grouping state buckets within a signal
48708
+ * @param params.initialValue - Default value when no persisted state exists
48709
+ * @returns Tuple [getState, setState] bound to the bucket and initial value
48710
+ *
48711
+ * @example
48712
+ * ```typescript
48713
+ * import { createSignalState } from "backtest-kit";
48714
+ *
48715
+ * const [getTradeState, setTradeState] = createSignalState({
48716
+ * bucketName: "trade",
48717
+ * initialValue: { peakPercent: 0, minutesOpen: 0 },
48718
+ * });
48719
+ *
48720
+ * // in onActivePing:
48721
+ * await setTradeState((s) => ({
48722
+ * peakPercent: Math.max(s.peakPercent, currentUnrealisedPercent),
48723
+ * minutesOpen: s.minutesOpen + 1,
48724
+ * }));
48725
+ * const { peakPercent, minutesOpen } = await getTradeState();
48726
+ * if (minutesOpen >= 15 && peakPercent < 0.3) await commitMarketClose(symbol);
48727
+ * ```
48728
+ */
48729
+ function createSignalState(params) {
48730
+ backtest.loggerService.info(CREATE_SIGNAL_STATE_METHOD_NAME, {
48731
+ bucketName: params.bucketName,
48732
+ });
48733
+ return [
48734
+ CREATE_GET_STATE_FN(params),
48735
+ CREATE_SET_STATE_FN(params),
48736
+ ];
48737
+ }
47138
48738
 
47139
48739
  const DEFAULT_BM25_K1 = 1.5;
47140
48740
  const DEFAULT_BM25_B = 0.75;
@@ -47221,7 +48821,7 @@ const createSearchIndex = () => {
47221
48821
  return { upsert, remove, list, search, read };
47222
48822
  };
47223
48823
 
47224
- const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}-${bucketName}`;
48824
+ const CREATE_KEY_FN$4 = (signalId, bucketName) => `${signalId}_${bucketName}`;
47225
48825
  const LIST_MEMORY_FN = ({ id, content }) => ({
47226
48826
  memoryId: id,
47227
48827
  content: content,
@@ -47242,18 +48842,35 @@ const MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ = "MemoryPersistInstance.readMemo
47242
48842
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH = "MemoryPersistInstance.searchMemory";
47243
48843
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST = "MemoryPersistInstance.listMemory";
47244
48844
  const MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE = "MemoryPersistInstance.removeMemory";
48845
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE = "MemoryBacktestAdapter.dispose";
48846
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE = "MemoryBacktestAdapter.writeMemory";
48847
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH = "MemoryBacktestAdapter.searchMemory";
48848
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST = "MemoryBacktestAdapter.listMemory";
48849
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE = "MemoryBacktestAdapter.removeMemory";
48850
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ = "MemoryBacktestAdapter.readMemory";
48851
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryBacktestAdapter.useLocal";
48852
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryBacktestAdapter.usePersist";
48853
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryBacktestAdapter.useDummy";
48854
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryBacktestAdapter.useMemoryAdapter";
48855
+ const MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "MemoryBacktestAdapter.clear";
48856
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE = "MemoryLiveAdapter.dispose";
48857
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE = "MemoryLiveAdapter.writeMemory";
48858
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH = "MemoryLiveAdapter.searchMemory";
48859
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST = "MemoryLiveAdapter.listMemory";
48860
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE = "MemoryLiveAdapter.removeMemory";
48861
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_READ = "MemoryLiveAdapter.readMemory";
48862
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryLiveAdapter.useLocal";
48863
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryLiveAdapter.usePersist";
48864
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryLiveAdapter.useDummy";
48865
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "MemoryLiveAdapter.useMemoryAdapter";
48866
+ const MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR = "MemoryLiveAdapter.clear";
47245
48867
  const MEMORY_ADAPTER_METHOD_NAME_ENABLE = "MemoryAdapter.enable";
47246
48868
  const MEMORY_ADAPTER_METHOD_NAME_DISABLE = "MemoryAdapter.disable";
47247
- const MEMORY_ADAPTER_METHOD_NAME_DISPOSE = "MemoryAdapter.dispose";
47248
48869
  const MEMORY_ADAPTER_METHOD_NAME_WRITE = "MemoryAdapter.writeMemory";
47249
48870
  const MEMORY_ADAPTER_METHOD_NAME_SEARCH = "MemoryAdapter.searchMemory";
47250
48871
  const MEMORY_ADAPTER_METHOD_NAME_LIST = "MemoryAdapter.listMemory";
47251
48872
  const MEMORY_ADAPTER_METHOD_NAME_REMOVE = "MemoryAdapter.removeMemory";
47252
48873
  const MEMORY_ADAPTER_METHOD_NAME_READ = "MemoryAdapter.readMemory";
47253
- const MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL = "MemoryAdapter.useLocal";
47254
- const MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST = "MemoryAdapter.usePersist";
47255
- const MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY = "MemoryAdapter.useDummy";
47256
- const MEMORY_ADAPTER_METHOD_NAME_CLEAR = "MemoryAdapter.clear";
47257
48874
  /**
47258
48875
  * In-memory BM25 search index backed instance.
47259
48876
  * All data lives in the process memory only - no disk persistence.
@@ -47278,7 +48895,7 @@ class MemoryLocalInstance {
47278
48895
  * Write a value into the BM25 index.
47279
48896
  * @param memoryId - Unique entry identifier
47280
48897
  * @param value - Value to store and index
47281
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
48898
+ * @param description - BM25 index string
47282
48899
  */
47283
48900
  async writeMemory(memoryId, value, description) {
47284
48901
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
@@ -47349,7 +48966,7 @@ class MemoryLocalInstance {
47349
48966
  }
47350
48967
  /** Releases resources held by this instance. */
47351
48968
  dispose() {
47352
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
48969
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_DISPOSE, {
47353
48970
  signalId: this.signalId,
47354
48971
  bucketName: this.bucketName,
47355
48972
  });
@@ -47398,7 +49015,7 @@ class MemoryPersistInstance {
47398
49015
  * Write a value to disk and update the BM25 index.
47399
49016
  * @param memoryId - Unique entry identifier
47400
49017
  * @param value - Value to persist and index
47401
- * @param index - Optional BM25 index string; defaults to JSON.stringify(value)
49018
+ * @param index - BM25 index string; defaults to JSON.stringify(value)
47402
49019
  */
47403
49020
  async writeMemory(memoryId, value, index = JSON.stringify(value)) {
47404
49021
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
@@ -47472,7 +49089,7 @@ class MemoryPersistInstance {
47472
49089
  }
47473
49090
  /** Releases resources held by this instance. */
47474
49091
  dispose() {
47475
- backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_DISPOSE, {
49092
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_DISPOSE, {
47476
49093
  signalId: this.signalId,
47477
49094
  bucketName: this.bucketName,
47478
49095
  });
@@ -47532,48 +49149,377 @@ class MemoryDummyInstance {
47532
49149
  }
47533
49150
  }
47534
49151
  /**
47535
- * Facade for memory instances scoped per (signalId, bucketName).
47536
- * Manages lazy initialization and instance lifecycle.
49152
+ * Backtest memory adapter with pluggable storage backend.
47537
49153
  *
47538
49154
  * Features:
47539
- * - Memoized instances per (signalId, bucketName) pair
47540
- * - Swappable backend via useLocal(), usePersist(), useDummy()
47541
- * - Default backend: MemoryPersistInstance (in-memory BM25 + persist storage)
49155
+ * - Adapter pattern for swappable memory instance implementations
49156
+ * - Default backend: MemoryLocalInstance (in-memory BM25, no disk persistence)
49157
+ * - Alternative backends: MemoryPersistInstance, MemoryDummyInstance
49158
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
49159
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
49160
+ *
49161
+ * Use this adapter for backtest memory storage.
47542
49162
  */
47543
- class MemoryAdapter {
49163
+ class MemoryBacktestAdapter {
49164
+ constructor() {
49165
+ this.MemoryFactory = MemoryLocalInstance;
49166
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
49167
+ /**
49168
+ * Disposes all memoized instances for the given signalId.
49169
+ * Called by MemoryAdapter when a signal is cancelled or closed.
49170
+ * @param signalId - Signal identifier to dispose
49171
+ */
49172
+ this.disposeSignal = (signalId) => {
49173
+ const prefix = CREATE_KEY_FN$4(signalId, "");
49174
+ for (const key of this.getInstance.keys()) {
49175
+ if (key.startsWith(prefix)) {
49176
+ const instance = this.getInstance.get(key);
49177
+ instance && instance.dispose();
49178
+ this.getInstance.clear(key);
49179
+ }
49180
+ }
49181
+ };
49182
+ /**
49183
+ * Write a value to memory.
49184
+ * @param dto.memoryId - Unique entry identifier
49185
+ * @param dto.value - Value to store
49186
+ * @param dto.signalId - Signal identifier
49187
+ * @param dto.bucketName - Bucket name
49188
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
49189
+ */
49190
+ this.writeMemory = async (dto) => {
49191
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
49192
+ signalId: dto.signalId,
49193
+ bucketName: dto.bucketName,
49194
+ memoryId: dto.memoryId,
49195
+ });
49196
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49197
+ const isInitial = !this.getInstance.has(key);
49198
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49199
+ await instance.waitForInit(isInitial);
49200
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
49201
+ };
49202
+ /**
49203
+ * Search memory using BM25 full-text scoring.
49204
+ * @param dto.query - Search query string
49205
+ * @param dto.signalId - Signal identifier
49206
+ * @param dto.bucketName - Bucket name
49207
+ * @returns Matching entries sorted by relevance score
49208
+ */
49209
+ this.searchMemory = async (dto) => {
49210
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_SEARCH, {
49211
+ signalId: dto.signalId,
49212
+ bucketName: dto.bucketName,
49213
+ query: dto.query,
49214
+ });
49215
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49216
+ const isInitial = !this.getInstance.has(key);
49217
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49218
+ await instance.waitForInit(isInitial);
49219
+ return await instance.searchMemory(dto.query, dto.settings);
49220
+ };
49221
+ /**
49222
+ * List all entries in memory.
49223
+ * @param dto.signalId - Signal identifier
49224
+ * @param dto.bucketName - Bucket name
49225
+ * @returns Array of all stored entries
49226
+ */
49227
+ this.listMemory = async (dto) => {
49228
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_LIST, {
49229
+ signalId: dto.signalId,
49230
+ bucketName: dto.bucketName,
49231
+ });
49232
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49233
+ const isInitial = !this.getInstance.has(key);
49234
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49235
+ await instance.waitForInit(isInitial);
49236
+ return await instance.listMemory();
49237
+ };
49238
+ /**
49239
+ * Remove an entry from memory.
49240
+ * @param dto.memoryId - Unique entry identifier
49241
+ * @param dto.signalId - Signal identifier
49242
+ * @param dto.bucketName - Bucket name
49243
+ */
49244
+ this.removeMemory = async (dto) => {
49245
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
49246
+ signalId: dto.signalId,
49247
+ bucketName: dto.bucketName,
49248
+ memoryId: dto.memoryId,
49249
+ });
49250
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49251
+ const isInitial = !this.getInstance.has(key);
49252
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49253
+ await instance.waitForInit(isInitial);
49254
+ return await instance.removeMemory(dto.memoryId);
49255
+ };
49256
+ /**
49257
+ * Read a single entry from memory.
49258
+ * @param dto.memoryId - Unique entry identifier
49259
+ * @param dto.signalId - Signal identifier
49260
+ * @param dto.bucketName - Bucket name
49261
+ * @returns Entry value
49262
+ * @throws Error if entry not found
49263
+ */
49264
+ this.readMemory = async (dto) => {
49265
+ backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_READ, {
49266
+ signalId: dto.signalId,
49267
+ bucketName: dto.bucketName,
49268
+ memoryId: dto.memoryId,
49269
+ });
49270
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49271
+ const isInitial = !this.getInstance.has(key);
49272
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49273
+ await instance.waitForInit(isInitial);
49274
+ return await instance.readMemory(dto.memoryId);
49275
+ };
49276
+ /**
49277
+ * Switches to in-memory BM25 adapter (default).
49278
+ * All data lives in process memory only.
49279
+ */
49280
+ this.useLocal = () => {
49281
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_LOCAL);
49282
+ this.MemoryFactory = MemoryLocalInstance;
49283
+ };
49284
+ /**
49285
+ * Switches to file-system backed adapter.
49286
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
49287
+ */
49288
+ this.usePersist = () => {
49289
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
49290
+ this.MemoryFactory = MemoryPersistInstance;
49291
+ };
49292
+ /**
49293
+ * Switches to dummy adapter that discards all writes.
49294
+ */
49295
+ this.useDummy = () => {
49296
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
49297
+ this.MemoryFactory = MemoryDummyInstance;
49298
+ };
49299
+ /**
49300
+ * Switches to a custom memory adapter implementation.
49301
+ * @param Ctor - Constructor for the custom memory instance
49302
+ */
49303
+ this.useMemoryAdapter = (Ctor) => {
49304
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
49305
+ this.MemoryFactory = Ctor;
49306
+ };
49307
+ /**
49308
+ * Clears the memoized instance cache.
49309
+ * Call this when process.cwd() changes between strategy iterations
49310
+ * so new instances are created with the updated base path.
49311
+ */
49312
+ this.clear = () => {
49313
+ backtest.loggerService.info(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
49314
+ this.getInstance.clear();
49315
+ };
49316
+ }
49317
+ }
49318
+ /**
49319
+ * Live trading memory adapter with pluggable storage backend.
49320
+ *
49321
+ * Features:
49322
+ * - Adapter pattern for swappable memory instance implementations
49323
+ * - Default backend: MemoryPersistInstance (file-system backed, survives restarts)
49324
+ * - Alternative backends: MemoryLocalInstance, MemoryDummyInstance
49325
+ * - Convenience methods: useLocal(), usePersist(), useDummy(), useMemoryAdapter()
49326
+ * - Memoized instances per (signalId, bucketName) pair; cleared via disposeSignal() from MemoryAdapter
49327
+ *
49328
+ * Use this adapter for live trading memory storage.
49329
+ */
49330
+ class MemoryLiveAdapter {
47544
49331
  constructor() {
47545
49332
  this.MemoryFactory = MemoryPersistInstance;
47546
49333
  this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$4(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.MemoryFactory, [signalId, bucketName]));
47547
49334
  /**
47548
- * Activates the adapter by subscribing to signal lifecycle events.
47549
- * Clears memoized instances for a signalId when it is cancelled or closed,
47550
- * preventing stale instances from accumulating in memory.
47551
- * Idempotent — subsequent calls return the same subscription handle.
47552
- * Must be called before any memory method is used.
49335
+ * Disposes all memoized instances for the given signalId.
49336
+ * Called by MemoryAdapter when a signal is cancelled or closed.
49337
+ * @param signalId - Signal identifier to dispose
49338
+ */
49339
+ this.disposeSignal = (signalId) => {
49340
+ const prefix = CREATE_KEY_FN$4(signalId, "");
49341
+ for (const key of this.getInstance.keys()) {
49342
+ if (key.startsWith(prefix)) {
49343
+ const instance = this.getInstance.get(key);
49344
+ instance && instance.dispose();
49345
+ this.getInstance.clear(key);
49346
+ }
49347
+ }
49348
+ };
49349
+ /**
49350
+ * Write a value to memory.
49351
+ * @param dto.memoryId - Unique entry identifier
49352
+ * @param dto.value - Value to store
49353
+ * @param dto.signalId - Signal identifier
49354
+ * @param dto.bucketName - Bucket name
49355
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
49356
+ */
49357
+ this.writeMemory = async (dto) => {
49358
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
49359
+ signalId: dto.signalId,
49360
+ bucketName: dto.bucketName,
49361
+ memoryId: dto.memoryId,
49362
+ });
49363
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49364
+ const isInitial = !this.getInstance.has(key);
49365
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49366
+ await instance.waitForInit(isInitial);
49367
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
49368
+ };
49369
+ /**
49370
+ * Search memory using BM25 full-text scoring.
49371
+ * @param dto.query - Search query string
49372
+ * @param dto.signalId - Signal identifier
49373
+ * @param dto.bucketName - Bucket name
49374
+ * @returns Matching entries sorted by relevance score
49375
+ */
49376
+ this.searchMemory = async (dto) => {
49377
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_SEARCH, {
49378
+ signalId: dto.signalId,
49379
+ bucketName: dto.bucketName,
49380
+ query: dto.query,
49381
+ });
49382
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49383
+ const isInitial = !this.getInstance.has(key);
49384
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49385
+ await instance.waitForInit(isInitial);
49386
+ return await instance.searchMemory(dto.query, dto.settings);
49387
+ };
49388
+ /**
49389
+ * List all entries in memory.
49390
+ * @param dto.signalId - Signal identifier
49391
+ * @param dto.bucketName - Bucket name
49392
+ * @returns Array of all stored entries
49393
+ */
49394
+ this.listMemory = async (dto) => {
49395
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_LIST, {
49396
+ signalId: dto.signalId,
49397
+ bucketName: dto.bucketName,
49398
+ });
49399
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49400
+ const isInitial = !this.getInstance.has(key);
49401
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49402
+ await instance.waitForInit(isInitial);
49403
+ return await instance.listMemory();
49404
+ };
49405
+ /**
49406
+ * Remove an entry from memory.
49407
+ * @param dto.memoryId - Unique entry identifier
49408
+ * @param dto.signalId - Signal identifier
49409
+ * @param dto.bucketName - Bucket name
49410
+ */
49411
+ this.removeMemory = async (dto) => {
49412
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
49413
+ signalId: dto.signalId,
49414
+ bucketName: dto.bucketName,
49415
+ memoryId: dto.memoryId,
49416
+ });
49417
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49418
+ const isInitial = !this.getInstance.has(key);
49419
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49420
+ await instance.waitForInit(isInitial);
49421
+ return await instance.removeMemory(dto.memoryId);
49422
+ };
49423
+ /**
49424
+ * Read a single entry from memory.
49425
+ * @param dto.memoryId - Unique entry identifier
49426
+ * @param dto.signalId - Signal identifier
49427
+ * @param dto.bucketName - Bucket name
49428
+ * @returns Entry value
49429
+ * @throws Error if entry not found
49430
+ */
49431
+ this.readMemory = async (dto) => {
49432
+ backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_READ, {
49433
+ signalId: dto.signalId,
49434
+ bucketName: dto.bucketName,
49435
+ memoryId: dto.memoryId,
49436
+ });
49437
+ const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
49438
+ const isInitial = !this.getInstance.has(key);
49439
+ const instance = this.getInstance(dto.signalId, dto.bucketName);
49440
+ await instance.waitForInit(isInitial);
49441
+ return await instance.readMemory(dto.memoryId);
49442
+ };
49443
+ /**
49444
+ * Switches to in-memory BM25 adapter.
49445
+ * All data lives in process memory only.
49446
+ */
49447
+ this.useLocal = () => {
49448
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_LOCAL);
49449
+ this.MemoryFactory = MemoryLocalInstance;
49450
+ };
49451
+ /**
49452
+ * Switches to file-system backed adapter (default).
49453
+ * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
49454
+ */
49455
+ this.usePersist = () => {
49456
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
49457
+ this.MemoryFactory = MemoryPersistInstance;
49458
+ };
49459
+ /**
49460
+ * Switches to dummy adapter that discards all writes.
49461
+ */
49462
+ this.useDummy = () => {
49463
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
49464
+ this.MemoryFactory = MemoryDummyInstance;
49465
+ };
49466
+ /**
49467
+ * Switches to a custom memory adapter implementation.
49468
+ * @param Ctor - Constructor for the custom memory instance
49469
+ */
49470
+ this.useMemoryAdapter = (Ctor) => {
49471
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
49472
+ this.MemoryFactory = Ctor;
49473
+ };
49474
+ /**
49475
+ * Clears the memoized instance cache.
49476
+ * Call this when process.cwd() changes between strategy iterations
49477
+ * so new instances are created with the updated base path.
49478
+ */
49479
+ this.clear = () => {
49480
+ backtest.loggerService.info(MEMORY_LIVE_ADAPTER_METHOD_NAME_CLEAR);
49481
+ this.getInstance.clear();
49482
+ };
49483
+ }
49484
+ }
49485
+ /**
49486
+ * Main memory adapter that manages both backtest and live memory storage.
49487
+ *
49488
+ * Features:
49489
+ * - Subscribes to signal lifecycle events (cancelled/closed) to dispose stale instances
49490
+ * - Routes all operations to MemoryBacktest or MemoryLive based on dto.backtest
49491
+ * - Singleshot enable pattern prevents duplicate subscriptions
49492
+ * - Cleanup function for proper unsubscription
49493
+ */
49494
+ class MemoryAdapter {
49495
+ constructor() {
49496
+ /**
49497
+ * Enables memory storage by subscribing to signal lifecycle events.
49498
+ * Clears memoized instances in MemoryBacktest and MemoryLive when a signal
49499
+ * is cancelled or closed, preventing stale instances from accumulating.
49500
+ * Uses singleshot to ensure one-time subscription.
49501
+ *
49502
+ * @returns Cleanup function that unsubscribes from all emitters
47553
49503
  */
47554
49504
  this.enable = functoolsKit.singleshot(() => {
47555
49505
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_ENABLE);
47556
- const handleDispose = (signalId) => {
47557
- const prefix = CREATE_KEY_FN$4(signalId, "");
47558
- for (const key of this.getInstance.keys()) {
47559
- if (key.startsWith(prefix)) {
47560
- const instance = this.getInstance.get(key);
47561
- instance && instance.dispose();
47562
- this.getInstance.clear(key);
47563
- }
47564
- }
47565
- };
47566
49506
  const unCancel = signalEmitter
47567
49507
  .filter(({ action }) => action === "cancelled")
47568
- .connect(({ signal }) => handleDispose(signal.id));
49508
+ .connect(({ signal }) => {
49509
+ MemoryBacktest.disposeSignal(signal.id);
49510
+ MemoryLive.disposeSignal(signal.id);
49511
+ });
47569
49512
  const unClose = signalEmitter
47570
49513
  .filter(({ action }) => action === "closed")
47571
- .connect(({ signal }) => handleDispose(signal.id));
47572
- return functoolsKit.compose(() => unCancel(), () => unClose());
49514
+ .connect(({ signal }) => {
49515
+ MemoryBacktest.disposeSignal(signal.id);
49516
+ MemoryLive.disposeSignal(signal.id);
49517
+ });
49518
+ return functoolsKit.compose(() => unCancel(), () => unClose(), () => this.enable.clear());
47573
49519
  });
47574
49520
  /**
47575
- * Deactivates the adapter by unsubscribing from signal lifecycle events.
47576
- * No-op if enable() was never called.
49521
+ * Disables memory storage by unsubscribing from signal lifecycle events.
49522
+ * Safe to call multiple times.
47577
49523
  */
47578
49524
  this.disable = () => {
47579
49525
  backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISABLE);
@@ -47584,11 +49530,13 @@ class MemoryAdapter {
47584
49530
  };
47585
49531
  /**
47586
49532
  * Write a value to memory.
49533
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47587
49534
  * @param dto.memoryId - Unique entry identifier
47588
49535
  * @param dto.value - Value to store
47589
49536
  * @param dto.signalId - Signal identifier
47590
49537
  * @param dto.bucketName - Bucket name
47591
- * @param dto.description - Optional BM25 index string; defaults to JSON.stringify(value)
49538
+ * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
49539
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47592
49540
  */
47593
49541
  this.writeMemory = async (dto) => {
47594
49542
  if (!this.enable.hasValue()) {
@@ -47598,18 +49546,20 @@ class MemoryAdapter {
47598
49546
  signalId: dto.signalId,
47599
49547
  bucketName: dto.bucketName,
47600
49548
  memoryId: dto.memoryId,
49549
+ backtest: dto.backtest,
47601
49550
  });
47602
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47603
- const isInitial = !this.getInstance.has(key);
47604
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47605
- await instance.waitForInit(isInitial);
47606
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
49551
+ if (dto.backtest) {
49552
+ return await MemoryBacktest.writeMemory(dto);
49553
+ }
49554
+ return await MemoryLive.writeMemory(dto);
47607
49555
  };
47608
49556
  /**
47609
49557
  * Search memory using BM25 full-text scoring.
49558
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47610
49559
  * @param dto.query - Search query string
47611
49560
  * @param dto.signalId - Signal identifier
47612
49561
  * @param dto.bucketName - Bucket name
49562
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47613
49563
  * @returns Matching entries sorted by relevance score
47614
49564
  */
47615
49565
  this.searchMemory = async (dto) => {
@@ -47620,17 +49570,19 @@ class MemoryAdapter {
47620
49570
  signalId: dto.signalId,
47621
49571
  bucketName: dto.bucketName,
47622
49572
  query: dto.query,
49573
+ backtest: dto.backtest,
47623
49574
  });
47624
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47625
- const isInitial = !this.getInstance.has(key);
47626
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47627
- await instance.waitForInit(isInitial);
47628
- return await instance.searchMemory(dto.query, dto.settings);
49575
+ if (dto.backtest) {
49576
+ return await MemoryBacktest.searchMemory(dto);
49577
+ }
49578
+ return await MemoryLive.searchMemory(dto);
47629
49579
  };
47630
49580
  /**
47631
49581
  * List all entries in memory.
49582
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47632
49583
  * @param dto.signalId - Signal identifier
47633
49584
  * @param dto.bucketName - Bucket name
49585
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47634
49586
  * @returns Array of all stored entries
47635
49587
  */
47636
49588
  this.listMemory = async (dto) => {
@@ -47640,18 +49592,20 @@ class MemoryAdapter {
47640
49592
  backtest.loggerService.debug(MEMORY_ADAPTER_METHOD_NAME_LIST, {
47641
49593
  signalId: dto.signalId,
47642
49594
  bucketName: dto.bucketName,
49595
+ backtest: dto.backtest,
47643
49596
  });
47644
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47645
- const isInitial = !this.getInstance.has(key);
47646
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47647
- await instance.waitForInit(isInitial);
47648
- return await instance.listMemory();
49597
+ if (dto.backtest) {
49598
+ return await MemoryBacktest.listMemory(dto);
49599
+ }
49600
+ return await MemoryLive.listMemory(dto);
47649
49601
  };
47650
49602
  /**
47651
49603
  * Remove an entry from memory.
49604
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47652
49605
  * @param dto.memoryId - Unique entry identifier
47653
49606
  * @param dto.signalId - Signal identifier
47654
49607
  * @param dto.bucketName - Bucket name
49608
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47655
49609
  */
47656
49610
  this.removeMemory = async (dto) => {
47657
49611
  if (!this.enable.hasValue()) {
@@ -47661,18 +49615,20 @@ class MemoryAdapter {
47661
49615
  signalId: dto.signalId,
47662
49616
  bucketName: dto.bucketName,
47663
49617
  memoryId: dto.memoryId,
49618
+ backtest: dto.backtest,
47664
49619
  });
47665
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47666
- const isInitial = !this.getInstance.has(key);
47667
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47668
- await instance.waitForInit(isInitial);
47669
- return await instance.removeMemory(dto.memoryId);
49620
+ if (dto.backtest) {
49621
+ return await MemoryBacktest.removeMemory(dto);
49622
+ }
49623
+ return await MemoryLive.removeMemory(dto);
47670
49624
  };
47671
49625
  /**
47672
49626
  * Read a single entry from memory.
49627
+ * Routes to MemoryBacktest or MemoryLive based on dto.backtest.
47673
49628
  * @param dto.memoryId - Unique entry identifier
47674
49629
  * @param dto.signalId - Signal identifier
47675
49630
  * @param dto.bucketName - Bucket name
49631
+ * @param dto.backtest - Flag indicating if the context is backtest or live
47676
49632
  * @returns Entry value
47677
49633
  * @throws Error if entry not found
47678
49634
  */
@@ -47684,56 +49640,30 @@ class MemoryAdapter {
47684
49640
  signalId: dto.signalId,
47685
49641
  bucketName: dto.bucketName,
47686
49642
  memoryId: dto.memoryId,
49643
+ backtest: dto.backtest,
47687
49644
  });
47688
- const key = CREATE_KEY_FN$4(dto.signalId, dto.bucketName);
47689
- const isInitial = !this.getInstance.has(key);
47690
- const instance = this.getInstance(dto.signalId, dto.bucketName);
47691
- await instance.waitForInit(isInitial);
47692
- return await instance.readMemory(dto.memoryId);
47693
- };
47694
- /**
47695
- * Switches to in-memory BM25 adapter (default).
47696
- * All data lives in process memory only.
47697
- */
47698
- this.useLocal = () => {
47699
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_LOCAL);
47700
- this.MemoryFactory = MemoryLocalInstance;
47701
- };
47702
- /**
47703
- * Switches to file-system backed adapter.
47704
- * Data is persisted to ./dump/memory/<signalId>/<bucketName>/.
47705
- */
47706
- this.usePersist = () => {
47707
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_PERSIST);
47708
- this.MemoryFactory = MemoryPersistInstance;
47709
- };
47710
- /**
47711
- * Switches to dummy adapter that discards all writes.
47712
- */
47713
- this.useDummy = () => {
47714
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_USE_DUMMY);
47715
- this.MemoryFactory = MemoryDummyInstance;
47716
- };
47717
- /**
47718
- * Clears the memoized instance cache.
47719
- * Call this when process.cwd() changes between strategy iterations
47720
- * so new instances are created with the updated base path.
47721
- */
47722
- this.clear = () => {
47723
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_CLEAR);
47724
- this.getInstance.clear();
47725
- };
47726
- /**
47727
- * Releases resources held by this adapter.
47728
- * Delegates to disable() to unsubscribe from signal lifecycle events.
47729
- */
47730
- this.dispose = () => {
47731
- backtest.loggerService.info(MEMORY_ADAPTER_METHOD_NAME_DISPOSE);
47732
- this.disable();
49645
+ if (dto.backtest) {
49646
+ return await MemoryBacktest.readMemory(dto);
49647
+ }
49648
+ return await MemoryLive.readMemory(dto);
47733
49649
  };
47734
49650
  }
47735
49651
  }
49652
+ /**
49653
+ * Global singleton instance of MemoryAdapter.
49654
+ * Provides unified memory management for backtest and live trading.
49655
+ */
47736
49656
  const Memory = new MemoryAdapter();
49657
+ /**
49658
+ * Global singleton instance of MemoryLiveAdapter.
49659
+ * Provides live trading memory storage with pluggable backends.
49660
+ */
49661
+ const MemoryLive = new MemoryLiveAdapter();
49662
+ /**
49663
+ * Global singleton instance of MemoryBacktestAdapter.
49664
+ * Provides backtest memory storage with pluggable backends.
49665
+ */
49666
+ const MemoryBacktest = new MemoryBacktestAdapter();
47737
49667
 
47738
49668
  const WRITE_MEMORY_METHOD_NAME = "memory.writeMemory";
47739
49669
  const READ_MEMORY_METHOD_NAME = "memory.readMemory";
@@ -47743,23 +49673,20 @@ const REMOVE_MEMORY_METHOD_NAME = "memory.removeMemory";
47743
49673
  /**
47744
49674
  * Writes a value to memory scoped to the current signal.
47745
49675
  *
47746
- * Reads symbol from execution context and signalId from the active pending signal.
47747
- * If no pending signal exists, logs a warning and returns without writing.
47748
- *
49676
+ * Resolves the active pending or scheduled signal automatically from execution context.
47749
49677
  * Automatically detects backtest/live mode from execution context.
47750
49678
  *
47751
49679
  * @param dto.bucketName - Memory bucket name
47752
49680
  * @param dto.memoryId - Unique memory entry identifier
47753
49681
  * @param dto.value - Value to store
49682
+ * @param dto.description - BM25 index string for contextual search
47754
49683
  * @returns Promise that resolves when write is complete
47755
49684
  *
47756
- * @deprecated Better use Memory.writeMemory with manual signalId argument
47757
- *
47758
49685
  * @example
47759
49686
  * ```typescript
47760
49687
  * import { writeMemory } from "backtest-kit";
47761
49688
  *
47762
- * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 } });
49689
+ * await writeMemory({ bucketName: "my-strategy", memoryId: "context", value: { trend: "up", confidence: 0.9 }, description: "Signal context at entry" });
47763
49690
  * ```
47764
49691
  */
47765
49692
  async function writeMemory(dto) {
@@ -47777,33 +49704,41 @@ async function writeMemory(dto) {
47777
49704
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47778
49705
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47779
49706
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47780
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47781
- if (!signal) {
47782
- console.warn(`backtest-kit writeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49707
+ let signal;
49708
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49709
+ await Memory.writeMemory({
49710
+ memoryId,
49711
+ value,
49712
+ signalId: signal.id,
49713
+ bucketName,
49714
+ description,
49715
+ backtest: isBacktest,
49716
+ });
47783
49717
  return;
47784
49718
  }
47785
- await Memory.writeMemory({
47786
- memoryId,
47787
- value,
47788
- signalId: signal.id,
47789
- bucketName,
47790
- description,
47791
- });
49719
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49720
+ await Memory.writeMemory({
49721
+ memoryId,
49722
+ value,
49723
+ signalId: signal.id,
49724
+ bucketName,
49725
+ description,
49726
+ backtest: isBacktest,
49727
+ });
49728
+ return;
49729
+ }
49730
+ throw new Error(`writeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47792
49731
  }
47793
49732
  /**
47794
49733
  * Reads a value from memory scoped to the current signal.
47795
49734
  *
47796
- * Reads symbol from execution context and signalId from the active pending signal.
47797
- * If no pending signal exists, logs a warning and returns null.
47798
- *
49735
+ * Resolves the active pending or scheduled signal automatically from execution context.
47799
49736
  * Automatically detects backtest/live mode from execution context.
47800
49737
  *
47801
49738
  * @param dto.bucketName - Memory bucket name
47802
49739
  * @param dto.memoryId - Unique memory entry identifier
47803
- * @returns Promise resolving to stored value or null if no signal
47804
- * @throws Error if entry not found within an active signal
47805
- *
47806
- * @deprecated Better use Memory.readMemory with manual signalId argument
49740
+ * @returns Promise resolving to stored value
49741
+ * @throws Error if no pending or scheduled signal exists, or if entry not found
47807
49742
  *
47808
49743
  * @example
47809
49744
  * ```typescript
@@ -47827,30 +49762,35 @@ async function readMemory(dto) {
47827
49762
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47828
49763
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47829
49764
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47830
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47831
- if (!signal) {
47832
- console.warn(`backtest-kit readMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
47833
- return null;
49765
+ let signal;
49766
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49767
+ return await Memory.readMemory({
49768
+ memoryId,
49769
+ signalId: signal.id,
49770
+ bucketName,
49771
+ backtest: isBacktest,
49772
+ });
47834
49773
  }
47835
- return await Memory.readMemory({
47836
- memoryId,
47837
- signalId: signal.id,
47838
- bucketName,
47839
- });
49774
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49775
+ return await Memory.readMemory({
49776
+ memoryId,
49777
+ signalId: signal.id,
49778
+ bucketName,
49779
+ backtest: isBacktest,
49780
+ });
49781
+ }
49782
+ throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47840
49783
  }
47841
49784
  /**
47842
49785
  * Searches memory entries for the current signal using BM25 full-text scoring.
47843
49786
  *
47844
- * Reads symbol from execution context and signalId from the active pending signal.
47845
- * If no pending signal exists, logs a warning and returns an empty array.
47846
- *
49787
+ * Resolves the active pending or scheduled signal automatically from execution context.
47847
49788
  * Automatically detects backtest/live mode from execution context.
47848
49789
  *
47849
49790
  * @param dto.bucketName - Memory bucket name
47850
49791
  * @param dto.query - Search query string
47851
- * @returns Promise resolving to matching entries sorted by relevance, or empty array if no signal
47852
- *
47853
- * @deprecated Better use Memory.searchMemory with manual signalId argument
49792
+ * @returns Promise resolving to matching entries sorted by relevance
49793
+ * @throws Error if no pending or scheduled signal exists
47854
49794
  *
47855
49795
  * @example
47856
49796
  * ```typescript
@@ -47874,29 +49814,34 @@ async function searchMemory(dto) {
47874
49814
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47875
49815
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47876
49816
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47877
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47878
- if (!signal) {
47879
- console.warn(`backtest-kit searchMemory no pending signal for symbol=${symbol} query=${query}`);
47880
- return [];
49817
+ let signal;
49818
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49819
+ return await Memory.searchMemory({
49820
+ query,
49821
+ signalId: signal.id,
49822
+ bucketName,
49823
+ backtest: isBacktest,
49824
+ });
47881
49825
  }
47882
- return await Memory.searchMemory({
47883
- query,
47884
- signalId: signal.id,
47885
- bucketName,
47886
- });
49826
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49827
+ return await Memory.searchMemory({
49828
+ query,
49829
+ signalId: signal.id,
49830
+ bucketName,
49831
+ backtest: isBacktest,
49832
+ });
49833
+ }
49834
+ throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
47887
49835
  }
47888
49836
  /**
47889
49837
  * Lists all memory entries for the current signal.
47890
49838
  *
47891
- * Reads symbol from execution context and signalId from the active pending signal.
47892
- * If no pending signal exists, logs a warning and returns an empty array.
47893
- *
49839
+ * Resolves the active pending or scheduled signal automatically from execution context.
47894
49840
  * Automatically detects backtest/live mode from execution context.
47895
49841
  *
47896
49842
  * @param dto.bucketName - Memory bucket name
47897
- * @returns Promise resolving to all stored entries, or empty array if no signal
47898
- *
47899
- * @deprecated Better use Memory.listMemory with manual signalId argument
49843
+ * @returns Promise resolving to all stored entries
49844
+ * @throws Error if no pending or scheduled signal exists
47900
49845
  *
47901
49846
  * @example
47902
49847
  * ```typescript
@@ -47919,29 +49864,33 @@ async function listMemory(dto) {
47919
49864
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47920
49865
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47921
49866
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47922
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47923
- if (!signal) {
47924
- console.warn(`backtest-kit listMemory no pending signal for symbol=${symbol}`);
47925
- return [];
49867
+ let signal;
49868
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49869
+ return await Memory.listMemory({
49870
+ signalId: signal.id,
49871
+ bucketName,
49872
+ backtest: isBacktest,
49873
+ });
47926
49874
  }
47927
- return await Memory.listMemory({
47928
- signalId: signal.id,
47929
- bucketName,
47930
- });
49875
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49876
+ return await Memory.listMemory({
49877
+ signalId: signal.id,
49878
+ bucketName,
49879
+ backtest: isBacktest,
49880
+ });
49881
+ }
49882
+ throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
47931
49883
  }
47932
49884
  /**
47933
49885
  * Removes a memory entry for the current signal.
47934
49886
  *
47935
- * Reads symbol from execution context and signalId from the active pending signal.
47936
- * If no pending signal exists, logs a warning and returns without removing.
47937
- *
49887
+ * Resolves the active pending or scheduled signal automatically from execution context.
47938
49888
  * Automatically detects backtest/live mode from execution context.
47939
49889
  *
47940
49890
  * @param dto.bucketName - Memory bucket name
47941
49891
  * @param dto.memoryId - Unique memory entry identifier
47942
49892
  * @returns Promise that resolves when removal is complete
47943
- *
47944
- * @deprecated Better use Memory.removeMemory with manual signalId argument
49893
+ * @throws Error if no pending or scheduled signal exists
47945
49894
  *
47946
49895
  * @example
47947
49896
  * ```typescript
@@ -47965,16 +49914,26 @@ async function removeMemory(dto) {
47965
49914
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
47966
49915
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
47967
49916
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
47968
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
47969
- if (!signal) {
47970
- console.warn(`backtest-kit removeMemory no pending signal for symbol=${symbol} memoryId=${memoryId}`);
49917
+ let signal;
49918
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49919
+ await Memory.removeMemory({
49920
+ memoryId,
49921
+ signalId: signal.id,
49922
+ bucketName,
49923
+ backtest: isBacktest,
49924
+ });
47971
49925
  return;
47972
49926
  }
47973
- await Memory.removeMemory({
47974
- memoryId,
47975
- signalId: signal.id,
47976
- bucketName,
47977
- });
49927
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
49928
+ await Memory.removeMemory({
49929
+ memoryId,
49930
+ signalId: signal.id,
49931
+ bucketName,
49932
+ backtest: isBacktest,
49933
+ });
49934
+ return;
49935
+ }
49936
+ throw new Error(`removeMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
47978
49937
  }
47979
49938
 
47980
49939
  const CREATE_KEY_FN$3 = (signalId, bucketName) => `${signalId}-${bucketName}`;
@@ -48069,11 +50028,12 @@ const RENDER_TABLE_FN = (rows) => {
48069
50028
  * Scoped to (signalId, bucketName) via constructor.
48070
50029
  */
48071
50030
  class DumpBothInstance {
48072
- constructor(signalId, bucketName) {
50031
+ constructor(signalId, bucketName, backtest) {
48073
50032
  this.signalId = signalId;
48074
50033
  this.bucketName = bucketName;
48075
- this._memory = new DumpMemoryInstance(signalId, bucketName);
48076
- this._markdown = new DumpMarkdownInstance(signalId, bucketName);
50034
+ this.backtest = backtest;
50035
+ this._memory = new DumpMemoryInstance(signalId, bucketName, backtest);
50036
+ this._markdown = new DumpMarkdownInstance(signalId, bucketName, backtest);
48077
50037
  }
48078
50038
  /** Releases resources held by both backends. */
48079
50039
  dispose() {
@@ -48205,9 +50165,10 @@ class DumpBothInstance {
48205
50165
  * Useful for downstream LLM retrieval via Memory.searchMemory.
48206
50166
  */
48207
50167
  class DumpMemoryInstance {
48208
- constructor(signalId, bucketName) {
50168
+ constructor(signalId, bucketName, backtest) {
48209
50169
  this.signalId = signalId;
48210
50170
  this.bucketName = bucketName;
50171
+ this.backtest = backtest;
48211
50172
  }
48212
50173
  /**
48213
50174
  * Stores the full agent message history in Memory as a `{ messages }` object.
@@ -48233,6 +50194,7 @@ class DumpMemoryInstance {
48233
50194
  signalId: this.signalId,
48234
50195
  value: { messages },
48235
50196
  description,
50197
+ backtest: this.backtest,
48236
50198
  });
48237
50199
  }
48238
50200
  /**
@@ -48254,6 +50216,7 @@ class DumpMemoryInstance {
48254
50216
  signalId: this.signalId,
48255
50217
  value: record,
48256
50218
  description,
50219
+ backtest: this.backtest,
48257
50220
  });
48258
50221
  }
48259
50222
  /**
@@ -48276,6 +50239,7 @@ class DumpMemoryInstance {
48276
50239
  signalId: this.signalId,
48277
50240
  value: { rows },
48278
50241
  description,
50242
+ backtest: this.backtest,
48279
50243
  });
48280
50244
  }
48281
50245
  /**
@@ -48297,6 +50261,7 @@ class DumpMemoryInstance {
48297
50261
  signalId: this.signalId,
48298
50262
  value: { content },
48299
50263
  description,
50264
+ backtest: this.backtest,
48300
50265
  });
48301
50266
  }
48302
50267
  /**
@@ -48318,6 +50283,7 @@ class DumpMemoryInstance {
48318
50283
  signalId: this.signalId,
48319
50284
  value: { content },
48320
50285
  description,
50286
+ backtest: this.backtest,
48321
50287
  });
48322
50288
  }
48323
50289
  /**
@@ -48340,6 +50306,7 @@ class DumpMemoryInstance {
48340
50306
  signalId: this.signalId,
48341
50307
  value: json,
48342
50308
  description,
50309
+ backtest: this.backtest,
48343
50310
  });
48344
50311
  }
48345
50312
  /** Releases resources held by this instance. */
@@ -48361,9 +50328,10 @@ class DumpMemoryInstance {
48361
50328
  * If the file already exists, the call is skipped (idempotent).
48362
50329
  */
48363
50330
  class DumpMarkdownInstance {
48364
- constructor(signalId, bucketName) {
50331
+ constructor(signalId, bucketName, backtest) {
48365
50332
  this.signalId = signalId;
48366
50333
  this.bucketName = bucketName;
50334
+ this.backtest = backtest;
48367
50335
  }
48368
50336
  getFilePath(dumpId) {
48369
50337
  return path.join("./dump/agent", this.signalId, this.bucketName, `${dumpId}.md`);
@@ -48552,9 +50520,10 @@ class DumpMarkdownInstance {
48552
50520
  * Used for disabling dumps in tests or dry-run scenarios.
48553
50521
  */
48554
50522
  class DumpDummyInstance {
48555
- constructor(signalId, bucketName) {
50523
+ constructor(signalId, bucketName, backtest) {
48556
50524
  this.signalId = signalId;
48557
50525
  this.bucketName = bucketName;
50526
+ this.backtest = backtest;
48558
50527
  }
48559
50528
  /** No-op. */
48560
50529
  async dumpAgentAnswer() {
@@ -48597,7 +50566,7 @@ class DumpDummyInstance {
48597
50566
  class DumpAdapter {
48598
50567
  constructor() {
48599
50568
  this.DumpFactory = DumpMarkdownInstance;
48600
- this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName) => Reflect.construct(this.DumpFactory, [signalId, bucketName]));
50569
+ this.getInstance = functoolsKit.memoize(([signalId, bucketName]) => CREATE_KEY_FN$3(signalId, bucketName), (signalId, bucketName, backtest) => Reflect.construct(this.DumpFactory, [signalId, bucketName, backtest]));
48601
50570
  /**
48602
50571
  * Activates the adapter by subscribing to signal lifecycle events.
48603
50572
  * Clears memoized instances for a signalId when it is cancelled or closed,
@@ -48648,7 +50617,7 @@ class DumpAdapter {
48648
50617
  bucketName: context.bucketName,
48649
50618
  dumpId: context.dumpId,
48650
50619
  });
48651
- const instance = this.getInstance(context.signalId, context.bucketName);
50620
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48652
50621
  return await instance.dumpAgentAnswer(messages, context.dumpId, context.description);
48653
50622
  };
48654
50623
  /**
@@ -48663,7 +50632,7 @@ class DumpAdapter {
48663
50632
  bucketName: context.bucketName,
48664
50633
  dumpId: context.dumpId,
48665
50634
  });
48666
- const instance = this.getInstance(context.signalId, context.bucketName);
50635
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48667
50636
  return await instance.dumpRecord(record, context.dumpId, context.description);
48668
50637
  };
48669
50638
  /**
@@ -48678,7 +50647,7 @@ class DumpAdapter {
48678
50647
  bucketName: context.bucketName,
48679
50648
  dumpId: context.dumpId,
48680
50649
  });
48681
- const instance = this.getInstance(context.signalId, context.bucketName);
50650
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48682
50651
  return await instance.dumpTable(rows, context.dumpId, context.description);
48683
50652
  };
48684
50653
  /**
@@ -48693,7 +50662,7 @@ class DumpAdapter {
48693
50662
  bucketName: context.bucketName,
48694
50663
  dumpId: context.dumpId,
48695
50664
  });
48696
- const instance = this.getInstance(context.signalId, context.bucketName);
50665
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48697
50666
  return await instance.dumpText(content, context.dumpId, context.description);
48698
50667
  };
48699
50668
  /**
@@ -48708,7 +50677,7 @@ class DumpAdapter {
48708
50677
  bucketName: context.bucketName,
48709
50678
  dumpId: context.dumpId,
48710
50679
  });
48711
- const instance = this.getInstance(context.signalId, context.bucketName);
50680
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48712
50681
  return await instance.dumpError(content, context.dumpId, context.description);
48713
50682
  };
48714
50683
  /**
@@ -48724,7 +50693,7 @@ class DumpAdapter {
48724
50693
  bucketName: context.bucketName,
48725
50694
  dumpId: context.dumpId,
48726
50695
  });
48727
- const instance = this.getInstance(context.signalId, context.bucketName);
50696
+ const instance = this.getInstance(context.signalId, context.bucketName, context.backtest);
48728
50697
  return await instance.dumpJson(json, context.dumpId, context.description);
48729
50698
  };
48730
50699
  /**
@@ -48790,16 +50759,15 @@ const DUMP_JSON_METHOD_NAME = "dump.dumpJson";
48790
50759
  /**
48791
50760
  * Dumps the full agent message history scoped to the current signal.
48792
50761
  *
48793
- * Reads signalId from the active pending signal via execution and method context.
48794
- * If no pending signal exists, logs a warning and returns without writing.
50762
+ * Resolves the active pending or scheduled signal automatically from execution context.
50763
+ * Automatically detects backtest/live mode from execution context.
48795
50764
  *
48796
50765
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48797
50766
  * @param dto.dumpId - Unique identifier for this agent invocation
48798
50767
  * @param dto.messages - Full chat history (system, user, assistant, tool)
48799
50768
  * @param dto.description - Human-readable label describing the agent invocation context; included in the BM25 index for Memory search
48800
50769
  * @returns Promise that resolves when the dump is complete
48801
- *
48802
- * @deprecated Better use Dump.dumpAgentAnswer with manual signalId argument
50770
+ * @throws Error if no pending or scheduled signal exists
48803
50771
  *
48804
50772
  * @example
48805
50773
  * ```typescript
@@ -48824,31 +50792,41 @@ async function dumpAgentAnswer(dto) {
48824
50792
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48825
50793
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48826
50794
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48827
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48828
- if (!signal) {
48829
- console.warn(`backtest-kit dumpAgentAnswer no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50795
+ let signal;
50796
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50797
+ await Dump.dumpAgentAnswer(messages, {
50798
+ dumpId,
50799
+ bucketName,
50800
+ signalId: signal.id,
50801
+ description,
50802
+ backtest: isBacktest,
50803
+ });
48830
50804
  return;
48831
50805
  }
48832
- await Dump.dumpAgentAnswer(messages, {
48833
- dumpId,
48834
- bucketName,
48835
- signalId: signal.id,
48836
- description,
48837
- });
50806
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50807
+ await Dump.dumpAgentAnswer(messages, {
50808
+ dumpId,
50809
+ bucketName,
50810
+ signalId: signal.id,
50811
+ description,
50812
+ backtest: isBacktest,
50813
+ });
50814
+ return;
50815
+ }
50816
+ throw new Error(`dumpAgentAnswer requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48838
50817
  }
48839
50818
  /**
48840
50819
  * Dumps a flat key-value record scoped to the current signal.
48841
50820
  *
48842
- * Reads signalId from the active pending signal via execution and method context.
48843
- * If no pending signal exists, logs a warning and returns without writing.
50821
+ * Resolves the active pending or scheduled signal automatically from execution context.
50822
+ * Automatically detects backtest/live mode from execution context.
48844
50823
  *
48845
50824
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48846
50825
  * @param dto.dumpId - Unique identifier for this dump entry
48847
50826
  * @param dto.record - Arbitrary flat object to persist
48848
50827
  * @param dto.description - Human-readable label describing the record contents; included in the BM25 index for Memory search
48849
50828
  * @returns Promise that resolves when the dump is complete
48850
- *
48851
- * @deprecated Better use Dump.dumpRecord with manual signalId argument
50829
+ * @throws Error if no pending or scheduled signal exists
48852
50830
  *
48853
50831
  * @example
48854
50832
  * ```typescript
@@ -48872,23 +50850,34 @@ async function dumpRecord(dto) {
48872
50850
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48873
50851
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48874
50852
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48875
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48876
- if (!signal) {
48877
- console.warn(`backtest-kit dumpRecord no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50853
+ let signal;
50854
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50855
+ await Dump.dumpRecord(record, {
50856
+ dumpId,
50857
+ bucketName,
50858
+ signalId: signal.id,
50859
+ description,
50860
+ backtest: isBacktest,
50861
+ });
48878
50862
  return;
48879
50863
  }
48880
- await Dump.dumpRecord(record, {
48881
- dumpId,
48882
- bucketName,
48883
- signalId: signal.id,
48884
- description,
48885
- });
50864
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50865
+ await Dump.dumpRecord(record, {
50866
+ dumpId,
50867
+ bucketName,
50868
+ signalId: signal.id,
50869
+ description,
50870
+ backtest: isBacktest,
50871
+ });
50872
+ return;
50873
+ }
50874
+ throw new Error(`dumpRecord requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48886
50875
  }
48887
50876
  /**
48888
50877
  * Dumps an array of objects as a table scoped to the current signal.
48889
50878
  *
48890
- * Reads signalId from the active pending signal via execution and method context.
48891
- * If no pending signal exists, logs a warning and returns without writing.
50879
+ * Resolves the active pending or scheduled signal automatically from execution context.
50880
+ * Automatically detects backtest/live mode from execution context.
48892
50881
  *
48893
50882
  * Column headers are derived from the union of all keys across all rows.
48894
50883
  *
@@ -48897,8 +50886,7 @@ async function dumpRecord(dto) {
48897
50886
  * @param dto.rows - Array of arbitrary objects to render as a table
48898
50887
  * @param dto.description - Human-readable label describing the table contents; included in the BM25 index for Memory search
48899
50888
  * @returns Promise that resolves when the dump is complete
48900
- *
48901
- * @deprecated Better use Dump.dumpTable with manual signalId argument
50889
+ * @throws Error if no pending or scheduled signal exists
48902
50890
  *
48903
50891
  * @example
48904
50892
  * ```typescript
@@ -48923,31 +50911,41 @@ async function dumpTable(dto) {
48923
50911
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48924
50912
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48925
50913
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48926
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48927
- if (!signal) {
48928
- console.warn(`backtest-kit dumpTable no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50914
+ let signal;
50915
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50916
+ await Dump.dumpTable(rows, {
50917
+ dumpId,
50918
+ bucketName,
50919
+ signalId: signal.id,
50920
+ description,
50921
+ backtest: isBacktest,
50922
+ });
48929
50923
  return;
48930
50924
  }
48931
- await Dump.dumpTable(rows, {
48932
- dumpId,
48933
- bucketName,
48934
- signalId: signal.id,
48935
- description,
48936
- });
50925
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50926
+ await Dump.dumpTable(rows, {
50927
+ dumpId,
50928
+ bucketName,
50929
+ signalId: signal.id,
50930
+ description,
50931
+ backtest: isBacktest,
50932
+ });
50933
+ return;
50934
+ }
50935
+ throw new Error(`dumpTable requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48937
50936
  }
48938
50937
  /**
48939
50938
  * Dumps raw text content scoped to the current signal.
48940
50939
  *
48941
- * Reads signalId from the active pending signal via execution and method context.
48942
- * If no pending signal exists, logs a warning and returns without writing.
50940
+ * Resolves the active pending or scheduled signal automatically from execution context.
50941
+ * Automatically detects backtest/live mode from execution context.
48943
50942
  *
48944
50943
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48945
50944
  * @param dto.dumpId - Unique identifier for this dump entry
48946
50945
  * @param dto.content - Arbitrary text content to persist
48947
50946
  * @param dto.description - Human-readable label describing the content; included in the BM25 index for Memory search
48948
50947
  * @returns Promise that resolves when the dump is complete
48949
- *
48950
- * @deprecated Better use Dump.dumpText with manual signalId argument
50948
+ * @throws Error if no pending or scheduled signal exists
48951
50949
  *
48952
50950
  * @example
48953
50951
  * ```typescript
@@ -48971,31 +50969,41 @@ async function dumpText(dto) {
48971
50969
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
48972
50970
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
48973
50971
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
48974
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
48975
- if (!signal) {
48976
- console.warn(`backtest-kit dumpText no pending signal for symbol=${symbol} dumpId=${dumpId}`);
50972
+ let signal;
50973
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50974
+ await Dump.dumpText(content, {
50975
+ dumpId,
50976
+ bucketName,
50977
+ signalId: signal.id,
50978
+ description,
50979
+ backtest: isBacktest,
50980
+ });
48977
50981
  return;
48978
50982
  }
48979
- await Dump.dumpText(content, {
48980
- dumpId,
48981
- bucketName,
48982
- signalId: signal.id,
48983
- description,
48984
- });
50983
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
50984
+ await Dump.dumpText(content, {
50985
+ dumpId,
50986
+ bucketName,
50987
+ signalId: signal.id,
50988
+ description,
50989
+ backtest: isBacktest,
50990
+ });
50991
+ return;
50992
+ }
50993
+ throw new Error(`dumpText requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
48985
50994
  }
48986
50995
  /**
48987
50996
  * Dumps an error description scoped to the current signal.
48988
50997
  *
48989
- * Reads signalId from the active pending signal via execution and method context.
48990
- * If no pending signal exists, logs a warning and returns without writing.
50998
+ * Resolves the active pending or scheduled signal automatically from execution context.
50999
+ * Automatically detects backtest/live mode from execution context.
48991
51000
  *
48992
51001
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
48993
51002
  * @param dto.dumpId - Unique identifier for this dump entry
48994
51003
  * @param dto.content - Error message or description to persist
48995
51004
  * @param dto.description - Human-readable label describing the error context; included in the BM25 index for Memory search
48996
51005
  * @returns Promise that resolves when the dump is complete
48997
- *
48998
- * @deprecated Better use Dump.dumpError with manual signalId argument
51006
+ * @throws Error if no pending or scheduled signal exists
48999
51007
  *
49000
51008
  * @example
49001
51009
  * ```typescript
@@ -49019,29 +51027,41 @@ async function dumpError(dto) {
49019
51027
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49020
51028
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49021
51029
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49022
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49023
- if (!signal) {
49024
- console.warn(`backtest-kit dumpError no pending signal for symbol=${symbol} dumpId=${dumpId}`);
51030
+ let signal;
51031
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
51032
+ await Dump.dumpError(content, {
51033
+ dumpId,
51034
+ bucketName,
51035
+ signalId: signal.id,
51036
+ description,
51037
+ backtest: isBacktest,
51038
+ });
49025
51039
  return;
49026
51040
  }
49027
- await Dump.dumpError(content, {
49028
- dumpId,
49029
- bucketName,
49030
- signalId: signal.id,
49031
- description,
49032
- });
51041
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
51042
+ await Dump.dumpError(content, {
51043
+ dumpId,
51044
+ bucketName,
51045
+ signalId: signal.id,
51046
+ description,
51047
+ backtest: isBacktest,
51048
+ });
51049
+ return;
51050
+ }
51051
+ throw new Error(`dumpError requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49033
51052
  }
49034
51053
  /**
49035
51054
  * Dumps an arbitrary nested object as a fenced JSON block scoped to the current signal.
49036
51055
  *
49037
- * Reads signalId from the active pending signal via execution and method context.
49038
- * If no pending signal exists, logs a warning and returns without writing.
51056
+ * Resolves the active pending or scheduled signal automatically from execution context.
51057
+ * Automatically detects backtest/live mode from execution context.
49039
51058
  *
49040
51059
  * @param dto.bucketName - Bucket name grouping dumps by strategy or agent name
49041
51060
  * @param dto.dumpId - Unique identifier for this dump entry
49042
51061
  * @param dto.json - Arbitrary nested object to serialize with JSON.stringify
49043
51062
  * @param dto.description - Human-readable label describing the object contents; included in the BM25 index for Memory search
49044
51063
  * @returns Promise that resolves when the dump is complete
51064
+ * @throws Error if no pending or scheduled signal exists
49045
51065
  *
49046
51066
  * @deprecated Prefer dumpRecord — flat key-value structure maps naturally to markdown tables and SQL storage
49047
51067
  *
@@ -49067,17 +51087,28 @@ async function dumpJson(dto) {
49067
51087
  const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
49068
51088
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49069
51089
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49070
- const signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
49071
- if (!signal) {
49072
- console.warn(`backtest-kit dumpJson no pending signal for symbol=${symbol} dumpId=${dumpId}`);
51090
+ let signal;
51091
+ if (signal = await backtest.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
51092
+ await Dump.dumpJson(json, {
51093
+ dumpId,
51094
+ bucketName,
51095
+ signalId: signal.id,
51096
+ description,
51097
+ backtest: isBacktest,
51098
+ });
49073
51099
  return;
49074
51100
  }
49075
- await Dump.dumpJson(json, {
49076
- dumpId,
49077
- bucketName,
49078
- signalId: signal.id,
49079
- description,
49080
- });
51101
+ if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
51102
+ await Dump.dumpJson(json, {
51103
+ dumpId,
51104
+ bucketName,
51105
+ signalId: signal.id,
51106
+ description,
51107
+ backtest: isBacktest,
51108
+ });
51109
+ return;
51110
+ }
51111
+ throw new Error(`dumpJson requires a pending or scheduled signal for symbol=${symbol} dumpId=${dumpId}`);
49081
51112
  }
49082
51113
 
49083
51114
  /**
@@ -50458,7 +52489,7 @@ class LogAdapter {
50458
52489
  */
50459
52490
  const Log = new LogAdapter();
50460
52491
 
50461
- const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
52492
+ const METHOD_NAME_CREATE_SNAPSHOT = "SystemUtils.createSnapshot";
50462
52493
  /** List of all global subjects whose listeners should be snapshotted for session isolation */
50463
52494
  const SUBJECT_ISOLATION_LIST = [
50464
52495
  activePingSubject,
@@ -50505,7 +52536,7 @@ const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
50505
52536
  * Allows temporarily detaching all subject subscriptions so that one session
50506
52537
  * does not interfere with another, then restoring them afterwards.
50507
52538
  */
50508
- class SessionUtils {
52539
+ class SystemUtils {
50509
52540
  constructor() {
50510
52541
  /**
50511
52542
  * Snapshots the current listener state of every global subject by replacing
@@ -50519,7 +52550,7 @@ class SessionUtils {
50519
52550
  };
50520
52551
  }
50521
52552
  }
50522
- const Session = new SessionUtils();
52553
+ const System = new SystemUtils();
50523
52554
 
50524
52555
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
50525
52556
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
@@ -59565,6 +61596,10 @@ exports.MarkdownFolderBase = MarkdownFolderBase;
59565
61596
  exports.MarkdownWriter = MarkdownWriter;
59566
61597
  exports.MaxDrawdown = MaxDrawdown;
59567
61598
  exports.Memory = Memory;
61599
+ exports.MemoryBacktest = MemoryBacktest;
61600
+ exports.MemoryBacktestAdapter = MemoryBacktestAdapter;
61601
+ exports.MemoryLive = MemoryLive;
61602
+ exports.MemoryLiveAdapter = MemoryLiveAdapter;
59568
61603
  exports.MethodContextService = MethodContextService;
59569
61604
  exports.Notification = Notification;
59570
61605
  exports.NotificationBacktest = NotificationBacktest;
@@ -59583,7 +61618,9 @@ exports.PersistPartialAdapter = PersistPartialAdapter;
59583
61618
  exports.PersistRecentAdapter = PersistRecentAdapter;
59584
61619
  exports.PersistRiskAdapter = PersistRiskAdapter;
59585
61620
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
61621
+ exports.PersistSessionAdapter = PersistSessionAdapter;
59586
61622
  exports.PersistSignalAdapter = PersistSignalAdapter;
61623
+ exports.PersistStateAdapter = PersistStateAdapter;
59587
61624
  exports.PersistStorageAdapter = PersistStorageAdapter;
59588
61625
  exports.Position = Position;
59589
61626
  exports.PositionSize = PositionSize;
@@ -59597,11 +61634,19 @@ exports.ReportWriter = ReportWriter;
59597
61634
  exports.Risk = Risk;
59598
61635
  exports.Schedule = Schedule;
59599
61636
  exports.Session = Session;
61637
+ exports.SessionBacktest = SessionBacktest;
61638
+ exports.SessionLive = SessionLive;
61639
+ exports.State = State;
61640
+ exports.StateBacktest = StateBacktest;
61641
+ exports.StateBacktestAdapter = StateBacktestAdapter;
61642
+ exports.StateLive = StateLive;
61643
+ exports.StateLiveAdapter = StateLiveAdapter;
59600
61644
  exports.Storage = Storage;
59601
61645
  exports.StorageBacktest = StorageBacktest;
59602
61646
  exports.StorageLive = StorageLive;
59603
61647
  exports.Strategy = Strategy;
59604
61648
  exports.Sync = Sync;
61649
+ exports.System = System;
59605
61650
  exports.Walker = Walker;
59606
61651
  exports.addActionSchema = addActionSchema;
59607
61652
  exports.addExchangeSchema = addExchangeSchema;
@@ -59626,6 +61671,7 @@ exports.commitTrailingStop = commitTrailingStop;
59626
61671
  exports.commitTrailingStopCost = commitTrailingStopCost;
59627
61672
  exports.commitTrailingTake = commitTrailingTake;
59628
61673
  exports.commitTrailingTakeCost = commitTrailingTakeCost;
61674
+ exports.createSignalState = createSignalState;
59629
61675
  exports.dumpAgentAnswer = dumpAgentAnswer;
59630
61676
  exports.dumpError = dumpError;
59631
61677
  exports.dumpJson = dumpJson;
@@ -59692,6 +61738,8 @@ exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
59692
61738
  exports.getRawCandles = getRawCandles;
59693
61739
  exports.getRiskSchema = getRiskSchema;
59694
61740
  exports.getScheduledSignal = getScheduledSignal;
61741
+ exports.getSessionData = getSessionData;
61742
+ exports.getSignalState = getSignalState;
59695
61743
  exports.getSizingSchema = getSizingSchema;
59696
61744
  exports.getStrategySchema = getStrategySchema;
59697
61745
  exports.getSymbol = getSymbol;
@@ -59777,6 +61825,8 @@ exports.set = set;
59777
61825
  exports.setColumns = setColumns;
59778
61826
  exports.setConfig = setConfig;
59779
61827
  exports.setLogger = setLogger;
61828
+ exports.setSessionData = setSessionData;
61829
+ exports.setSignalState = setSignalState;
59780
61830
  exports.shutdown = shutdown;
59781
61831
  exports.slPercentShiftToPrice = slPercentShiftToPrice;
59782
61832
  exports.slPriceToPercentShift = slPriceToPercentShift;