backtest-kit 9.0.0 → 9.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -918,7 +918,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
918
918
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
919
919
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
920
920
  // Calculate step in milliseconds for candle close time validation
921
- const INTERVAL_MINUTES$9 = {
921
+ const INTERVAL_MINUTES$a = {
922
922
  "1m": 1,
923
923
  "3m": 3,
924
924
  "5m": 5,
@@ -931,7 +931,7 @@ const INTERVAL_MINUTES$9 = {
931
931
  "8h": 480,
932
932
  "1d": 1440,
933
933
  };
934
- const MS_PER_MINUTE$7 = 60000;
934
+ const MS_PER_MINUTE$8 = 60000;
935
935
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
936
936
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
937
937
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -2256,7 +2256,7 @@ class PersistCandleInstance {
2256
2256
  * @returns Promise resolving to candles in order, or null on cache miss
2257
2257
  */
2258
2258
  async readCandlesData(limit, sinceTimestamp, _untilTimestamp) {
2259
- const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
2259
+ const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
2260
2260
  const cachedCandles = [];
2261
2261
  for (let i = 0; i < limit; i++) {
2262
2262
  const expectedTimestamp = sinceTimestamp + i * stepMs;
@@ -2291,7 +2291,7 @@ class PersistCandleInstance {
2291
2291
  * @returns Promise that resolves when all writes are complete
2292
2292
  */
2293
2293
  async writeCandlesData(candles) {
2294
- const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
2294
+ const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
2295
2295
  const now = Date.now();
2296
2296
  for (const candle of candles) {
2297
2297
  const candleCloseTime = candle.timestamp + stepMs;
@@ -3177,12 +3177,12 @@ class PersistMeasureUtils {
3177
3177
  * @param key - Cache key within the bucket
3178
3178
  * @returns Promise that resolves when write is complete
3179
3179
  */
3180
- this.writeMeasureData = async (data, bucket, key) => {
3180
+ this.writeMeasureData = async (data, bucket, key, when) => {
3181
3181
  LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3182
3182
  const isInitial = !this.getMeasureStorage.has(bucket);
3183
3183
  const instance = this.getMeasureStorage(bucket);
3184
3184
  await instance.waitForInit(isInitial);
3185
- return instance.writeMeasureData(data, key);
3185
+ return instance.writeMeasureData(data, key, when);
3186
3186
  };
3187
3187
  /**
3188
3188
  * Soft-deletes a measure entry in the given bucket by setting `removed: true`.
@@ -3418,12 +3418,12 @@ class PersistIntervalUtils {
3418
3418
  * @param key - Marker key within the bucket
3419
3419
  * @returns Promise that resolves when write is complete
3420
3420
  */
3421
- this.writeIntervalData = async (data, bucket, key) => {
3421
+ this.writeIntervalData = async (data, bucket, key, when) => {
3422
3422
  LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3423
3423
  const isInitial = !this.getIntervalStorage.has(bucket);
3424
3424
  const instance = this.getIntervalStorage(bucket);
3425
3425
  await instance.waitForInit(isInitial);
3426
- return instance.writeIntervalData(data, key);
3426
+ return instance.writeIntervalData(data, key, when);
3427
3427
  };
3428
3428
  /**
3429
3429
  * Soft-deletes a marker in the given bucket by setting `removed: true`.
@@ -3561,7 +3561,7 @@ class PersistMemoryInstance {
3561
3561
  * @param memoryId - Memory entry identifier
3562
3562
  * @returns Promise that resolves when write is complete
3563
3563
  */
3564
- async writeMemoryData(data, memoryId) {
3564
+ async writeMemoryData(data, memoryId, _when) {
3565
3565
  await this._storage.writeValue(memoryId, data);
3566
3566
  }
3567
3567
  /**
@@ -3626,7 +3626,7 @@ class PersistMemoryDummyInstance {
3626
3626
  * No-op write (discards entry).
3627
3627
  * @returns Promise that resolves immediately
3628
3628
  */
3629
- async writeMemoryData(_data, _memoryId) { }
3629
+ async writeMemoryData(_data, _memoryId, _when) { }
3630
3630
  /**
3631
3631
  * No-op remove.
3632
3632
  * @returns Promise that resolves immediately
@@ -3719,19 +3719,20 @@ class PersistMemoryUtils {
3719
3719
  * Writes a memory entry for the given context.
3720
3720
  * Lazily initializes the instance on first access.
3721
3721
  *
3722
- * @param data - Entry data to persist
3722
+ * @param data - Entry data to persist (already carries `data.when`)
3723
3723
  * @param signalId - Signal identifier
3724
3724
  * @param bucketName - Bucket name
3725
3725
  * @param memoryId - Memory entry identifier
3726
+ * @param when - Logical timestamp this entry belongs to (duplicates `data.when` for API consistency)
3726
3727
  * @returns Promise that resolves when write is complete
3727
3728
  */
3728
- this.writeMemoryData = async (data, signalId, bucketName, memoryId) => {
3729
+ this.writeMemoryData = async (data, signalId, bucketName, memoryId, when) => {
3729
3730
  LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
3730
3731
  const key = `${signalId}:${bucketName}`;
3731
3732
  const isInitial = !this.getMemoryStorage.has(key);
3732
3733
  const instance = this.getMemoryStorage(signalId, bucketName);
3733
3734
  await instance.waitForInit(isInitial);
3734
- return instance.writeMemoryData(data, memoryId);
3735
+ return instance.writeMemoryData(data, memoryId, when);
3735
3736
  };
3736
3737
  /**
3737
3738
  * Soft-deletes a memory entry for the given context.
@@ -3895,7 +3896,7 @@ class PersistRecentInstance {
3895
3896
  * @param signalRow - Recent signal data to persist
3896
3897
  * @returns Promise that resolves when write is complete
3897
3898
  */
3898
- async writeRecentData(signalRow) {
3899
+ async writeRecentData(signalRow, _when) {
3899
3900
  await this._storage.writeValue(this.symbol, signalRow);
3900
3901
  }
3901
3902
  }
@@ -3923,7 +3924,7 @@ class PersistRecentDummyInstance {
3923
3924
  * No-op write (discards recent signal).
3924
3925
  * @returns Promise that resolves immediately
3925
3926
  */
3926
- async writeRecentData(_signalRow) { }
3927
+ async writeRecentData(_signalRow, _when) { }
3927
3928
  }
3928
3929
  /**
3929
3930
  * Utility class for managing recent signal persistence.
@@ -3970,21 +3971,22 @@ class PersistRecentUtils {
3970
3971
  * Writes the latest recent signal for the given context.
3971
3972
  * Lazily initializes the instance on first access.
3972
3973
  *
3973
- * @param signalRow - Recent signal data to persist
3974
+ * @param signalRow - Recent signal data to persist (already carries `signalRow.timestamp`)
3974
3975
  * @param symbol - Trading pair symbol
3975
3976
  * @param strategyName - Strategy identifier
3976
3977
  * @param exchangeName - Exchange identifier
3977
3978
  * @param frameName - Frame identifier (may be empty)
3978
3979
  * @param backtest - True for backtest mode, false for live mode
3980
+ * @param when - Logical timestamp this signal belongs to (duplicates `signalRow.timestamp` for API consistency)
3979
3981
  * @returns Promise that resolves when write is complete
3980
3982
  */
3981
- this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
3983
+ this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest, when) => {
3982
3984
  LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
3983
3985
  const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
3984
3986
  const isInitial = !this.getStorage.has(key);
3985
3987
  const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
3986
3988
  await instance.waitForInit(isInitial);
3987
- return instance.writeRecentData(signalRow);
3989
+ return instance.writeRecentData(signalRow, when);
3988
3990
  };
3989
3991
  }
3990
3992
  /**
@@ -4098,7 +4100,7 @@ class PersistStateInstance {
4098
4100
  * @param data - State data to persist
4099
4101
  * @returns Promise that resolves when write is complete
4100
4102
  */
4101
- async writeStateData(data) {
4103
+ async writeStateData(data, _when) {
4102
4104
  await this._storage.writeValue(this.bucketName, data);
4103
4105
  }
4104
4106
  /**
@@ -4131,7 +4133,7 @@ class PersistStateDummyInstance {
4131
4133
  * No-op write (discards state).
4132
4134
  * @returns Promise that resolves immediately
4133
4135
  */
4134
- async writeStateData(_data) { }
4136
+ async writeStateData(_data, _when) { }
4135
4137
  /**
4136
4138
  * No-op dispose.
4137
4139
  */
@@ -4196,18 +4198,19 @@ class PersistStateUtils {
4196
4198
  * Writes state for the given context.
4197
4199
  * Lazily initializes the instance on first access.
4198
4200
  *
4199
- * @param data - State data to persist
4201
+ * @param data - State data to persist (already carries `data.when`)
4200
4202
  * @param signalId - Signal identifier
4201
4203
  * @param bucketName - Bucket name
4204
+ * @param when - Logical timestamp this value belongs to (duplicates `data.when` for API consistency)
4202
4205
  * @returns Promise that resolves when write is complete
4203
4206
  */
4204
- this.writeStateData = async (data, signalId, bucketName) => {
4207
+ this.writeStateData = async (data, signalId, bucketName, when) => {
4205
4208
  LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
4206
4209
  const key = `${signalId}:${bucketName}`;
4207
4210
  const isInitial = !this.getStateStorage.has(key);
4208
4211
  const instance = this.getStateStorage(signalId, bucketName);
4209
4212
  await instance.waitForInit(isInitial);
4210
- return instance.writeStateData(data);
4213
+ return instance.writeStateData(data, when);
4211
4214
  };
4212
4215
  /**
4213
4216
  * Switches to PersistStateDummyInstance (all operations are no-ops).
@@ -4317,7 +4320,7 @@ class PersistSessionInstance {
4317
4320
  * @param data - Session data to persist
4318
4321
  * @returns Promise that resolves when write is complete
4319
4322
  */
4320
- async writeSessionData(data) {
4323
+ async writeSessionData(data, _when) {
4321
4324
  await this._storage.writeValue(this.frameName, data);
4322
4325
  }
4323
4326
  /**
@@ -4350,7 +4353,7 @@ class PersistSessionDummyInstance {
4350
4353
  * No-op write (discards session data).
4351
4354
  * @returns Promise that resolves immediately
4352
4355
  */
4353
- async writeSessionData(_data) { }
4356
+ async writeSessionData(_data, _when) { }
4354
4357
  /**
4355
4358
  * No-op dispose.
4356
4359
  */
@@ -4418,19 +4421,20 @@ class PersistSessionUtils {
4418
4421
  * Writes session data for the given context.
4419
4422
  * Lazily initializes the instance on first access.
4420
4423
  *
4421
- * @param data - Session data to persist
4424
+ * @param data - Session data to persist (already carries `data.when`)
4422
4425
  * @param strategyName - Strategy identifier
4423
4426
  * @param exchangeName - Exchange identifier
4424
4427
  * @param frameName - Frame identifier
4428
+ * @param when - Logical timestamp this value belongs to (duplicates `data.when` for API consistency)
4425
4429
  * @returns Promise that resolves when write is complete
4426
4430
  */
4427
- this.writeSessionData = async (data, strategyName, exchangeName, frameName) => {
4431
+ this.writeSessionData = async (data, strategyName, exchangeName, frameName, when) => {
4428
4432
  LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
4429
4433
  const key = `${strategyName}:${exchangeName}:${frameName}`;
4430
4434
  const isInitial = !this.getSessionStorage.has(key);
4431
4435
  const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
4432
4436
  await instance.waitForInit(isInitial);
4433
- return instance.writeSessionData(data);
4437
+ return instance.writeSessionData(data, when);
4434
4438
  };
4435
4439
  /**
4436
4440
  * Switches to PersistSessionDummyInstance (all operations are no-ops).
@@ -4609,8 +4613,8 @@ class CandleUtils {
4609
4613
  }
4610
4614
  const Candle = new CandleUtils();
4611
4615
 
4612
- const MS_PER_MINUTE$6 = 60000;
4613
- const INTERVAL_MINUTES$8 = {
4616
+ const MS_PER_MINUTE$7 = 60000;
4617
+ const INTERVAL_MINUTES$9 = {
4614
4618
  "1m": 1,
4615
4619
  "3m": 3,
4616
4620
  "5m": 5,
@@ -4641,7 +4645,7 @@ const INTERVAL_MINUTES$8 = {
4641
4645
  * @returns Aligned timestamp rounded down to interval boundary
4642
4646
  */
4643
4647
  const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
4644
- const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
4648
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$7;
4645
4649
  return Math.floor(timestamp / intervalMs) * intervalMs;
4646
4650
  };
4647
4651
  /**
@@ -4795,9 +4799,9 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
4795
4799
  * @returns Promise resolving to array of candle data
4796
4800
  */
4797
4801
  const GET_CANDLES_FN$1 = async (dto, since, self) => {
4798
- const step = INTERVAL_MINUTES$8[dto.interval];
4802
+ const step = INTERVAL_MINUTES$9[dto.interval];
4799
4803
  const sinceTimestamp = since.getTime();
4800
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
4804
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$7;
4801
4805
  await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
4802
4806
  try {
4803
4807
  // Try to read from cache first
@@ -4911,11 +4915,11 @@ class ClientExchange {
4911
4915
  interval,
4912
4916
  limit,
4913
4917
  });
4914
- const step = INTERVAL_MINUTES$8[interval];
4918
+ const step = INTERVAL_MINUTES$9[interval];
4915
4919
  if (!step) {
4916
4920
  throw new Error(`ClientExchange unknown interval=${interval}`);
4917
4921
  }
4918
- const stepMs = step * MS_PER_MINUTE$6;
4922
+ const stepMs = step * MS_PER_MINUTE$7;
4919
4923
  // Align when down to interval boundary
4920
4924
  const whenTimestamp = this.params.execution.context.when.getTime();
4921
4925
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
@@ -4992,11 +4996,11 @@ class ClientExchange {
4992
4996
  if (!this.params.execution.context.backtest) {
4993
4997
  throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
4994
4998
  }
4995
- const step = INTERVAL_MINUTES$8[interval];
4999
+ const step = INTERVAL_MINUTES$9[interval];
4996
5000
  if (!step) {
4997
5001
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
4998
5002
  }
4999
- const stepMs = step * MS_PER_MINUTE$6;
5003
+ const stepMs = step * MS_PER_MINUTE$7;
5000
5004
  const now = Date.now();
5001
5005
  // Align when down to interval boundary
5002
5006
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -5181,11 +5185,11 @@ class ClientExchange {
5181
5185
  sDate,
5182
5186
  eDate,
5183
5187
  });
5184
- const step = INTERVAL_MINUTES$8[interval];
5188
+ const step = INTERVAL_MINUTES$9[interval];
5185
5189
  if (!step) {
5186
5190
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
5187
5191
  }
5188
- const stepMs = step * MS_PER_MINUTE$6;
5192
+ const stepMs = step * MS_PER_MINUTE$7;
5189
5193
  const whenTimestamp = this.params.execution.context.when.getTime();
5190
5194
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
5191
5195
  let sinceTimestamp;
@@ -5314,7 +5318,7 @@ class ClientExchange {
5314
5318
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
5315
5319
  const to = new Date(alignedTo);
5316
5320
  const from = new Date(alignedTo -
5317
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
5321
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$7);
5318
5322
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
5319
5323
  }
5320
5324
  /**
@@ -5343,7 +5347,7 @@ class ClientExchange {
5343
5347
  const whenTimestamp = this.params.execution.context.when.getTime();
5344
5348
  // Align to 1-minute boundary to prevent look-ahead bias
5345
5349
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
5346
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
5350
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$7 - MS_PER_MINUTE$7;
5347
5351
  // No limit: fetch a single window and return as-is
5348
5352
  if (limit === undefined) {
5349
5353
  const to = new Date(alignedTo);
@@ -6297,7 +6301,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
6297
6301
  }
6298
6302
  };
6299
6303
 
6300
- const INTERVAL_MINUTES$7 = {
6304
+ const INTERVAL_MINUTES$8 = {
6301
6305
  "1m": 1,
6302
6306
  "3m": 3,
6303
6307
  "5m": 5,
@@ -6718,7 +6722,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
6718
6722
  }
6719
6723
  const currentTime = self.params.execution.context.when.getTime();
6720
6724
  {
6721
- const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
6725
+ const intervalMinutes = INTERVAL_MINUTES$8[self.params.interval];
6722
6726
  const intervalMs = intervalMinutes * 60 * 1000;
6723
6727
  const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
6724
6728
  // Проверяем что наступил новый интервал (по aligned timestamp)
@@ -13713,7 +13717,7 @@ class StrategyConnectionService {
13713
13717
  * Maps FrameInterval to minutes for timestamp calculation.
13714
13718
  * Used to generate timeframe arrays with proper spacing.
13715
13719
  */
13716
- const INTERVAL_MINUTES$6 = {
13720
+ const INTERVAL_MINUTES$7 = {
13717
13721
  "1m": 1,
13718
13722
  "3m": 3,
13719
13723
  "5m": 5,
@@ -13767,7 +13771,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
13767
13771
  symbol,
13768
13772
  });
13769
13773
  const { interval, startDate, endDate } = self.params;
13770
- const intervalMinutes = INTERVAL_MINUTES$6[interval];
13774
+ const intervalMinutes = INTERVAL_MINUTES$7[interval];
13771
13775
  if (!intervalMinutes) {
13772
13776
  throw new Error(`ClientFrame unknown interval: ${interval}`);
13773
13777
  }
@@ -14132,8 +14136,8 @@ const get = (object, path) => {
14132
14136
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
14133
14137
  };
14134
14138
 
14135
- const MS_PER_MINUTE$5 = 60000;
14136
- const INTERVAL_MINUTES$5 = {
14139
+ const MS_PER_MINUTE$6 = 60000;
14140
+ const INTERVAL_MINUTES$6 = {
14137
14141
  "1m": 1,
14138
14142
  "3m": 3,
14139
14143
  "5m": 5,
@@ -14164,11 +14168,11 @@ const INTERVAL_MINUTES$5 = {
14164
14168
  * @returns New Date aligned down to interval boundary
14165
14169
  */
14166
14170
  const alignToInterval = (date, interval) => {
14167
- const minutes = INTERVAL_MINUTES$5[interval];
14171
+ const minutes = INTERVAL_MINUTES$6[interval];
14168
14172
  if (minutes === undefined) {
14169
14173
  throw new Error(`alignToInterval: unknown interval=${interval}`);
14170
14174
  }
14171
- const intervalMs = minutes * MS_PER_MINUTE$5;
14175
+ const intervalMs = minutes * MS_PER_MINUTE$6;
14172
14176
  return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
14173
14177
  };
14174
14178
 
@@ -19650,8 +19654,8 @@ class BacktestLogicPrivateService {
19650
19654
  }
19651
19655
 
19652
19656
  const EMITTER_CHECK_INTERVAL = 5000;
19653
- const MS_PER_MINUTE$4 = 60000;
19654
- const INTERVAL_MINUTES$4 = {
19657
+ const MS_PER_MINUTE$5 = 60000;
19658
+ const INTERVAL_MINUTES$5 = {
19655
19659
  "1m": 1,
19656
19660
  "3m": 3,
19657
19661
  "5m": 5,
@@ -19666,7 +19670,7 @@ const INTERVAL_MINUTES$4 = {
19666
19670
  };
19667
19671
  const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
19668
19672
  const tickSubject = new Subject();
19669
- const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
19673
+ const intervalMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$5;
19670
19674
  {
19671
19675
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
19672
19676
  Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -35172,7 +35176,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
35172
35176
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
35173
35177
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
35174
35178
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
35175
- const MS_PER_MINUTE$3 = 60000;
35179
+ const MS_PER_MINUTE$4 = 60000;
35176
35180
  /**
35177
35181
  * Gets current timestamp from execution context if available.
35178
35182
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -35239,7 +35243,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
35239
35243
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
35240
35244
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
35241
35245
  };
35242
- const INTERVAL_MINUTES$3 = {
35246
+ const INTERVAL_MINUTES$4 = {
35243
35247
  "1m": 1,
35244
35248
  "3m": 3,
35245
35249
  "5m": 5,
@@ -35270,7 +35274,7 @@ const INTERVAL_MINUTES$3 = {
35270
35274
  * @returns Aligned timestamp rounded down to interval boundary
35271
35275
  */
35272
35276
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
35273
- const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
35277
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
35274
35278
  return Math.floor(timestamp / intervalMs) * intervalMs;
35275
35279
  };
35276
35280
  /**
@@ -35410,11 +35414,11 @@ class ExchangeInstance {
35410
35414
  limit,
35411
35415
  });
35412
35416
  const getCandles = this._methods.getCandles;
35413
- const step = INTERVAL_MINUTES$3[interval];
35417
+ const step = INTERVAL_MINUTES$4[interval];
35414
35418
  if (!step) {
35415
35419
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
35416
35420
  }
35417
- const stepMs = step * MS_PER_MINUTE$3;
35421
+ const stepMs = step * MS_PER_MINUTE$4;
35418
35422
  // Align when down to interval boundary
35419
35423
  const when = await GET_TIMESTAMP_FN();
35420
35424
  const whenTimestamp = when.getTime();
@@ -35628,7 +35632,7 @@ class ExchangeInstance {
35628
35632
  const when = await GET_TIMESTAMP_FN();
35629
35633
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
35630
35634
  const to = new Date(alignedTo);
35631
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
35635
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
35632
35636
  const isBacktest = await GET_BACKTEST_FN();
35633
35637
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
35634
35638
  };
@@ -35660,7 +35664,7 @@ class ExchangeInstance {
35660
35664
  const when = await GET_TIMESTAMP_FN();
35661
35665
  // Align to 1-minute boundary to prevent look-ahead bias
35662
35666
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
35663
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
35667
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$4 - MS_PER_MINUTE$4;
35664
35668
  const isBacktest = await GET_BACKTEST_FN();
35665
35669
  // No limit: fetch a single window and return as-is
35666
35670
  if (limit === undefined) {
@@ -35723,11 +35727,11 @@ class ExchangeInstance {
35723
35727
  sDate,
35724
35728
  eDate,
35725
35729
  });
35726
- const step = INTERVAL_MINUTES$3[interval];
35730
+ const step = INTERVAL_MINUTES$4[interval];
35727
35731
  if (!step) {
35728
35732
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
35729
35733
  }
35730
- const stepMs = step * MS_PER_MINUTE$3;
35734
+ const stepMs = step * MS_PER_MINUTE$4;
35731
35735
  const when = await GET_TIMESTAMP_FN();
35732
35736
  const nowTimestamp = when.getTime();
35733
35737
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -36039,8 +36043,8 @@ const Exchange = new ExchangeUtils();
36039
36043
 
36040
36044
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
36041
36045
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
36042
- const MS_PER_MINUTE$2 = 60000;
36043
- const INTERVAL_MINUTES$2 = {
36046
+ const MS_PER_MINUTE$3 = 60000;
36047
+ const INTERVAL_MINUTES$3 = {
36044
36048
  "1m": 1,
36045
36049
  "3m": 3,
36046
36050
  "5m": 5,
@@ -36054,7 +36058,7 @@ const INTERVAL_MINUTES$2 = {
36054
36058
  "1d": 1440,
36055
36059
  };
36056
36060
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
36057
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
36061
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
36058
36062
  return Math.floor(timestamp / intervalMs) * intervalMs;
36059
36063
  };
36060
36064
  const BAR_LENGTH = 30;
@@ -36079,11 +36083,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
36079
36083
  async function checkCandles(params) {
36080
36084
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
36081
36085
  backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
36082
- const step = INTERVAL_MINUTES$2[interval];
36086
+ const step = INTERVAL_MINUTES$3[interval];
36083
36087
  if (!step) {
36084
36088
  throw new Error(`checkCandles: unsupported interval=${interval}`);
36085
36089
  }
36086
- const stepMs = step * MS_PER_MINUTE$2;
36090
+ const stepMs = step * MS_PER_MINUTE$3;
36087
36091
  const dir = join(baseDir, exchangeName, symbol, interval);
36088
36092
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
36089
36093
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -36153,11 +36157,11 @@ async function warmCandles(params) {
36153
36157
  from,
36154
36158
  to,
36155
36159
  });
36156
- const step = INTERVAL_MINUTES$2[interval];
36160
+ const step = INTERVAL_MINUTES$3[interval];
36157
36161
  if (!step) {
36158
36162
  throw new Error(`warmCandles: unsupported interval=${interval}`);
36159
36163
  }
36160
- const stepMs = step * MS_PER_MINUTE$2;
36164
+ const stepMs = step * MS_PER_MINUTE$3;
36161
36165
  const instance = new ExchangeInstance(exchangeName);
36162
36166
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
36163
36167
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -48500,18 +48504,21 @@ class RecentPersistBacktestUtils {
48500
48504
  backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
48501
48505
  signalId: event.data.id,
48502
48506
  });
48503
- await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
48507
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest, new Date(event.data.timestamp));
48504
48508
  };
48505
48509
  /**
48506
48510
  * Retrieves the latest persisted signal for the given context.
48511
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48512
+ * (look-ahead bias protection).
48507
48513
  * @param symbol - Trading pair symbol
48508
48514
  * @param strategyName - Strategy identifier
48509
48515
  * @param exchangeName - Exchange identifier
48510
48516
  * @param frameName - Frame identifier
48511
48517
  * @param backtest - Flag indicating if the context is backtest or live
48512
- * @returns The latest signal or null if not found
48518
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48519
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48513
48520
  */
48514
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48521
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48515
48522
  backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
48516
48523
  symbol,
48517
48524
  strategyName,
@@ -48519,20 +48526,26 @@ class RecentPersistBacktestUtils {
48519
48526
  frameName,
48520
48527
  backtest: backtest$1,
48521
48528
  });
48522
- return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48529
+ const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48530
+ if (!signal || signal.timestamp > when.getTime()) {
48531
+ return null;
48532
+ }
48533
+ return signal;
48523
48534
  };
48524
48535
  /**
48525
48536
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48526
- * @param timestamp - Current timestamp in milliseconds
48537
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48538
+ * the requested one is treated as not yet visible.
48539
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48527
48540
  * @param symbol - Trading pair symbol
48528
48541
  * @param strategyName - Strategy identifier
48529
48542
  * @param exchangeName - Exchange identifier
48530
48543
  * @param frameName - Frame identifier
48531
48544
  * @param backtest - Flag indicating if the context is backtest or live
48532
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48545
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48533
48546
  */
48534
48547
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48535
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48548
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48536
48549
  if (!signal) {
48537
48550
  return null;
48538
48551
  }
@@ -48568,30 +48581,39 @@ class RecentMemoryBacktestUtils {
48568
48581
  };
48569
48582
  /**
48570
48583
  * Retrieves the latest in-memory signal for the given context.
48584
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48585
+ * (look-ahead bias protection).
48571
48586
  * @param symbol - Trading pair symbol
48572
48587
  * @param strategyName - Strategy identifier
48573
48588
  * @param exchangeName - Exchange identifier
48574
48589
  * @param frameName - Frame identifier
48575
48590
  * @param backtest - Flag indicating if the context is backtest or live
48576
- * @returns The latest signal or null if not found
48591
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48592
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48577
48593
  */
48578
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48594
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48579
48595
  const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
48580
48596
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
48581
- return this._signals.get(key) ?? null;
48597
+ const signal = this._signals.get(key) ?? null;
48598
+ if (!signal || signal.timestamp > when.getTime()) {
48599
+ return null;
48600
+ }
48601
+ return signal;
48582
48602
  };
48583
48603
  /**
48584
48604
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48585
- * @param timestamp - Current timestamp in milliseconds
48605
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48606
+ * the requested one is treated as not yet visible.
48607
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48586
48608
  * @param symbol - Trading pair symbol
48587
48609
  * @param strategyName - Strategy identifier
48588
48610
  * @param exchangeName - Exchange identifier
48589
48611
  * @param frameName - Frame identifier
48590
48612
  * @param backtest - Flag indicating if the context is backtest or live
48591
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48613
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48592
48614
  */
48593
48615
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48594
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48616
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48595
48617
  if (!signal) {
48596
48618
  return null;
48597
48619
  }
@@ -48619,18 +48641,21 @@ class RecentPersistLiveUtils {
48619
48641
  backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
48620
48642
  signalId: event.data.id,
48621
48643
  });
48622
- await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
48644
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest, new Date(event.data.timestamp));
48623
48645
  };
48624
48646
  /**
48625
48647
  * Retrieves the latest persisted signal for the given context.
48648
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48649
+ * (look-ahead bias protection).
48626
48650
  * @param symbol - Trading pair symbol
48627
48651
  * @param strategyName - Strategy identifier
48628
48652
  * @param exchangeName - Exchange identifier
48629
48653
  * @param frameName - Frame identifier
48630
48654
  * @param backtest - Flag indicating if the context is backtest or live
48631
- * @returns The latest signal or null if not found
48655
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48656
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48632
48657
  */
48633
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48658
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48634
48659
  backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
48635
48660
  symbol,
48636
48661
  strategyName,
@@ -48638,20 +48663,26 @@ class RecentPersistLiveUtils {
48638
48663
  frameName,
48639
48664
  backtest: backtest$1,
48640
48665
  });
48641
- return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48666
+ const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48667
+ if (!signal || signal.timestamp > when.getTime()) {
48668
+ return null;
48669
+ }
48670
+ return signal;
48642
48671
  };
48643
48672
  /**
48644
48673
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48645
- * @param timestamp - Current timestamp in milliseconds
48674
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48675
+ * the requested one is treated as not yet visible.
48676
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48646
48677
  * @param symbol - Trading pair symbol
48647
48678
  * @param strategyName - Strategy identifier
48648
48679
  * @param exchangeName - Exchange identifier
48649
48680
  * @param frameName - Frame identifier
48650
48681
  * @param backtest - Flag indicating if the context is backtest or live
48651
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48682
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48652
48683
  */
48653
48684
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48654
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48685
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48655
48686
  if (!signal) {
48656
48687
  return null;
48657
48688
  }
@@ -48687,30 +48718,39 @@ class RecentMemoryLiveUtils {
48687
48718
  };
48688
48719
  /**
48689
48720
  * Retrieves the latest in-memory signal for the given context.
48721
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48722
+ * (look-ahead bias protection).
48690
48723
  * @param symbol - Trading pair symbol
48691
48724
  * @param strategyName - Strategy identifier
48692
48725
  * @param exchangeName - Exchange identifier
48693
48726
  * @param frameName - Frame identifier
48694
48727
  * @param backtest - Flag indicating if the context is backtest or live
48695
- * @returns The latest signal or null if not found
48728
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48729
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48696
48730
  */
48697
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48731
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48698
48732
  const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
48699
48733
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
48700
- return this._signals.get(key) ?? null;
48734
+ const signal = this._signals.get(key) ?? null;
48735
+ if (!signal || signal.timestamp > when.getTime()) {
48736
+ return null;
48737
+ }
48738
+ return signal;
48701
48739
  };
48702
48740
  /**
48703
48741
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48704
- * @param timestamp - Current timestamp in milliseconds
48742
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48743
+ * the requested one is treated as not yet visible.
48744
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48705
48745
  * @param symbol - Trading pair symbol
48706
48746
  * @param strategyName - Strategy identifier
48707
48747
  * @param exchangeName - Exchange identifier
48708
48748
  * @param frameName - Frame identifier
48709
48749
  * @param backtest - Flag indicating if the context is backtest or live
48710
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48750
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48711
48751
  */
48712
48752
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48713
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48753
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48714
48754
  if (!signal) {
48715
48755
  return null;
48716
48756
  }
@@ -48750,9 +48790,10 @@ class RecentBacktestAdapter {
48750
48790
  * @param exchangeName - Exchange identifier
48751
48791
  * @param frameName - Frame identifier
48752
48792
  * @param backtest - Flag indicating if the context is backtest or live
48753
- * @returns The latest signal or null if not found
48793
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48794
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48754
48795
  */
48755
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48796
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48756
48797
  backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
48757
48798
  symbol,
48758
48799
  strategyName,
@@ -48760,18 +48801,20 @@ class RecentBacktestAdapter {
48760
48801
  frameName,
48761
48802
  backtest: backtest$1,
48762
48803
  });
48763
- return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48804
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
48764
48805
  };
48765
48806
  /**
48766
48807
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48767
- * Proxies call to the underlying storage adapter.
48768
- * @param timestamp - Current timestamp in milliseconds
48808
+ * Proxies call to the underlying storage adapter. `timestamp` doubles as the
48809
+ * look-ahead cutoff a signal whose `timestamp` exceeds the requested one is
48810
+ * treated as not yet visible.
48811
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48769
48812
  * @param symbol - Trading pair symbol
48770
48813
  * @param strategyName - Strategy identifier
48771
48814
  * @param exchangeName - Exchange identifier
48772
48815
  * @param frameName - Frame identifier
48773
48816
  * @param backtest - Flag indicating if the context is backtest or live
48774
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48817
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48775
48818
  */
48776
48819
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
48777
48820
  backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
@@ -48782,7 +48825,7 @@ class RecentBacktestAdapter {
48782
48825
  backtest: backtest$1,
48783
48826
  timestamp,
48784
48827
  });
48785
- const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48828
+ const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
48786
48829
  if (!signal) {
48787
48830
  return null;
48788
48831
  }
@@ -48854,9 +48897,10 @@ class RecentLiveAdapter {
48854
48897
  * @param exchangeName - Exchange identifier
48855
48898
  * @param frameName - Frame identifier
48856
48899
  * @param backtest - Flag indicating if the context is backtest or live
48857
- * @returns The latest signal or null if not found
48900
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48901
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48858
48902
  */
48859
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48903
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48860
48904
  backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
48861
48905
  symbol,
48862
48906
  strategyName,
@@ -48864,18 +48908,20 @@ class RecentLiveAdapter {
48864
48908
  frameName,
48865
48909
  backtest: backtest$1,
48866
48910
  });
48867
- return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48911
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
48868
48912
  };
48869
48913
  /**
48870
48914
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48871
- * Proxies call to the underlying storage adapter.
48872
- * @param timestamp - Current timestamp in milliseconds
48915
+ * Proxies call to the underlying storage adapter. `timestamp` doubles as the
48916
+ * look-ahead cutoff a signal whose `timestamp` exceeds the requested one is
48917
+ * treated as not yet visible.
48918
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48873
48919
  * @param symbol - Trading pair symbol
48874
48920
  * @param strategyName - Strategy identifier
48875
48921
  * @param exchangeName - Exchange identifier
48876
48922
  * @param frameName - Frame identifier
48877
48923
  * @param backtest - Flag indicating if the context is backtest or live
48878
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48924
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48879
48925
  */
48880
48926
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
48881
48927
  backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
@@ -48886,7 +48932,7 @@ class RecentLiveAdapter {
48886
48932
  backtest: backtest$1,
48887
48933
  timestamp,
48888
48934
  });
48889
- const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48935
+ const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
48890
48936
  if (!signal) {
48891
48937
  return null;
48892
48938
  }
@@ -48976,14 +49022,16 @@ class RecentAdapter {
48976
49022
  /**
48977
49023
  * Retrieves the latest active signal for the given symbol and context.
48978
49024
  * Searches backtest storage first, then live storage.
49025
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
49026
+ * (look-ahead bias protection).
48979
49027
  *
48980
49028
  * @param symbol - Trading pair symbol
48981
49029
  * @param context - Execution context with strategyName, exchangeName, and frameName
48982
- * @param backtest - Flag indicating if the context is backtest or live
48983
- * @returns The latest signal or null if not found
49030
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
49031
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48984
49032
  * @throws Error if RecentAdapter is not enabled
48985
49033
  */
48986
- this.getLatestSignal = async (symbol, context) => {
49034
+ this.getLatestSignal = async (symbol, context, when) => {
48987
49035
  backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
48988
49036
  symbol,
48989
49037
  context,
@@ -48992,10 +49040,10 @@ class RecentAdapter {
48992
49040
  throw new Error("RecentAdapter is not enabled. Call enable() first.");
48993
49041
  }
48994
49042
  let result = null;
48995
- if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
49043
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
48996
49044
  return result;
48997
49045
  }
48998
- if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
49046
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
48999
49047
  return result;
49000
49048
  }
49001
49049
  return null;
@@ -49003,13 +49051,16 @@ class RecentAdapter {
49003
49051
  /**
49004
49052
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
49005
49053
  * Searches backtest storage first, then live storage.
49006
- * @param timestamp - Current timestamp in milliseconds
49054
+ * `when` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
49055
+ * `when.getTime()` is treated as not yet visible — and as the "now" against
49056
+ * which elapsed minutes are computed.
49007
49057
  * @param symbol - Trading pair symbol
49008
49058
  * @param context - Execution context with strategyName, exchangeName, and frameName
49009
- * @returns Whole minutes since the latest signal was created, or null if no signal found
49059
+ * @param when - Logical timestamp at which the read is happening (look-ahead cutoff + "now")
49060
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
49010
49061
  * @throws Error if RecentAdapter is not enabled
49011
49062
  */
49012
- this.getMinutesSinceLatestSignalCreated = async (symbol, context) => {
49063
+ this.getMinutesSinceLatestSignalCreated = async (symbol, context, when) => {
49013
49064
  backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
49014
49065
  symbol,
49015
49066
  context,
@@ -49018,13 +49069,11 @@ class RecentAdapter {
49018
49069
  throw new Error("RecentAdapter is not enabled. Call enable() first.");
49019
49070
  }
49020
49071
  let signal = null;
49021
- if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
49022
- const timestamp = await backtest.timeMetaService.getTimestamp(symbol, context, true);
49023
- return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
49072
+ if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
49073
+ return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
49024
49074
  }
49025
- if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
49026
- const timestamp = await backtest.timeMetaService.getTimestamp(symbol, context, false);
49027
- return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
49075
+ if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
49076
+ return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
49028
49077
  }
49029
49078
  return null;
49030
49079
  };
@@ -49089,41 +49138,54 @@ class StateLocalInstance {
49089
49138
  this.initialValue = initialValue;
49090
49139
  this.signalId = signalId;
49091
49140
  this.bucketName = bucketName;
49141
+ this._when = 0;
49092
49142
  /**
49093
49143
  * Initializes _value from initialValue - local state needs no async setup.
49094
49144
  * @returns Promise that resolves immediately
49095
49145
  */
49096
49146
  this.waitForInit = singleshot(async (_initial) => {
49097
49147
  this._value = this.initialValue;
49148
+ this._when = 0;
49098
49149
  });
49099
49150
  /**
49100
49151
  * Update the in-memory state value.
49152
+ * Records `when` so future reads with a smaller `when` see `initialValue`.
49153
+ * The dispatch updater receives the look-ahead-guarded current value.
49101
49154
  * @param dispatch - New value or updater function receiving current value
49155
+ * @param when - Logical timestamp this value belongs to
49102
49156
  * @returns Updated state value
49103
49157
  */
49104
- this.setState = queued(async (dispatch) => {
49158
+ this.setState = queued(async (dispatch, when) => {
49105
49159
  backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
49106
49160
  signalId: this.signalId,
49107
49161
  bucketName: this.bucketName,
49108
49162
  });
49109
49163
  if (typeof dispatch === "function") {
49110
- this._value = await dispatch(this._value);
49164
+ const prev = this._when > when.getTime() ? this.initialValue : this._value;
49165
+ this._value = await dispatch(prev);
49111
49166
  }
49112
49167
  else {
49113
49168
  this._value = dispatch;
49114
49169
  }
49170
+ this._when = when.getTime();
49115
49171
  return this._value;
49116
49172
  });
49117
49173
  }
49118
49174
  /**
49119
49175
  * Read the current in-memory state value.
49176
+ * Returns `initialValue` when the stored `when` is greater than the requested `when`
49177
+ * (look-ahead bias protection).
49178
+ * @param when - Logical timestamp at which the read is happening
49120
49179
  * @returns Current state value
49121
49180
  */
49122
- async getState() {
49181
+ async getState(when) {
49123
49182
  backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
49124
49183
  signalId: this.signalId,
49125
49184
  bucketName: this.bucketName,
49126
49185
  });
49186
+ if (this._when > when.getTime()) {
49187
+ return this.initialValue;
49188
+ }
49127
49189
  return this._value;
49128
49190
  }
49129
49191
  /** Releases resources held by this instance. */
@@ -49157,14 +49219,14 @@ class StateDummyInstance {
49157
49219
  * No-op read - always returns initialValue.
49158
49220
  * @returns initialValue
49159
49221
  */
49160
- async getState() {
49222
+ async getState(_when) {
49161
49223
  return this.initialValue;
49162
49224
  }
49163
49225
  /**
49164
49226
  * No-op write - discards the value and returns initialValue.
49165
49227
  * @returns initialValue
49166
49228
  */
49167
- async setState(_dispatch) {
49229
+ async setState(_dispatch, _when) {
49168
49230
  return this.initialValue;
49169
49231
  }
49170
49232
  /** No-op. */
@@ -49190,6 +49252,7 @@ class StatePersistInstance {
49190
49252
  this.initialValue = initialValue;
49191
49253
  this.signalId = signalId;
49192
49254
  this.bucketName = bucketName;
49255
+ this._when = 0;
49193
49256
  /**
49194
49257
  * Initialize persistence storage and restore state from disk.
49195
49258
  * @param initial - Whether this is the first initialization
@@ -49204,40 +49267,54 @@ class StatePersistInstance {
49204
49267
  const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
49205
49268
  if (data) {
49206
49269
  this._value = data.data;
49270
+ this._when = data.when;
49207
49271
  return;
49208
49272
  }
49209
49273
  this._value = this.initialValue;
49274
+ this._when = 0;
49210
49275
  });
49211
49276
  /**
49212
49277
  * Update state and persist to disk atomically.
49278
+ * A write with a smaller `when` overwrites an existing record — that lets a
49279
+ * restarted backtest reset live-written state without breaking live.
49280
+ * The dispatch updater receives the look-ahead-guarded current value.
49213
49281
  * @param dispatch - New value or updater function receiving current value
49282
+ * @param when - Logical timestamp this value belongs to
49214
49283
  * @returns Updated state value
49215
49284
  */
49216
- this.setState = queued(async (dispatch) => {
49285
+ this.setState = queued(async (dispatch, when) => {
49217
49286
  backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
49218
49287
  signalId: this.signalId,
49219
49288
  bucketName: this.bucketName,
49220
49289
  });
49221
49290
  if (typeof dispatch === "function") {
49222
- this._value = await dispatch(this._value);
49291
+ const prev = this._when > when.getTime() ? this.initialValue : this._value;
49292
+ this._value = await dispatch(prev);
49223
49293
  }
49224
49294
  else {
49225
49295
  this._value = dispatch;
49226
49296
  }
49297
+ this._when = when.getTime();
49227
49298
  const id = CREATE_KEY_FN$6(this.signalId, this.bucketName);
49228
- await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
49299
+ await PersistStateAdapter.writeStateData({ id, data: this._value, when: this._when }, this.signalId, this.bucketName, when);
49229
49300
  return this._value;
49230
49301
  });
49231
49302
  }
49232
49303
  /**
49233
49304
  * Read the current persisted state value.
49305
+ * Returns `initialValue` when the stored `when` is greater than the requested `when`
49306
+ * (look-ahead bias protection).
49307
+ * @param when - Logical timestamp at which the read is happening
49234
49308
  * @returns Current state value
49235
49309
  */
49236
- async getState() {
49310
+ async getState(when) {
49237
49311
  backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
49238
49312
  signalId: this.signalId,
49239
49313
  bucketName: this.bucketName,
49240
49314
  });
49315
+ if (this._when > when.getTime()) {
49316
+ return this.initialValue;
49317
+ }
49241
49318
  return this._value;
49242
49319
  }
49243
49320
  /** Releases resources held by this instance. */
@@ -49290,6 +49367,7 @@ class StateBacktestAdapter {
49290
49367
  * @param dto.signalId - Signal identifier
49291
49368
  * @param dto.bucketName - Bucket name
49292
49369
  * @param dto.initialValue - Default value when no persisted state exists
49370
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49293
49371
  * @returns Current state value
49294
49372
  */
49295
49373
  this.getState = async (dto) => {
@@ -49301,7 +49379,7 @@ class StateBacktestAdapter {
49301
49379
  const isInitial = !this.getInstance.has(key);
49302
49380
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49303
49381
  await instance.waitForInit(isInitial);
49304
- return await instance.getState();
49382
+ return await instance.getState(dto.when);
49305
49383
  };
49306
49384
  /**
49307
49385
  * Update the state value for a signal.
@@ -49309,6 +49387,7 @@ class StateBacktestAdapter {
49309
49387
  * @param dto.signalId - Signal identifier
49310
49388
  * @param dto.bucketName - Bucket name
49311
49389
  * @param dto.initialValue - Default value when no persisted state exists
49390
+ * @param dto.when - Logical timestamp this value belongs to
49312
49391
  * @returns Updated state value
49313
49392
  */
49314
49393
  this.setState = async (dispatch, dto) => {
@@ -49320,7 +49399,7 @@ class StateBacktestAdapter {
49320
49399
  const isInitial = !this.getInstance.has(key);
49321
49400
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49322
49401
  await instance.waitForInit(isInitial);
49323
- return await instance.setState(dispatch);
49402
+ return await instance.setState(dispatch, dto.when);
49324
49403
  };
49325
49404
  /**
49326
49405
  * Switches to in-memory adapter (default).
@@ -49405,6 +49484,7 @@ class StateLiveAdapter {
49405
49484
  * @param dto.signalId - Signal identifier
49406
49485
  * @param dto.bucketName - Bucket name
49407
49486
  * @param dto.initialValue - Default value when no persisted state exists
49487
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49408
49488
  * @returns Current state value
49409
49489
  */
49410
49490
  this.getState = async (dto) => {
@@ -49416,7 +49496,7 @@ class StateLiveAdapter {
49416
49496
  const isInitial = !this.getInstance.has(key);
49417
49497
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49418
49498
  await instance.waitForInit(isInitial);
49419
- return await instance.getState();
49499
+ return await instance.getState(dto.when);
49420
49500
  };
49421
49501
  /**
49422
49502
  * Update the state value for a signal.
@@ -49424,6 +49504,7 @@ class StateLiveAdapter {
49424
49504
  * @param dto.signalId - Signal identifier
49425
49505
  * @param dto.bucketName - Bucket name
49426
49506
  * @param dto.initialValue - Default value when no persisted state exists
49507
+ * @param dto.when - Logical timestamp this value belongs to
49427
49508
  * @returns Updated state value
49428
49509
  */
49429
49510
  this.setState = async (dispatch, dto) => {
@@ -49435,7 +49516,7 @@ class StateLiveAdapter {
49435
49516
  const isInitial = !this.getInstance.has(key);
49436
49517
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49437
49518
  await instance.waitForInit(isInitial);
49438
- return await instance.setState(dispatch);
49519
+ return await instance.setState(dispatch, dto.when);
49439
49520
  };
49440
49521
  /**
49441
49522
  * Switches to in-memory adapter.
@@ -49532,6 +49613,7 @@ class StateAdapter {
49532
49613
  * @param dto.bucketName - Bucket name
49533
49614
  * @param dto.initialValue - Default value when no persisted state exists
49534
49615
  * @param dto.backtest - Flag indicating if the context is backtest or live
49616
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49535
49617
  * @returns Current state value
49536
49618
  * @throws Error if adapter is not enabled
49537
49619
  */
@@ -49557,6 +49639,7 @@ class StateAdapter {
49557
49639
  * @param dto.bucketName - Bucket name
49558
49640
  * @param dto.initialValue - Default value when no persisted state exists
49559
49641
  * @param dto.backtest - Flag indicating if the context is backtest or live
49642
+ * @param dto.when - Logical timestamp this value belongs to
49560
49643
  * @returns Updated state value
49561
49644
  * @throws Error if adapter is not enabled
49562
49645
  */
@@ -49630,8 +49713,9 @@ async function getLatestSignal(symbol) {
49630
49713
  if (!MethodContextService.hasContext()) {
49631
49714
  throw new Error("getLatestSignal requires a method context");
49632
49715
  }
49716
+ const { when } = backtest.executionContextService.context;
49633
49717
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49634
- return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
49718
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName }, when);
49635
49719
  }
49636
49720
  /**
49637
49721
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
@@ -49666,8 +49750,9 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
49666
49750
  if (!MethodContextService.hasContext()) {
49667
49751
  throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
49668
49752
  }
49753
+ const { when } = backtest.executionContextService.context;
49669
49754
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49670
- return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
49755
+ return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName }, when);
49671
49756
  }
49672
49757
  /**
49673
49758
  * Reads the state value scoped to the current active signal.
@@ -49712,7 +49797,7 @@ async function getSignalState(symbol, dto) {
49712
49797
  if (!MethodContextService.hasContext()) {
49713
49798
  throw new Error("getSignalState requires a method context");
49714
49799
  }
49715
- const { backtest: isBacktest } = backtest.executionContextService.context;
49800
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
49716
49801
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49717
49802
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49718
49803
  let signal;
@@ -49722,6 +49807,7 @@ async function getSignalState(symbol, dto) {
49722
49807
  bucketName,
49723
49808
  initialValue,
49724
49809
  backtest: isBacktest,
49810
+ when,
49725
49811
  });
49726
49812
  }
49727
49813
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -49730,6 +49816,7 @@ async function getSignalState(symbol, dto) {
49730
49816
  bucketName,
49731
49817
  initialValue,
49732
49818
  backtest: isBacktest,
49819
+ when,
49733
49820
  });
49734
49821
  }
49735
49822
  throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -49781,7 +49868,7 @@ async function setSignalState(symbol, dispatch, dto) {
49781
49868
  if (!MethodContextService.hasContext()) {
49782
49869
  throw new Error("setSignalState requires a method context");
49783
49870
  }
49784
- const { backtest: isBacktest } = backtest.executionContextService.context;
49871
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
49785
49872
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49786
49873
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49787
49874
  let signal;
@@ -49791,6 +49878,7 @@ async function setSignalState(symbol, dispatch, dto) {
49791
49878
  bucketName,
49792
49879
  initialValue,
49793
49880
  backtest: isBacktest,
49881
+ when,
49794
49882
  });
49795
49883
  }
49796
49884
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -49799,6 +49887,7 @@ async function setSignalState(symbol, dispatch, dto) {
49799
49887
  bucketName,
49800
49888
  initialValue,
49801
49889
  backtest: isBacktest,
49890
+ when,
49802
49891
  });
49803
49892
  }
49804
49893
  throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -49852,36 +49941,47 @@ class SessionLocalInstance {
49852
49941
  this.frameName = frameName;
49853
49942
  this.backtest = backtest$1;
49854
49943
  this._data = null;
49944
+ this._when = 0;
49855
49945
  /**
49856
49946
  * Initializes _data to null — local session needs no async setup.
49857
49947
  * @returns Promise that resolves immediately
49858
49948
  */
49859
49949
  this.waitForInit = singleshot(async (_initial) => {
49860
49950
  this._data = null;
49951
+ this._when = 0;
49861
49952
  });
49862
49953
  /**
49863
49954
  * Read the current in-memory session value.
49864
- * @returns Current session value, or null if not set
49955
+ * Returns null if the stored `when` is greater than the requested `when`
49956
+ * (look-ahead bias protection).
49957
+ * @param when - Logical timestamp at which the read is happening
49958
+ * @returns Current session value, or null
49865
49959
  */
49866
- this.getData = async () => {
49960
+ this.getData = async (when) => {
49867
49961
  backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_GET, {
49868
49962
  strategyName: this.strategyName,
49869
49963
  exchangeName: this.exchangeName,
49870
49964
  frameName: this.frameName,
49871
49965
  });
49966
+ if (this._when > when.getTime()) {
49967
+ return null;
49968
+ }
49872
49969
  return this._data;
49873
49970
  };
49874
49971
  /**
49875
49972
  * Update the in-memory session value.
49973
+ * Records `when` so future reads with a smaller `when` see no value.
49876
49974
  * @param value - New value or null to clear
49975
+ * @param when - Logical timestamp this value belongs to
49877
49976
  */
49878
- this.setData = async (value) => {
49977
+ this.setData = async (value, when) => {
49879
49978
  backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_SET, {
49880
49979
  strategyName: this.strategyName,
49881
49980
  exchangeName: this.exchangeName,
49882
49981
  frameName: this.frameName,
49883
49982
  });
49884
49983
  this._data = value;
49984
+ this._when = when.getTime();
49885
49985
  };
49886
49986
  }
49887
49987
  /** Releases resources held by this instance. */
@@ -49917,13 +50017,13 @@ class SessionDummyInstance {
49917
50017
  * No-op read — always returns null.
49918
50018
  * @returns null
49919
50019
  */
49920
- this.getData = async () => {
50020
+ this.getData = async (_when) => {
49921
50021
  return null;
49922
50022
  };
49923
50023
  /**
49924
50024
  * No-op write — discards the value.
49925
50025
  */
49926
- this.setData = async (_value) => {
50026
+ this.setData = async (_value, _when) => {
49927
50027
  };
49928
50028
  }
49929
50029
  /** No-op. */
@@ -49949,6 +50049,7 @@ class SessionPersistInstance {
49949
50049
  this.frameName = frameName;
49950
50050
  this.backtest = backtest$1;
49951
50051
  this._data = null;
50052
+ this._when = 0;
49952
50053
  /**
49953
50054
  * Initialize persistence storage and restore session from disk.
49954
50055
  * @param initial - Whether this is the first initialization
@@ -49964,35 +50065,47 @@ class SessionPersistInstance {
49964
50065
  const data = await PersistSessionAdapter.readSessionData(this.strategyName, this.exchangeName, this.frameName);
49965
50066
  if (data) {
49966
50067
  this._data = data.data;
50068
+ this._when = data.when;
49967
50069
  return;
49968
50070
  }
49969
50071
  this._data = null;
50072
+ this._when = 0;
49970
50073
  });
49971
50074
  /**
49972
50075
  * Read the current persisted session value.
49973
- * @returns Current session value, or null if not set
50076
+ * Returns null if the stored `when` is greater than the requested `when`
50077
+ * (look-ahead bias protection).
50078
+ * @param when - Logical timestamp at which the read is happening
50079
+ * @returns Current session value, or null
49974
50080
  */
49975
- this.getData = async () => {
50081
+ this.getData = async (when) => {
49976
50082
  backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_GET, {
49977
50083
  strategyName: this.strategyName,
49978
50084
  exchangeName: this.exchangeName,
49979
50085
  frameName: this.frameName,
49980
50086
  });
50087
+ if (this._when > when.getTime()) {
50088
+ return null;
50089
+ }
49981
50090
  return this._data;
49982
50091
  };
49983
50092
  /**
49984
50093
  * Update session value and persist to disk atomically.
50094
+ * A write with a smaller `when` overwrites an existing record — that lets
50095
+ * a restarted backtest reset live-written state without breaking live.
49985
50096
  * @param value - New value or null to clear
50097
+ * @param when - Logical timestamp this value belongs to
49986
50098
  */
49987
- this.setData = async (value) => {
50099
+ this.setData = async (value, when) => {
49988
50100
  backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_SET, {
49989
50101
  strategyName: this.strategyName,
49990
50102
  exchangeName: this.exchangeName,
49991
50103
  frameName: this.frameName,
49992
50104
  });
49993
50105
  this._data = value;
50106
+ this._when = when.getTime();
49994
50107
  const id = CREATE_KEY_FN$5(this.symbol, this.strategyName, this.exchangeName, this.frameName, this.backtest);
49995
- await PersistSessionAdapter.writeSessionData({ id, data: value }, this.strategyName, this.exchangeName, this.frameName);
50108
+ await PersistSessionAdapter.writeSessionData({ id, data: value, when: this._when }, this.strategyName, this.exchangeName, this.frameName, when);
49996
50109
  };
49997
50110
  }
49998
50111
  /** Releases resources held by this instance. */
@@ -50025,9 +50138,10 @@ class SessionBacktestAdapter {
50025
50138
  * @param context.strategyName - Strategy identifier
50026
50139
  * @param context.exchangeName - Exchange identifier
50027
50140
  * @param context.frameName - Frame identifier
50028
- * @returns Current session value, or null if not set
50141
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50142
+ * @returns Current session value, or null if not set / look-ahead
50029
50143
  */
50030
- this.getData = async (symbol, context) => {
50144
+ this.getData = async (symbol, context, when) => {
50031
50145
  backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET, {
50032
50146
  strategyName: context.strategyName,
50033
50147
  exchangeName: context.exchangeName,
@@ -50037,7 +50151,7 @@ class SessionBacktestAdapter {
50037
50151
  const isInitial = !this.getInstance.has(key);
50038
50152
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
50039
50153
  await instance.waitForInit(isInitial);
50040
- return await instance.getData();
50154
+ return await instance.getData(when);
50041
50155
  };
50042
50156
  /**
50043
50157
  * Update the session value for a backtest run.
@@ -50046,8 +50160,9 @@ class SessionBacktestAdapter {
50046
50160
  * @param context.strategyName - Strategy identifier
50047
50161
  * @param context.exchangeName - Exchange identifier
50048
50162
  * @param context.frameName - Frame identifier
50163
+ * @param when - Logical timestamp this value belongs to
50049
50164
  */
50050
- this.setData = async (symbol, value, context) => {
50165
+ this.setData = async (symbol, value, context, when) => {
50051
50166
  backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET, {
50052
50167
  strategyName: context.strategyName,
50053
50168
  exchangeName: context.exchangeName,
@@ -50057,7 +50172,7 @@ class SessionBacktestAdapter {
50057
50172
  const isInitial = !this.getInstance.has(key);
50058
50173
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
50059
50174
  await instance.waitForInit(isInitial);
50060
- return await instance.setData(value);
50175
+ return await instance.setData(value, when);
50061
50176
  };
50062
50177
  /**
50063
50178
  * Switches to in-memory adapter (default).
@@ -50121,9 +50236,10 @@ class SessionLiveAdapter {
50121
50236
  * @param context.strategyName - Strategy identifier
50122
50237
  * @param context.exchangeName - Exchange identifier
50123
50238
  * @param context.frameName - Frame identifier
50124
- * @returns Current session value, or null if not set
50239
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50240
+ * @returns Current session value, or null if not set / look-ahead
50125
50241
  */
50126
- this.getData = async (symbol, context) => {
50242
+ this.getData = async (symbol, context, when) => {
50127
50243
  backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_GET, {
50128
50244
  strategyName: context.strategyName,
50129
50245
  exchangeName: context.exchangeName,
@@ -50133,7 +50249,7 @@ class SessionLiveAdapter {
50133
50249
  const isInitial = !this.getInstance.has(key);
50134
50250
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
50135
50251
  await instance.waitForInit(isInitial);
50136
- return await instance.getData();
50252
+ return await instance.getData(when);
50137
50253
  };
50138
50254
  /**
50139
50255
  * Update the session value for a live run.
@@ -50142,8 +50258,9 @@ class SessionLiveAdapter {
50142
50258
  * @param context.strategyName - Strategy identifier
50143
50259
  * @param context.exchangeName - Exchange identifier
50144
50260
  * @param context.frameName - Frame identifier
50261
+ * @param when - Logical timestamp this value belongs to
50145
50262
  */
50146
- this.setData = async (symbol, value, context) => {
50263
+ this.setData = async (symbol, value, context, when) => {
50147
50264
  backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_SET, {
50148
50265
  strategyName: context.strategyName,
50149
50266
  exchangeName: context.exchangeName,
@@ -50153,7 +50270,7 @@ class SessionLiveAdapter {
50153
50270
  const isInitial = !this.getInstance.has(key);
50154
50271
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
50155
50272
  await instance.waitForInit(isInitial);
50156
- return await instance.setData(value);
50273
+ return await instance.setData(value, when);
50157
50274
  };
50158
50275
  /**
50159
50276
  * Switches to in-memory adapter.
@@ -50213,9 +50330,10 @@ class SessionAdapter {
50213
50330
  * @param context.exchangeName - Exchange identifier
50214
50331
  * @param context.frameName - Frame identifier
50215
50332
  * @param backtest - Flag indicating if the context is backtest or live
50216
- * @returns Current session value, or null if not set
50333
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50334
+ * @returns Current session value, or null if not set / look-ahead
50217
50335
  */
50218
- this.getData = async (symbol, context, backtest$1) => {
50336
+ this.getData = async (symbol, context, backtest$1, when) => {
50219
50337
  backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_GET, {
50220
50338
  strategyName: context.strategyName,
50221
50339
  exchangeName: context.exchangeName,
@@ -50223,9 +50341,9 @@ class SessionAdapter {
50223
50341
  backtest: backtest$1,
50224
50342
  });
50225
50343
  if (backtest$1) {
50226
- return await SessionBacktest.getData(symbol, context);
50344
+ return await SessionBacktest.getData(symbol, context, when);
50227
50345
  }
50228
- return await SessionLive.getData(symbol, context);
50346
+ return await SessionLive.getData(symbol, context, when);
50229
50347
  };
50230
50348
  /**
50231
50349
  * Update the session value for a signal.
@@ -50236,8 +50354,9 @@ class SessionAdapter {
50236
50354
  * @param context.exchangeName - Exchange identifier
50237
50355
  * @param context.frameName - Frame identifier
50238
50356
  * @param backtest - Flag indicating if the context is backtest or live
50357
+ * @param when - Logical timestamp this value belongs to
50239
50358
  */
50240
- this.setData = async (symbol, value, context, backtest$1) => {
50359
+ this.setData = async (symbol, value, context, backtest$1, when) => {
50241
50360
  backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_SET, {
50242
50361
  strategyName: context.strategyName,
50243
50362
  exchangeName: context.exchangeName,
@@ -50245,9 +50364,9 @@ class SessionAdapter {
50245
50364
  backtest: backtest$1,
50246
50365
  });
50247
50366
  if (backtest$1) {
50248
- return await SessionBacktest.setData(symbol, value, context);
50367
+ return await SessionBacktest.setData(symbol, value, context, when);
50249
50368
  }
50250
- return await SessionLive.setData(symbol, value, context);
50369
+ return await SessionLive.setData(symbol, value, context, when);
50251
50370
  };
50252
50371
  }
50253
50372
  }
@@ -50299,9 +50418,9 @@ async function getSessionData(symbol) {
50299
50418
  if (!MethodContextService.hasContext()) {
50300
50419
  throw new Error("getSession requires a method context");
50301
50420
  }
50302
- const { backtest: isBacktest } = backtest.executionContextService.context;
50421
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50303
50422
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50304
- return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest);
50423
+ return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest, when);
50305
50424
  }
50306
50425
  /**
50307
50426
  * Writes a session value scoped to the current (symbol, strategy, exchange, frame) context.
@@ -50333,9 +50452,9 @@ async function setSessionData(symbol, value) {
50333
50452
  if (!MethodContextService.hasContext()) {
50334
50453
  throw new Error("setSession requires a method context");
50335
50454
  }
50336
- const { backtest: isBacktest } = backtest.executionContextService.context;
50455
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50337
50456
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50338
- await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest);
50457
+ await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest, when);
50339
50458
  }
50340
50459
 
50341
50460
  const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
@@ -50346,7 +50465,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50346
50465
  if (!MethodContextService.hasContext()) {
50347
50466
  throw new Error("createSignalState requires a method context");
50348
50467
  }
50349
- const { backtest: isBacktest } = backtest.executionContextService.context;
50468
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50350
50469
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50351
50470
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
50352
50471
  let signal;
@@ -50356,6 +50475,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50356
50475
  bucketName: params.bucketName,
50357
50476
  initialValue: params.initialValue,
50358
50477
  signalId: signal.id,
50478
+ when,
50359
50479
  });
50360
50480
  }
50361
50481
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -50364,6 +50484,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50364
50484
  bucketName: params.bucketName,
50365
50485
  initialValue: params.initialValue,
50366
50486
  signalId: signal.id,
50487
+ when,
50367
50488
  });
50368
50489
  }
50369
50490
  throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
@@ -50375,7 +50496,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50375
50496
  if (!MethodContextService.hasContext()) {
50376
50497
  throw new Error("createSignalState requires a method context");
50377
50498
  }
50378
- const { backtest: isBacktest } = backtest.executionContextService.context;
50499
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50379
50500
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50380
50501
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
50381
50502
  let signal;
@@ -50385,6 +50506,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50385
50506
  bucketName: params.bucketName,
50386
50507
  initialValue: params.initialValue,
50387
50508
  signalId: signal.id,
50509
+ when,
50388
50510
  });
50389
50511
  }
50390
50512
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -50393,6 +50515,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50393
50515
  bucketName: params.bucketName,
50394
50516
  initialValue: params.initialValue,
50395
50517
  signalId: signal.id,
50518
+ when,
50396
50519
  });
50397
50520
  }
50398
50521
  throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
@@ -50475,7 +50598,7 @@ const createSearchIndex = () => {
50475
50598
  df.set(term, count);
50476
50599
  });
50477
50600
  };
50478
- const upsert = ({ id, content, index = JSON.stringify(content), priority = Date.now(), }) => {
50601
+ const upsert = ({ id, content, when, index = JSON.stringify(content), priority = Date.now(), }) => {
50479
50602
  const existing = docs.get(id);
50480
50603
  {
50481
50604
  existing && subtractDf(existing.tf);
@@ -50484,12 +50607,21 @@ const createSearchIndex = () => {
50484
50607
  const tf = new Map();
50485
50608
  for (const t of tokens)
50486
50609
  tf.set(t, (tf.get(t) ?? 0) + 1);
50487
- docs.set(id, { tf, len: tokens.length, content, priority });
50610
+ docs.set(id, { tf, len: tokens.length, content, priority, when });
50488
50611
  {
50489
50612
  addDf(tf);
50490
50613
  }
50491
50614
  };
50492
- const read = (id) => docs.get(id)?.content;
50615
+ /**
50616
+ * Read a document by id. Returns undefined when the document was written
50617
+ * with a `when` greater than the requested `when` (look-ahead guard).
50618
+ */
50619
+ const read = (id, when) => {
50620
+ const doc = docs.get(id);
50621
+ if (!doc || doc.when > when)
50622
+ return undefined;
50623
+ return doc.content;
50624
+ };
50493
50625
  const remove = (id) => {
50494
50626
  {
50495
50627
  const existing = docs.get(id);
@@ -50497,16 +50629,30 @@ const createSearchIndex = () => {
50497
50629
  }
50498
50630
  docs.delete(id);
50499
50631
  };
50500
- const list = () => Array.from(docs.entries())
50632
+ /**
50633
+ * List documents whose `when` is less than or equal to the requested `when`
50634
+ * (look-ahead guard), sorted by priority.
50635
+ */
50636
+ const list = (when) => Array.from(docs.entries())
50637
+ .filter(([, doc]) => doc.when <= when)
50501
50638
  .sort(([, a], [, b]) => a.priority - b.priority)
50502
50639
  .map(([id, { content }]) => ({ id, content }));
50503
- const search = (query, settings = DEFAULT_SETTINGS) => {
50640
+ /**
50641
+ * BM25 search over documents whose `when` is less than or equal to the
50642
+ * requested `when` (look-ahead guard).
50643
+ *
50644
+ * Document frequency (df) is computed across the whole index — the time-cut
50645
+ * is applied only to the candidate set, so scores stay comparable across
50646
+ * different `when` values.
50647
+ */
50648
+ const search = (query, when, settings = DEFAULT_SETTINGS) => {
50504
50649
  const terms = tokenize(query);
50505
50650
  if (!terms.length || !docs.size)
50506
50651
  return [];
50507
50652
  const N = docs.size;
50508
50653
  const avgLen = [...docs.values()].reduce((s, d) => s + d.len, 0) / N;
50509
50654
  return [...docs.entries()]
50655
+ .filter(([, doc]) => doc.when <= when)
50510
50656
  .map(([id, doc]) => {
50511
50657
  let score = 0;
50512
50658
  for (const term of terms) {
@@ -50603,8 +50749,9 @@ class MemoryLocalInstance {
50603
50749
  * @param memoryId - Unique entry identifier
50604
50750
  * @param value - Value to store and index
50605
50751
  * @param description - BM25 index string
50752
+ * @param when - Logical timestamp this entry belongs to (look-ahead guard)
50606
50753
  */
50607
- async writeMemory(memoryId, value, description) {
50754
+ async writeMemory(memoryId, value, description, when) {
50608
50755
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
50609
50756
  signalId: this.signalId,
50610
50757
  bucketName: this.bucketName,
@@ -50615,21 +50762,24 @@ class MemoryLocalInstance {
50615
50762
  content: value,
50616
50763
  index: description,
50617
50764
  priority: Date.now(),
50765
+ when: when.getTime(),
50618
50766
  });
50619
50767
  }
50620
50768
  /**
50621
50769
  * Read a single entry from the in-memory index.
50770
+ * Behaves as not-found if the stored `when` is greater than the requested `when`.
50622
50771
  * @param memoryId - Unique entry identifier
50772
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50623
50773
  * @returns Parsed entry value
50624
- * @throws Error if entry not found
50774
+ * @throws Error if entry not found (or shadowed by look-ahead)
50625
50775
  */
50626
- async readMemory(memoryId) {
50776
+ async readMemory(memoryId, when) {
50627
50777
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_READ, {
50628
50778
  signalId: this.signalId,
50629
50779
  bucketName: this.bucketName,
50630
50780
  memoryId,
50631
50781
  });
50632
- const value = this._index.read(memoryId);
50782
+ const value = this._index.read(memoryId, when.getTime());
50633
50783
  if (!value) {
50634
50784
  throw new Error(`MemoryLocalInstance value not found memoryId=${memoryId}`);
50635
50785
  }
@@ -50637,33 +50787,38 @@ class MemoryLocalInstance {
50637
50787
  }
50638
50788
  /**
50639
50789
  * Search entries using BM25 full-text scoring.
50790
+ * Filters out entries whose `when` is greater than the requested `when`.
50640
50791
  * @param query - Search query string
50792
+ * @param when - Logical timestamp at which the search is happening (look-ahead guard)
50641
50793
  * @returns Matching entries sorted by relevance score
50642
50794
  */
50643
- async searchMemory(query, settings) {
50795
+ async searchMemory(query, when, settings) {
50644
50796
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_SEARCH, {
50645
50797
  signalId: this.signalId,
50646
50798
  bucketName: this.bucketName,
50647
50799
  query,
50648
50800
  });
50649
- return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
50801
+ return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
50650
50802
  }
50651
50803
  /**
50652
50804
  * List all entries stored in the in-memory index.
50805
+ * Filters out entries whose `when` is greater than the requested `when`.
50806
+ * @param when - Logical timestamp at which the list is happening (look-ahead guard)
50653
50807
  * @returns Array of all stored entries
50654
50808
  */
50655
- async listMemory() {
50809
+ async listMemory(when) {
50656
50810
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_LIST, {
50657
50811
  signalId: this.signalId,
50658
50812
  bucketName: this.bucketName,
50659
50813
  });
50660
- return this._index.list().map(LIST_MEMORY_FN);
50814
+ return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
50661
50815
  }
50662
50816
  /**
50663
50817
  * Remove an entry from the in-memory index.
50664
50818
  * @param memoryId - Unique entry identifier
50819
+ * @param when - Logical timestamp (kept for API consistency; removal is by UUID)
50665
50820
  */
50666
- async removeMemory(memoryId) {
50821
+ async removeMemory(memoryId, _when) {
50667
50822
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_REMOVE, {
50668
50823
  signalId: this.signalId,
50669
50824
  bucketName: this.bucketName,
@@ -50709,12 +50864,13 @@ class MemoryPersistInstance {
50709
50864
  initial,
50710
50865
  });
50711
50866
  await PersistMemoryAdapter.waitForInit(this.signalId, this.bucketName, initial);
50712
- for await (const { memoryId, data: { data, index, priority } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
50867
+ for await (const { memoryId, data: { data, index, priority, when } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
50713
50868
  this._index.upsert({
50714
50869
  id: memoryId,
50715
50870
  content: data,
50716
50871
  index,
50717
50872
  priority,
50873
+ when,
50718
50874
  });
50719
50875
  }
50720
50876
  }
@@ -50723,69 +50879,79 @@ class MemoryPersistInstance {
50723
50879
  * @param memoryId - Unique entry identifier
50724
50880
  * @param value - Value to persist and index
50725
50881
  * @param index - BM25 index string; defaults to JSON.stringify(value)
50882
+ * @param when - Logical timestamp this entry belongs to (look-ahead guard)
50726
50883
  */
50727
- async writeMemory(memoryId, value, index = JSON.stringify(value)) {
50884
+ async writeMemory(memoryId, value, index, when) {
50728
50885
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
50729
50886
  signalId: this.signalId,
50730
50887
  bucketName: this.bucketName,
50731
50888
  memoryId,
50732
50889
  });
50733
50890
  const priority = Date.now();
50734
- await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index }, this.signalId, this.bucketName, memoryId);
50891
+ const whenMs = when.getTime();
50892
+ await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index, when: whenMs }, this.signalId, this.bucketName, memoryId, when);
50735
50893
  this._index.upsert({
50736
50894
  id: memoryId,
50737
50895
  content: value,
50738
50896
  index,
50739
50897
  priority,
50898
+ when: whenMs,
50740
50899
  });
50741
50900
  }
50742
50901
  /**
50743
50902
  * Read a single entry from disk.
50903
+ * Behaves as not-found if the stored `when` is greater than the requested `when`.
50744
50904
  * @param memoryId - Unique entry identifier
50905
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50745
50906
  * @returns Entry value
50746
- * @throws Error if entry not found
50907
+ * @throws Error if entry not found (or shadowed by look-ahead)
50747
50908
  */
50748
- async readMemory(memoryId) {
50909
+ async readMemory(memoryId, when) {
50749
50910
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ, {
50750
50911
  signalId: this.signalId,
50751
50912
  bucketName: this.bucketName,
50752
50913
  memoryId,
50753
50914
  });
50754
50915
  const data = await PersistMemoryAdapter.readMemoryData(this.signalId, this.bucketName, memoryId);
50755
- if (!data) {
50916
+ if (!data || data.when > when.getTime()) {
50756
50917
  throw new Error(`MemoryPersistInstance value not found memoryId=${memoryId}`);
50757
50918
  }
50758
50919
  return data.data;
50759
50920
  }
50760
50921
  /**
50761
50922
  * Search entries using BM25 index rebuilt from disk on init.
50923
+ * Filters out entries whose `when` is greater than the requested `when`.
50762
50924
  * @param query - Search query string
50925
+ * @param when - Logical timestamp at which the search is happening (look-ahead guard)
50763
50926
  * @returns Matching entries sorted by relevance score
50764
50927
  */
50765
- async searchMemory(query, settings) {
50928
+ async searchMemory(query, when, settings) {
50766
50929
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH, {
50767
50930
  signalId: this.signalId,
50768
50931
  bucketName: this.bucketName,
50769
50932
  query,
50770
50933
  });
50771
- return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
50934
+ return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
50772
50935
  }
50773
50936
  /**
50774
50937
  * List all entries from the in-memory index (populated from disk on init).
50938
+ * Filters out entries whose `when` is greater than the requested `when`.
50939
+ * @param when - Logical timestamp at which the list is happening (look-ahead guard)
50775
50940
  * @returns Array of all stored entries
50776
50941
  */
50777
- async listMemory() {
50942
+ async listMemory(when) {
50778
50943
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST, {
50779
50944
  signalId: this.signalId,
50780
50945
  bucketName: this.bucketName,
50781
50946
  });
50782
- return this._index.list().map(LIST_MEMORY_FN);
50947
+ return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
50783
50948
  }
50784
50949
  /**
50785
50950
  * Remove an entry from disk and from the BM25 index.
50786
50951
  * @param memoryId - Unique entry identifier
50952
+ * @param when - Logical timestamp (kept for API consistency; removal is by UUID)
50787
50953
  */
50788
- async removeMemory(memoryId) {
50954
+ async removeMemory(memoryId, _when) {
50789
50955
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE, {
50790
50956
  signalId: this.signalId,
50791
50957
  bucketName: this.bucketName,
@@ -50822,34 +50988,34 @@ class MemoryDummyInstance {
50822
50988
  * No-op write - discards the value.
50823
50989
  * @returns Promise that resolves immediately
50824
50990
  */
50825
- async writeMemory() {
50991
+ async writeMemory(_memoryId, _value, _description, _when) {
50826
50992
  }
50827
50993
  /**
50828
50994
  * No-op read - always throws.
50829
50995
  * @throws Error always
50830
50996
  */
50831
- async readMemory(_memoryId) {
50997
+ async readMemory(_memoryId, _when) {
50832
50998
  throw new Error("MemoryDummyInstance: readMemory not supported");
50833
50999
  }
50834
51000
  /**
50835
51001
  * No-op search - returns empty array.
50836
51002
  * @returns Empty array
50837
51003
  */
50838
- async searchMemory() {
51004
+ async searchMemory(_query, _when, _settings) {
50839
51005
  return [];
50840
51006
  }
50841
51007
  /**
50842
51008
  * No-op list - returns empty array.
50843
51009
  * @returns Empty array
50844
51010
  */
50845
- async listMemory() {
51011
+ async listMemory(_when) {
50846
51012
  return [];
50847
51013
  }
50848
51014
  /**
50849
51015
  * No-op remove.
50850
51016
  * @returns Promise that resolves immediately
50851
51017
  */
50852
- async removeMemory() {
51018
+ async removeMemory(_memoryId, _when) {
50853
51019
  }
50854
51020
  /** No-op. */
50855
51021
  dispose() {
@@ -50893,6 +51059,7 @@ class MemoryBacktestAdapter {
50893
51059
  * @param dto.signalId - Signal identifier
50894
51060
  * @param dto.bucketName - Bucket name
50895
51061
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51062
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
50896
51063
  */
50897
51064
  this.writeMemory = async (dto) => {
50898
51065
  backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
@@ -50904,13 +51071,14 @@ class MemoryBacktestAdapter {
50904
51071
  const isInitial = !this.getInstance.has(key);
50905
51072
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50906
51073
  await instance.waitForInit(isInitial);
50907
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
51074
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
50908
51075
  };
50909
51076
  /**
50910
51077
  * Search memory using BM25 full-text scoring.
50911
51078
  * @param dto.query - Search query string
50912
51079
  * @param dto.signalId - Signal identifier
50913
51080
  * @param dto.bucketName - Bucket name
51081
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
50914
51082
  * @returns Matching entries sorted by relevance score
50915
51083
  */
50916
51084
  this.searchMemory = async (dto) => {
@@ -50923,12 +51091,13 @@ class MemoryBacktestAdapter {
50923
51091
  const isInitial = !this.getInstance.has(key);
50924
51092
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50925
51093
  await instance.waitForInit(isInitial);
50926
- return await instance.searchMemory(dto.query, dto.settings);
51094
+ return await instance.searchMemory(dto.query, dto.when, dto.settings);
50927
51095
  };
50928
51096
  /**
50929
51097
  * List all entries in memory.
50930
51098
  * @param dto.signalId - Signal identifier
50931
51099
  * @param dto.bucketName - Bucket name
51100
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
50932
51101
  * @returns Array of all stored entries
50933
51102
  */
50934
51103
  this.listMemory = async (dto) => {
@@ -50940,13 +51109,14 @@ class MemoryBacktestAdapter {
50940
51109
  const isInitial = !this.getInstance.has(key);
50941
51110
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50942
51111
  await instance.waitForInit(isInitial);
50943
- return await instance.listMemory();
51112
+ return await instance.listMemory(dto.when);
50944
51113
  };
50945
51114
  /**
50946
51115
  * Remove an entry from memory.
50947
51116
  * @param dto.memoryId - Unique entry identifier
50948
51117
  * @param dto.signalId - Signal identifier
50949
51118
  * @param dto.bucketName - Bucket name
51119
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
50950
51120
  */
50951
51121
  this.removeMemory = async (dto) => {
50952
51122
  backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
@@ -50958,13 +51128,14 @@ class MemoryBacktestAdapter {
50958
51128
  const isInitial = !this.getInstance.has(key);
50959
51129
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50960
51130
  await instance.waitForInit(isInitial);
50961
- return await instance.removeMemory(dto.memoryId);
51131
+ return await instance.removeMemory(dto.memoryId, dto.when);
50962
51132
  };
50963
51133
  /**
50964
51134
  * Read a single entry from memory.
50965
51135
  * @param dto.memoryId - Unique entry identifier
50966
51136
  * @param dto.signalId - Signal identifier
50967
51137
  * @param dto.bucketName - Bucket name
51138
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
50968
51139
  * @returns Entry value
50969
51140
  * @throws Error if entry not found
50970
51141
  */
@@ -50978,7 +51149,7 @@ class MemoryBacktestAdapter {
50978
51149
  const isInitial = !this.getInstance.has(key);
50979
51150
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50980
51151
  await instance.waitForInit(isInitial);
50981
- return await instance.readMemory(dto.memoryId);
51152
+ return await instance.readMemory(dto.memoryId, dto.when);
50982
51153
  };
50983
51154
  /**
50984
51155
  * Switches to in-memory BM25 adapter (default).
@@ -51060,6 +51231,7 @@ class MemoryLiveAdapter {
51060
51231
  * @param dto.signalId - Signal identifier
51061
51232
  * @param dto.bucketName - Bucket name
51062
51233
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51234
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
51063
51235
  */
51064
51236
  this.writeMemory = async (dto) => {
51065
51237
  backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
@@ -51071,13 +51243,14 @@ class MemoryLiveAdapter {
51071
51243
  const isInitial = !this.getInstance.has(key);
51072
51244
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51073
51245
  await instance.waitForInit(isInitial);
51074
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
51246
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
51075
51247
  };
51076
51248
  /**
51077
51249
  * Search memory using BM25 full-text scoring.
51078
51250
  * @param dto.query - Search query string
51079
51251
  * @param dto.signalId - Signal identifier
51080
51252
  * @param dto.bucketName - Bucket name
51253
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
51081
51254
  * @returns Matching entries sorted by relevance score
51082
51255
  */
51083
51256
  this.searchMemory = async (dto) => {
@@ -51090,12 +51263,13 @@ class MemoryLiveAdapter {
51090
51263
  const isInitial = !this.getInstance.has(key);
51091
51264
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51092
51265
  await instance.waitForInit(isInitial);
51093
- return await instance.searchMemory(dto.query, dto.settings);
51266
+ return await instance.searchMemory(dto.query, dto.when, dto.settings);
51094
51267
  };
51095
51268
  /**
51096
51269
  * List all entries in memory.
51097
51270
  * @param dto.signalId - Signal identifier
51098
51271
  * @param dto.bucketName - Bucket name
51272
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
51099
51273
  * @returns Array of all stored entries
51100
51274
  */
51101
51275
  this.listMemory = async (dto) => {
@@ -51107,13 +51281,14 @@ class MemoryLiveAdapter {
51107
51281
  const isInitial = !this.getInstance.has(key);
51108
51282
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51109
51283
  await instance.waitForInit(isInitial);
51110
- return await instance.listMemory();
51284
+ return await instance.listMemory(dto.when);
51111
51285
  };
51112
51286
  /**
51113
51287
  * Remove an entry from memory.
51114
51288
  * @param dto.memoryId - Unique entry identifier
51115
51289
  * @param dto.signalId - Signal identifier
51116
51290
  * @param dto.bucketName - Bucket name
51291
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
51117
51292
  */
51118
51293
  this.removeMemory = async (dto) => {
51119
51294
  backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
@@ -51125,13 +51300,14 @@ class MemoryLiveAdapter {
51125
51300
  const isInitial = !this.getInstance.has(key);
51126
51301
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51127
51302
  await instance.waitForInit(isInitial);
51128
- return await instance.removeMemory(dto.memoryId);
51303
+ return await instance.removeMemory(dto.memoryId, dto.when);
51129
51304
  };
51130
51305
  /**
51131
51306
  * Read a single entry from memory.
51132
51307
  * @param dto.memoryId - Unique entry identifier
51133
51308
  * @param dto.signalId - Signal identifier
51134
51309
  * @param dto.bucketName - Bucket name
51310
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
51135
51311
  * @returns Entry value
51136
51312
  * @throws Error if entry not found
51137
51313
  */
@@ -51145,7 +51321,7 @@ class MemoryLiveAdapter {
51145
51321
  const isInitial = !this.getInstance.has(key);
51146
51322
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51147
51323
  await instance.waitForInit(isInitial);
51148
- return await instance.readMemory(dto.memoryId);
51324
+ return await instance.readMemory(dto.memoryId, dto.when);
51149
51325
  };
51150
51326
  /**
51151
51327
  * Switches to in-memory BM25 adapter.
@@ -51244,6 +51420,7 @@ class MemoryAdapter {
51244
51420
  * @param dto.bucketName - Bucket name
51245
51421
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51246
51422
  * @param dto.backtest - Flag indicating if the context is backtest or live
51423
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
51247
51424
  */
51248
51425
  this.writeMemory = async (dto) => {
51249
51426
  if (!this.enable.hasValue()) {
@@ -51267,6 +51444,7 @@ class MemoryAdapter {
51267
51444
  * @param dto.signalId - Signal identifier
51268
51445
  * @param dto.bucketName - Bucket name
51269
51446
  * @param dto.backtest - Flag indicating if the context is backtest or live
51447
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
51270
51448
  * @returns Matching entries sorted by relevance score
51271
51449
  */
51272
51450
  this.searchMemory = async (dto) => {
@@ -51290,6 +51468,7 @@ class MemoryAdapter {
51290
51468
  * @param dto.signalId - Signal identifier
51291
51469
  * @param dto.bucketName - Bucket name
51292
51470
  * @param dto.backtest - Flag indicating if the context is backtest or live
51471
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
51293
51472
  * @returns Array of all stored entries
51294
51473
  */
51295
51474
  this.listMemory = async (dto) => {
@@ -51313,6 +51492,7 @@ class MemoryAdapter {
51313
51492
  * @param dto.signalId - Signal identifier
51314
51493
  * @param dto.bucketName - Bucket name
51315
51494
  * @param dto.backtest - Flag indicating if the context is backtest or live
51495
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
51316
51496
  */
51317
51497
  this.removeMemory = async (dto) => {
51318
51498
  if (!this.enable.hasValue()) {
@@ -51336,6 +51516,7 @@ class MemoryAdapter {
51336
51516
  * @param dto.signalId - Signal identifier
51337
51517
  * @param dto.bucketName - Bucket name
51338
51518
  * @param dto.backtest - Flag indicating if the context is backtest or live
51519
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
51339
51520
  * @returns Entry value
51340
51521
  * @throws Error if entry not found
51341
51522
  */
@@ -51408,7 +51589,7 @@ async function writeMemory(dto) {
51408
51589
  if (!MethodContextService.hasContext()) {
51409
51590
  throw new Error("writeMemory requires a method context");
51410
51591
  }
51411
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51592
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51412
51593
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51413
51594
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51414
51595
  let signal;
@@ -51420,6 +51601,7 @@ async function writeMemory(dto) {
51420
51601
  bucketName,
51421
51602
  description,
51422
51603
  backtest: isBacktest,
51604
+ when,
51423
51605
  });
51424
51606
  return;
51425
51607
  }
@@ -51431,6 +51613,7 @@ async function writeMemory(dto) {
51431
51613
  bucketName,
51432
51614
  description,
51433
51615
  backtest: isBacktest,
51616
+ when,
51434
51617
  });
51435
51618
  return;
51436
51619
  }
@@ -51466,7 +51649,7 @@ async function readMemory(dto) {
51466
51649
  if (!MethodContextService.hasContext()) {
51467
51650
  throw new Error("readMemory requires a method context");
51468
51651
  }
51469
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51652
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51470
51653
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51471
51654
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51472
51655
  let signal;
@@ -51476,6 +51659,7 @@ async function readMemory(dto) {
51476
51659
  signalId: signal.id,
51477
51660
  bucketName,
51478
51661
  backtest: isBacktest,
51662
+ when,
51479
51663
  });
51480
51664
  }
51481
51665
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51484,6 +51668,7 @@ async function readMemory(dto) {
51484
51668
  signalId: signal.id,
51485
51669
  bucketName,
51486
51670
  backtest: isBacktest,
51671
+ when,
51487
51672
  });
51488
51673
  }
51489
51674
  throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
@@ -51518,7 +51703,7 @@ async function searchMemory(dto) {
51518
51703
  if (!MethodContextService.hasContext()) {
51519
51704
  throw new Error("searchMemory requires a method context");
51520
51705
  }
51521
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51706
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51522
51707
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51523
51708
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51524
51709
  let signal;
@@ -51528,6 +51713,7 @@ async function searchMemory(dto) {
51528
51713
  signalId: signal.id,
51529
51714
  bucketName,
51530
51715
  backtest: isBacktest,
51716
+ when,
51531
51717
  });
51532
51718
  }
51533
51719
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51536,6 +51722,7 @@ async function searchMemory(dto) {
51536
51722
  signalId: signal.id,
51537
51723
  bucketName,
51538
51724
  backtest: isBacktest,
51725
+ when,
51539
51726
  });
51540
51727
  }
51541
51728
  throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
@@ -51568,7 +51755,7 @@ async function listMemory(dto) {
51568
51755
  if (!MethodContextService.hasContext()) {
51569
51756
  throw new Error("listMemory requires a method context");
51570
51757
  }
51571
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51758
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51572
51759
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51573
51760
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51574
51761
  let signal;
@@ -51577,6 +51764,7 @@ async function listMemory(dto) {
51577
51764
  signalId: signal.id,
51578
51765
  bucketName,
51579
51766
  backtest: isBacktest,
51767
+ when,
51580
51768
  });
51581
51769
  }
51582
51770
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51584,6 +51772,7 @@ async function listMemory(dto) {
51584
51772
  signalId: signal.id,
51585
51773
  bucketName,
51586
51774
  backtest: isBacktest,
51775
+ when,
51587
51776
  });
51588
51777
  }
51589
51778
  throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -51618,7 +51807,7 @@ async function removeMemory(dto) {
51618
51807
  if (!MethodContextService.hasContext()) {
51619
51808
  throw new Error("removeMemory requires a method context");
51620
51809
  }
51621
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51810
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51622
51811
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51623
51812
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51624
51813
  let signal;
@@ -51628,6 +51817,7 @@ async function removeMemory(dto) {
51628
51817
  signalId: signal.id,
51629
51818
  bucketName,
51630
51819
  backtest: isBacktest,
51820
+ when,
51631
51821
  });
51632
51822
  return;
51633
51823
  }
@@ -51637,6 +51827,7 @@ async function removeMemory(dto) {
51637
51827
  signalId: signal.id,
51638
51828
  bucketName,
51639
51829
  backtest: isBacktest,
51830
+ when,
51640
51831
  });
51641
51832
  return;
51642
51833
  }
@@ -51902,6 +52093,9 @@ class DumpMemoryInstance {
51902
52093
  value: { messages },
51903
52094
  description,
51904
52095
  backtest: this.backtest,
52096
+ // when=0: dumps are UI artifacts (agent transcripts, markdown reports);
52097
+ // they must stay visible regardless of the reader's logical time.
52098
+ when: new Date(0)
51905
52099
  });
51906
52100
  }
51907
52101
  /**
@@ -51924,6 +52118,7 @@ class DumpMemoryInstance {
51924
52118
  value: record,
51925
52119
  description,
51926
52120
  backtest: this.backtest,
52121
+ when: new Date(0)
51927
52122
  });
51928
52123
  }
51929
52124
  /**
@@ -51947,6 +52142,7 @@ class DumpMemoryInstance {
51947
52142
  value: { rows },
51948
52143
  description,
51949
52144
  backtest: this.backtest,
52145
+ when: new Date(0)
51950
52146
  });
51951
52147
  }
51952
52148
  /**
@@ -51969,6 +52165,7 @@ class DumpMemoryInstance {
51969
52165
  value: { content },
51970
52166
  description,
51971
52167
  backtest: this.backtest,
52168
+ when: new Date(0)
51972
52169
  });
51973
52170
  }
51974
52171
  /**
@@ -51991,6 +52188,7 @@ class DumpMemoryInstance {
51991
52188
  value: { content },
51992
52189
  description,
51993
52190
  backtest: this.backtest,
52191
+ when: new Date(0)
51994
52192
  });
51995
52193
  }
51996
52194
  /**
@@ -52014,6 +52212,7 @@ class DumpMemoryInstance {
52014
52212
  value: json,
52015
52213
  description,
52016
52214
  backtest: this.backtest,
52215
+ when: new Date(0)
52017
52216
  });
52018
52217
  }
52019
52218
  /** Releases resources held by this instance. */
@@ -60975,8 +61174,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
60975
61174
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
60976
61175
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
60977
61176
  const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
60978
- const MS_PER_MINUTE$1 = 60000;
60979
- const INTERVAL_MINUTES$1 = {
61177
+ const MS_PER_MINUTE$2 = 60000;
61178
+ const INTERVAL_MINUTES$2 = {
60980
61179
  "1m": 1,
60981
61180
  "3m": 3,
60982
61181
  "5m": 5,
@@ -61010,11 +61209,11 @@ const INTERVAL_MINUTES$1 = {
61010
61209
  * ```
61011
61210
  */
61012
61211
  const align$1 = (timestamp, interval) => {
61013
- const intervalMinutes = INTERVAL_MINUTES$1[interval];
61212
+ const intervalMinutes = INTERVAL_MINUTES$2[interval];
61014
61213
  if (!intervalMinutes) {
61015
61214
  throw new Error(`align: unknown interval=${interval}`);
61016
61215
  }
61017
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
61216
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
61018
61217
  return Math.floor(timestamp / intervalMs) * intervalMs;
61019
61218
  };
61020
61219
  /**
@@ -61103,7 +61302,7 @@ class CacheFnInstance {
61103
61302
  */
61104
61303
  this.run = (...args) => {
61105
61304
  backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
61106
- const step = INTERVAL_MINUTES$1[this.interval];
61305
+ const step = INTERVAL_MINUTES$2[this.interval];
61107
61306
  {
61108
61307
  if (!MethodContextService.hasContext()) {
61109
61308
  throw new Error("CacheFnInstance run requires method context");
@@ -61298,7 +61497,7 @@ class CacheFileInstance {
61298
61497
  */
61299
61498
  this.run = async (...args) => {
61300
61499
  backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
61301
- const step = INTERVAL_MINUTES$1[this.interval];
61500
+ const step = INTERVAL_MINUTES$2[this.interval];
61302
61501
  {
61303
61502
  if (!MethodContextService.hasContext()) {
61304
61503
  throw new Error("CacheFileInstance run requires method context");
@@ -61320,7 +61519,7 @@ class CacheFileInstance {
61320
61519
  return cached.data;
61321
61520
  }
61322
61521
  const result = await this.fn.call(null, ...args);
61323
- await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
61522
+ await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey, when);
61324
61523
  return result;
61325
61524
  };
61326
61525
  /**
@@ -61609,8 +61808,8 @@ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
61609
61808
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
61610
61809
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
61611
61810
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
61612
- const MS_PER_MINUTE = 60000;
61613
- const INTERVAL_MINUTES = {
61811
+ const MS_PER_MINUTE$1 = 60000;
61812
+ const INTERVAL_MINUTES$1 = {
61614
61813
  "1m": 1,
61615
61814
  "3m": 3,
61616
61815
  "5m": 5,
@@ -61640,11 +61839,11 @@ const INTERVAL_MINUTES = {
61640
61839
  * ```
61641
61840
  */
61642
61841
  const align = (timestamp, interval) => {
61643
- const intervalMinutes = INTERVAL_MINUTES[interval];
61842
+ const intervalMinutes = INTERVAL_MINUTES$1[interval];
61644
61843
  if (!intervalMinutes) {
61645
61844
  throw new Error(`align: unknown interval=${interval}`);
61646
61845
  }
61647
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
61846
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
61648
61847
  return Math.floor(timestamp / intervalMs) * intervalMs;
61649
61848
  };
61650
61849
  /**
@@ -61719,7 +61918,7 @@ class IntervalFnInstance {
61719
61918
  */
61720
61919
  this.run = (...args) => {
61721
61920
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
61722
- const step = INTERVAL_MINUTES[this.interval];
61921
+ const step = INTERVAL_MINUTES$1[this.interval];
61723
61922
  {
61724
61923
  if (!MethodContextService.hasContext()) {
61725
61924
  throw new Error("IntervalFnInstance run requires method context");
@@ -61886,7 +62085,7 @@ class IntervalFileInstance {
61886
62085
  */
61887
62086
  this.run = async (...args) => {
61888
62087
  backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
61889
- const step = INTERVAL_MINUTES[this.interval];
62088
+ const step = INTERVAL_MINUTES$1[this.interval];
61890
62089
  {
61891
62090
  if (!MethodContextService.hasContext()) {
61892
62091
  throw new Error("IntervalFileInstance run requires method context");
@@ -61909,7 +62108,7 @@ class IntervalFileInstance {
61909
62108
  }
61910
62109
  const result = await this.fn.call(null, ...args);
61911
62110
  if (result !== null) {
61912
- await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
62111
+ await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, when, removed: false }, bucket, entityKey, when);
61913
62112
  }
61914
62113
  return result;
61915
62114
  };
@@ -63033,6 +63232,31 @@ class ActionBase {
63033
63232
  // @ts-ignore
63034
63233
  ActionBase = makeExtendable(ActionBase);
63035
63234
 
63235
+ const MS_PER_MINUTE = 60000;
63236
+ const INTERVAL_MINUTES = {
63237
+ "1m": 1,
63238
+ "3m": 3,
63239
+ "5m": 5,
63240
+ "15m": 15,
63241
+ "30m": 30,
63242
+ "1h": 60,
63243
+ "2h": 120,
63244
+ "4h": 240,
63245
+ "6h": 360,
63246
+ "8h": 480,
63247
+ "1d": 1440,
63248
+ };
63249
+ /**
63250
+ * Returns the step in milliseconds for a given candle interval.
63251
+ * For example, for "15m" interval, it returns 900000 (15 * 60 * 1000).
63252
+ *
63253
+ * @param interval - Candle interval (e.g., "1m", "15m", "1h")
63254
+ * @returns Step in milliseconds corresponding to the interval
63255
+ */
63256
+ const intervalStepMs = (interval) => {
63257
+ return INTERVAL_MINUTES[interval] * MS_PER_MINUTE;
63258
+ };
63259
+
63036
63260
  /**
63037
63261
  * Rounds a price to the appropriate precision based on the tick size.
63038
63262
  *
@@ -63299,4 +63523,4 @@ const validateSignal = (signal, currentPrice) => {
63299
63523
  return !errors.length;
63300
63524
  };
63301
63525
 
63302
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
63526
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };