backtest-kit 9.0.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -938,7 +938,7 @@ const LOGGER_SERVICE$7 = new LoggerService();
938
938
  /** Symbol key for the singleshot waitForInit function on PersistBase instances. */
939
939
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
940
940
  // Calculate step in milliseconds for candle close time validation
941
- const INTERVAL_MINUTES$9 = {
941
+ const INTERVAL_MINUTES$a = {
942
942
  "1m": 1,
943
943
  "3m": 3,
944
944
  "5m": 5,
@@ -951,7 +951,7 @@ const INTERVAL_MINUTES$9 = {
951
951
  "8h": 480,
952
952
  "1d": 1440,
953
953
  };
954
- const MS_PER_MINUTE$7 = 60000;
954
+ const MS_PER_MINUTE$8 = 60000;
955
955
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
956
956
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
957
957
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -2276,7 +2276,7 @@ class PersistCandleInstance {
2276
2276
  * @returns Promise resolving to candles in order, or null on cache miss
2277
2277
  */
2278
2278
  async readCandlesData(limit, sinceTimestamp, _untilTimestamp) {
2279
- const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
2279
+ const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
2280
2280
  const cachedCandles = [];
2281
2281
  for (let i = 0; i < limit; i++) {
2282
2282
  const expectedTimestamp = sinceTimestamp + i * stepMs;
@@ -2311,7 +2311,7 @@ class PersistCandleInstance {
2311
2311
  * @returns Promise that resolves when all writes are complete
2312
2312
  */
2313
2313
  async writeCandlesData(candles) {
2314
- const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
2314
+ const stepMs = INTERVAL_MINUTES$a[this.interval] * MS_PER_MINUTE$8;
2315
2315
  const now = Date.now();
2316
2316
  for (const candle of candles) {
2317
2317
  const candleCloseTime = candle.timestamp + stepMs;
@@ -3197,12 +3197,12 @@ class PersistMeasureUtils {
3197
3197
  * @param key - Cache key within the bucket
3198
3198
  * @returns Promise that resolves when write is complete
3199
3199
  */
3200
- this.writeMeasureData = async (data, bucket, key) => {
3200
+ this.writeMeasureData = async (data, bucket, key, when) => {
3201
3201
  LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3202
3202
  const isInitial = !this.getMeasureStorage.has(bucket);
3203
3203
  const instance = this.getMeasureStorage(bucket);
3204
3204
  await instance.waitForInit(isInitial);
3205
- return instance.writeMeasureData(data, key);
3205
+ return instance.writeMeasureData(data, key, when);
3206
3206
  };
3207
3207
  /**
3208
3208
  * Soft-deletes a measure entry in the given bucket by setting `removed: true`.
@@ -3438,12 +3438,12 @@ class PersistIntervalUtils {
3438
3438
  * @param key - Marker key within the bucket
3439
3439
  * @returns Promise that resolves when write is complete
3440
3440
  */
3441
- this.writeIntervalData = async (data, bucket, key) => {
3441
+ this.writeIntervalData = async (data, bucket, key, when) => {
3442
3442
  LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
3443
3443
  const isInitial = !this.getIntervalStorage.has(bucket);
3444
3444
  const instance = this.getIntervalStorage(bucket);
3445
3445
  await instance.waitForInit(isInitial);
3446
- return instance.writeIntervalData(data, key);
3446
+ return instance.writeIntervalData(data, key, when);
3447
3447
  };
3448
3448
  /**
3449
3449
  * Soft-deletes a marker in the given bucket by setting `removed: true`.
@@ -4629,8 +4629,8 @@ class CandleUtils {
4629
4629
  }
4630
4630
  const Candle = new CandleUtils();
4631
4631
 
4632
- const MS_PER_MINUTE$6 = 60000;
4633
- const INTERVAL_MINUTES$8 = {
4632
+ const MS_PER_MINUTE$7 = 60000;
4633
+ const INTERVAL_MINUTES$9 = {
4634
4634
  "1m": 1,
4635
4635
  "3m": 3,
4636
4636
  "5m": 5,
@@ -4661,7 +4661,7 @@ const INTERVAL_MINUTES$8 = {
4661
4661
  * @returns Aligned timestamp rounded down to interval boundary
4662
4662
  */
4663
4663
  const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
4664
- const intervalMs = intervalMinutes * MS_PER_MINUTE$6;
4664
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$7;
4665
4665
  return Math.floor(timestamp / intervalMs) * intervalMs;
4666
4666
  };
4667
4667
  /**
@@ -4815,9 +4815,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
4815
4815
  * @returns Promise resolving to array of candle data
4816
4816
  */
4817
4817
  const GET_CANDLES_FN$1 = async (dto, since, self) => {
4818
- const step = INTERVAL_MINUTES$8[dto.interval];
4818
+ const step = INTERVAL_MINUTES$9[dto.interval];
4819
4819
  const sinceTimestamp = since.getTime();
4820
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$6;
4820
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$7;
4821
4821
  await Candle.acquireLock(`ClientExchange GET_CANDLES_FN symbol=${dto.symbol} interval=${dto.interval} limit=${dto.limit}`);
4822
4822
  try {
4823
4823
  // Try to read from cache first
@@ -4931,11 +4931,11 @@ class ClientExchange {
4931
4931
  interval,
4932
4932
  limit,
4933
4933
  });
4934
- const step = INTERVAL_MINUTES$8[interval];
4934
+ const step = INTERVAL_MINUTES$9[interval];
4935
4935
  if (!step) {
4936
4936
  throw new Error(`ClientExchange unknown interval=${interval}`);
4937
4937
  }
4938
- const stepMs = step * MS_PER_MINUTE$6;
4938
+ const stepMs = step * MS_PER_MINUTE$7;
4939
4939
  // Align when down to interval boundary
4940
4940
  const whenTimestamp = this.params.execution.context.when.getTime();
4941
4941
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
@@ -5012,11 +5012,11 @@ class ClientExchange {
5012
5012
  if (!this.params.execution.context.backtest) {
5013
5013
  throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
5014
5014
  }
5015
- const step = INTERVAL_MINUTES$8[interval];
5015
+ const step = INTERVAL_MINUTES$9[interval];
5016
5016
  if (!step) {
5017
5017
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
5018
5018
  }
5019
- const stepMs = step * MS_PER_MINUTE$6;
5019
+ const stepMs = step * MS_PER_MINUTE$7;
5020
5020
  const now = Date.now();
5021
5021
  // Align when down to interval boundary
5022
5022
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -5201,11 +5201,11 @@ class ClientExchange {
5201
5201
  sDate,
5202
5202
  eDate,
5203
5203
  });
5204
- const step = INTERVAL_MINUTES$8[interval];
5204
+ const step = INTERVAL_MINUTES$9[interval];
5205
5205
  if (!step) {
5206
5206
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
5207
5207
  }
5208
- const stepMs = step * MS_PER_MINUTE$6;
5208
+ const stepMs = step * MS_PER_MINUTE$7;
5209
5209
  const whenTimestamp = this.params.execution.context.when.getTime();
5210
5210
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
5211
5211
  let sinceTimestamp;
@@ -5334,7 +5334,7 @@ class ClientExchange {
5334
5334
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
5335
5335
  const to = new Date(alignedTo);
5336
5336
  const from = new Date(alignedTo -
5337
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$6);
5337
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$7);
5338
5338
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
5339
5339
  }
5340
5340
  /**
@@ -5363,7 +5363,7 @@ class ClientExchange {
5363
5363
  const whenTimestamp = this.params.execution.context.when.getTime();
5364
5364
  // Align to 1-minute boundary to prevent look-ahead bias
5365
5365
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, 1);
5366
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$6 - MS_PER_MINUTE$6;
5366
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$7 - MS_PER_MINUTE$7;
5367
5367
  // No limit: fetch a single window and return as-is
5368
5368
  if (limit === undefined) {
5369
5369
  const to = new Date(alignedTo);
@@ -6317,7 +6317,7 @@ const validateScheduledSignal = (signal, currentPrice) => {
6317
6317
  }
6318
6318
  };
6319
6319
 
6320
- const INTERVAL_MINUTES$7 = {
6320
+ const INTERVAL_MINUTES$8 = {
6321
6321
  "1m": 1,
6322
6322
  "3m": 3,
6323
6323
  "5m": 5,
@@ -6738,7 +6738,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
6738
6738
  }
6739
6739
  const currentTime = self.params.execution.context.when.getTime();
6740
6740
  {
6741
- const intervalMinutes = INTERVAL_MINUTES$7[self.params.interval];
6741
+ const intervalMinutes = INTERVAL_MINUTES$8[self.params.interval];
6742
6742
  const intervalMs = intervalMinutes * 60 * 1000;
6743
6743
  const alignedTime = Math.floor(currentTime / intervalMs) * intervalMs;
6744
6744
  // Проверяем что наступил новый интервал (по aligned timestamp)
@@ -13733,7 +13733,7 @@ class StrategyConnectionService {
13733
13733
  * Maps FrameInterval to minutes for timestamp calculation.
13734
13734
  * Used to generate timeframe arrays with proper spacing.
13735
13735
  */
13736
- const INTERVAL_MINUTES$6 = {
13736
+ const INTERVAL_MINUTES$7 = {
13737
13737
  "1m": 1,
13738
13738
  "3m": 3,
13739
13739
  "5m": 5,
@@ -13787,7 +13787,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
13787
13787
  symbol,
13788
13788
  });
13789
13789
  const { interval, startDate, endDate } = self.params;
13790
- const intervalMinutes = INTERVAL_MINUTES$6[interval];
13790
+ const intervalMinutes = INTERVAL_MINUTES$7[interval];
13791
13791
  if (!intervalMinutes) {
13792
13792
  throw new Error(`ClientFrame unknown interval: ${interval}`);
13793
13793
  }
@@ -14152,8 +14152,8 @@ const get = (object, path) => {
14152
14152
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
14153
14153
  };
14154
14154
 
14155
- const MS_PER_MINUTE$5 = 60000;
14156
- const INTERVAL_MINUTES$5 = {
14155
+ const MS_PER_MINUTE$6 = 60000;
14156
+ const INTERVAL_MINUTES$6 = {
14157
14157
  "1m": 1,
14158
14158
  "3m": 3,
14159
14159
  "5m": 5,
@@ -14184,11 +14184,11 @@ const INTERVAL_MINUTES$5 = {
14184
14184
  * @returns New Date aligned down to interval boundary
14185
14185
  */
14186
14186
  const alignToInterval = (date, interval) => {
14187
- const minutes = INTERVAL_MINUTES$5[interval];
14187
+ const minutes = INTERVAL_MINUTES$6[interval];
14188
14188
  if (minutes === undefined) {
14189
14189
  throw new Error(`alignToInterval: unknown interval=${interval}`);
14190
14190
  }
14191
- const intervalMs = minutes * MS_PER_MINUTE$5;
14191
+ const intervalMs = minutes * MS_PER_MINUTE$6;
14192
14192
  return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
14193
14193
  };
14194
14194
 
@@ -19670,8 +19670,8 @@ class BacktestLogicPrivateService {
19670
19670
  }
19671
19671
 
19672
19672
  const EMITTER_CHECK_INTERVAL = 5000;
19673
- const MS_PER_MINUTE$4 = 60000;
19674
- const INTERVAL_MINUTES$4 = {
19673
+ const MS_PER_MINUTE$5 = 60000;
19674
+ const INTERVAL_MINUTES$5 = {
19675
19675
  "1m": 1,
19676
19676
  "3m": 3,
19677
19677
  "5m": 5,
@@ -19686,7 +19686,7 @@ const INTERVAL_MINUTES$4 = {
19686
19686
  };
19687
19687
  const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
19688
19688
  const tickSubject = new functoolsKit.Subject();
19689
- const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
19689
+ const intervalMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$5;
19690
19690
  {
19691
19691
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
19692
19692
  functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -35192,7 +35192,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
35192
35192
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
35193
35193
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
35194
35194
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
35195
- const MS_PER_MINUTE$3 = 60000;
35195
+ const MS_PER_MINUTE$4 = 60000;
35196
35196
  /**
35197
35197
  * Gets current timestamp from execution context if available.
35198
35198
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -35259,7 +35259,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
35259
35259
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
35260
35260
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
35261
35261
  };
35262
- const INTERVAL_MINUTES$3 = {
35262
+ const INTERVAL_MINUTES$4 = {
35263
35263
  "1m": 1,
35264
35264
  "3m": 3,
35265
35265
  "5m": 5,
@@ -35290,7 +35290,7 @@ const INTERVAL_MINUTES$3 = {
35290
35290
  * @returns Aligned timestamp rounded down to interval boundary
35291
35291
  */
35292
35292
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
35293
- const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
35293
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
35294
35294
  return Math.floor(timestamp / intervalMs) * intervalMs;
35295
35295
  };
35296
35296
  /**
@@ -35430,11 +35430,11 @@ class ExchangeInstance {
35430
35430
  limit,
35431
35431
  });
35432
35432
  const getCandles = this._methods.getCandles;
35433
- const step = INTERVAL_MINUTES$3[interval];
35433
+ const step = INTERVAL_MINUTES$4[interval];
35434
35434
  if (!step) {
35435
35435
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
35436
35436
  }
35437
- const stepMs = step * MS_PER_MINUTE$3;
35437
+ const stepMs = step * MS_PER_MINUTE$4;
35438
35438
  // Align when down to interval boundary
35439
35439
  const when = await GET_TIMESTAMP_FN();
35440
35440
  const whenTimestamp = when.getTime();
@@ -35648,7 +35648,7 @@ class ExchangeInstance {
35648
35648
  const when = await GET_TIMESTAMP_FN();
35649
35649
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
35650
35650
  const to = new Date(alignedTo);
35651
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
35651
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
35652
35652
  const isBacktest = await GET_BACKTEST_FN();
35653
35653
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
35654
35654
  };
@@ -35680,7 +35680,7 @@ class ExchangeInstance {
35680
35680
  const when = await GET_TIMESTAMP_FN();
35681
35681
  // Align to 1-minute boundary to prevent look-ahead bias
35682
35682
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
35683
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
35683
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$4 - MS_PER_MINUTE$4;
35684
35684
  const isBacktest = await GET_BACKTEST_FN();
35685
35685
  // No limit: fetch a single window and return as-is
35686
35686
  if (limit === undefined) {
@@ -35743,11 +35743,11 @@ class ExchangeInstance {
35743
35743
  sDate,
35744
35744
  eDate,
35745
35745
  });
35746
- const step = INTERVAL_MINUTES$3[interval];
35746
+ const step = INTERVAL_MINUTES$4[interval];
35747
35747
  if (!step) {
35748
35748
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
35749
35749
  }
35750
- const stepMs = step * MS_PER_MINUTE$3;
35750
+ const stepMs = step * MS_PER_MINUTE$4;
35751
35751
  const when = await GET_TIMESTAMP_FN();
35752
35752
  const nowTimestamp = when.getTime();
35753
35753
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -36059,8 +36059,8 @@ const Exchange = new ExchangeUtils();
36059
36059
 
36060
36060
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
36061
36061
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
36062
- const MS_PER_MINUTE$2 = 60000;
36063
- const INTERVAL_MINUTES$2 = {
36062
+ const MS_PER_MINUTE$3 = 60000;
36063
+ const INTERVAL_MINUTES$3 = {
36064
36064
  "1m": 1,
36065
36065
  "3m": 3,
36066
36066
  "5m": 5,
@@ -36074,7 +36074,7 @@ const INTERVAL_MINUTES$2 = {
36074
36074
  "1d": 1440,
36075
36075
  };
36076
36076
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
36077
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
36077
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
36078
36078
  return Math.floor(timestamp / intervalMs) * intervalMs;
36079
36079
  };
36080
36080
  const BAR_LENGTH = 30;
@@ -36099,11 +36099,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
36099
36099
  async function checkCandles(params) {
36100
36100
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
36101
36101
  backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
36102
- const step = INTERVAL_MINUTES$2[interval];
36102
+ const step = INTERVAL_MINUTES$3[interval];
36103
36103
  if (!step) {
36104
36104
  throw new Error(`checkCandles: unsupported interval=${interval}`);
36105
36105
  }
36106
- const stepMs = step * MS_PER_MINUTE$2;
36106
+ const stepMs = step * MS_PER_MINUTE$3;
36107
36107
  const dir = path.join(baseDir, exchangeName, symbol, interval);
36108
36108
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
36109
36109
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -36173,11 +36173,11 @@ async function warmCandles(params) {
36173
36173
  from,
36174
36174
  to,
36175
36175
  });
36176
- const step = INTERVAL_MINUTES$2[interval];
36176
+ const step = INTERVAL_MINUTES$3[interval];
36177
36177
  if (!step) {
36178
36178
  throw new Error(`warmCandles: unsupported interval=${interval}`);
36179
36179
  }
36180
- const stepMs = step * MS_PER_MINUTE$2;
36180
+ const stepMs = step * MS_PER_MINUTE$3;
36181
36181
  const instance = new ExchangeInstance(exchangeName);
36182
36182
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
36183
36183
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -48524,14 +48524,17 @@ class RecentPersistBacktestUtils {
48524
48524
  };
48525
48525
  /**
48526
48526
  * Retrieves the latest persisted signal for the given context.
48527
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48528
+ * (look-ahead bias protection).
48527
48529
  * @param symbol - Trading pair symbol
48528
48530
  * @param strategyName - Strategy identifier
48529
48531
  * @param exchangeName - Exchange identifier
48530
48532
  * @param frameName - Frame identifier
48531
48533
  * @param backtest - Flag indicating if the context is backtest or live
48532
- * @returns The latest signal or null if not found
48534
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48535
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48533
48536
  */
48534
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48537
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48535
48538
  backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
48536
48539
  symbol,
48537
48540
  strategyName,
@@ -48539,20 +48542,26 @@ class RecentPersistBacktestUtils {
48539
48542
  frameName,
48540
48543
  backtest: backtest$1,
48541
48544
  });
48542
- return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48545
+ const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48546
+ if (!signal || signal.timestamp > when.getTime()) {
48547
+ return null;
48548
+ }
48549
+ return signal;
48543
48550
  };
48544
48551
  /**
48545
48552
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48546
- * @param timestamp - Current timestamp in milliseconds
48553
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48554
+ * the requested one is treated as not yet visible.
48555
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48547
48556
  * @param symbol - Trading pair symbol
48548
48557
  * @param strategyName - Strategy identifier
48549
48558
  * @param exchangeName - Exchange identifier
48550
48559
  * @param frameName - Frame identifier
48551
48560
  * @param backtest - Flag indicating if the context is backtest or live
48552
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48561
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48553
48562
  */
48554
48563
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48555
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48564
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48556
48565
  if (!signal) {
48557
48566
  return null;
48558
48567
  }
@@ -48588,30 +48597,39 @@ class RecentMemoryBacktestUtils {
48588
48597
  };
48589
48598
  /**
48590
48599
  * Retrieves the latest in-memory signal for the given context.
48600
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48601
+ * (look-ahead bias protection).
48591
48602
  * @param symbol - Trading pair symbol
48592
48603
  * @param strategyName - Strategy identifier
48593
48604
  * @param exchangeName - Exchange identifier
48594
48605
  * @param frameName - Frame identifier
48595
48606
  * @param backtest - Flag indicating if the context is backtest or live
48596
- * @returns The latest signal or null if not found
48607
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48608
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48597
48609
  */
48598
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48610
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48599
48611
  const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
48600
48612
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
48601
- return this._signals.get(key) ?? null;
48613
+ const signal = this._signals.get(key) ?? null;
48614
+ if (!signal || signal.timestamp > when.getTime()) {
48615
+ return null;
48616
+ }
48617
+ return signal;
48602
48618
  };
48603
48619
  /**
48604
48620
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48605
- * @param timestamp - Current timestamp in milliseconds
48621
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48622
+ * the requested one is treated as not yet visible.
48623
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48606
48624
  * @param symbol - Trading pair symbol
48607
48625
  * @param strategyName - Strategy identifier
48608
48626
  * @param exchangeName - Exchange identifier
48609
48627
  * @param frameName - Frame identifier
48610
48628
  * @param backtest - Flag indicating if the context is backtest or live
48611
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48629
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48612
48630
  */
48613
48631
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48614
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48632
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48615
48633
  if (!signal) {
48616
48634
  return null;
48617
48635
  }
@@ -48643,14 +48661,17 @@ class RecentPersistLiveUtils {
48643
48661
  };
48644
48662
  /**
48645
48663
  * Retrieves the latest persisted signal for the given context.
48664
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48665
+ * (look-ahead bias protection).
48646
48666
  * @param symbol - Trading pair symbol
48647
48667
  * @param strategyName - Strategy identifier
48648
48668
  * @param exchangeName - Exchange identifier
48649
48669
  * @param frameName - Frame identifier
48650
48670
  * @param backtest - Flag indicating if the context is backtest or live
48651
- * @returns The latest signal or null if not found
48671
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48672
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48652
48673
  */
48653
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48674
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48654
48675
  backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
48655
48676
  symbol,
48656
48677
  strategyName,
@@ -48658,20 +48679,26 @@ class RecentPersistLiveUtils {
48658
48679
  frameName,
48659
48680
  backtest: backtest$1,
48660
48681
  });
48661
- return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48682
+ const signal = await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
48683
+ if (!signal || signal.timestamp > when.getTime()) {
48684
+ return null;
48685
+ }
48686
+ return signal;
48662
48687
  };
48663
48688
  /**
48664
48689
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48665
- * @param timestamp - Current timestamp in milliseconds
48690
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48691
+ * the requested one is treated as not yet visible.
48692
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48666
48693
  * @param symbol - Trading pair symbol
48667
48694
  * @param strategyName - Strategy identifier
48668
48695
  * @param exchangeName - Exchange identifier
48669
48696
  * @param frameName - Frame identifier
48670
48697
  * @param backtest - Flag indicating if the context is backtest or live
48671
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48698
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48672
48699
  */
48673
48700
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48674
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48701
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48675
48702
  if (!signal) {
48676
48703
  return null;
48677
48704
  }
@@ -48707,30 +48734,39 @@ class RecentMemoryLiveUtils {
48707
48734
  };
48708
48735
  /**
48709
48736
  * Retrieves the latest in-memory signal for the given context.
48737
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
48738
+ * (look-ahead bias protection).
48710
48739
  * @param symbol - Trading pair symbol
48711
48740
  * @param strategyName - Strategy identifier
48712
48741
  * @param exchangeName - Exchange identifier
48713
48742
  * @param frameName - Frame identifier
48714
48743
  * @param backtest - Flag indicating if the context is backtest or live
48715
- * @returns The latest signal or null if not found
48744
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48745
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48716
48746
  */
48717
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48747
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48718
48748
  const key = CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest$1);
48719
48749
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
48720
- return this._signals.get(key) ?? null;
48750
+ const signal = this._signals.get(key) ?? null;
48751
+ if (!signal || signal.timestamp > when.getTime()) {
48752
+ return null;
48753
+ }
48754
+ return signal;
48721
48755
  };
48722
48756
  /**
48723
48757
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48724
- * @param timestamp - Current timestamp in milliseconds
48758
+ * `timestamp` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
48759
+ * the requested one is treated as not yet visible.
48760
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48725
48761
  * @param symbol - Trading pair symbol
48726
48762
  * @param strategyName - Strategy identifier
48727
48763
  * @param exchangeName - Exchange identifier
48728
48764
  * @param frameName - Frame identifier
48729
48765
  * @param backtest - Flag indicating if the context is backtest or live
48730
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48766
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48731
48767
  */
48732
48768
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
48733
- const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
48769
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest, new Date(timestamp));
48734
48770
  if (!signal) {
48735
48771
  return null;
48736
48772
  }
@@ -48770,9 +48806,10 @@ class RecentBacktestAdapter {
48770
48806
  * @param exchangeName - Exchange identifier
48771
48807
  * @param frameName - Frame identifier
48772
48808
  * @param backtest - Flag indicating if the context is backtest or live
48773
- * @returns The latest signal or null if not found
48809
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48810
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48774
48811
  */
48775
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48812
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48776
48813
  backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
48777
48814
  symbol,
48778
48815
  strategyName,
@@ -48780,18 +48817,20 @@ class RecentBacktestAdapter {
48780
48817
  frameName,
48781
48818
  backtest: backtest$1,
48782
48819
  });
48783
- return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48820
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
48784
48821
  };
48785
48822
  /**
48786
48823
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48787
- * Proxies call to the underlying storage adapter.
48788
- * @param timestamp - Current timestamp in milliseconds
48824
+ * Proxies call to the underlying storage adapter. `timestamp` doubles as the
48825
+ * look-ahead cutoff a signal whose `timestamp` exceeds the requested one is
48826
+ * treated as not yet visible.
48827
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48789
48828
  * @param symbol - Trading pair symbol
48790
48829
  * @param strategyName - Strategy identifier
48791
48830
  * @param exchangeName - Exchange identifier
48792
48831
  * @param frameName - Frame identifier
48793
48832
  * @param backtest - Flag indicating if the context is backtest or live
48794
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48833
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48795
48834
  */
48796
48835
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
48797
48836
  backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
@@ -48802,7 +48841,7 @@ class RecentBacktestAdapter {
48802
48841
  backtest: backtest$1,
48803
48842
  timestamp,
48804
48843
  });
48805
- const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48844
+ const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
48806
48845
  if (!signal) {
48807
48846
  return null;
48808
48847
  }
@@ -48874,9 +48913,10 @@ class RecentLiveAdapter {
48874
48913
  * @param exchangeName - Exchange identifier
48875
48914
  * @param frameName - Frame identifier
48876
48915
  * @param backtest - Flag indicating if the context is backtest or live
48877
- * @returns The latest signal or null if not found
48916
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
48917
+ * @returns The latest signal or null if not found / shadowed by look-ahead
48878
48918
  */
48879
- this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
48919
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1, when) => {
48880
48920
  backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
48881
48921
  symbol,
48882
48922
  strategyName,
@@ -48884,18 +48924,20 @@ class RecentLiveAdapter {
48884
48924
  frameName,
48885
48925
  backtest: backtest$1,
48886
48926
  });
48887
- return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48927
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, when);
48888
48928
  };
48889
48929
  /**
48890
48930
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
48891
- * Proxies call to the underlying storage adapter.
48892
- * @param timestamp - Current timestamp in milliseconds
48931
+ * Proxies call to the underlying storage adapter. `timestamp` doubles as the
48932
+ * look-ahead cutoff a signal whose `timestamp` exceeds the requested one is
48933
+ * treated as not yet visible.
48934
+ * @param timestamp - Current timestamp in milliseconds (also serves as look-ahead cutoff)
48893
48935
  * @param symbol - Trading pair symbol
48894
48936
  * @param strategyName - Strategy identifier
48895
48937
  * @param exchangeName - Exchange identifier
48896
48938
  * @param frameName - Frame identifier
48897
48939
  * @param backtest - Flag indicating if the context is backtest or live
48898
- * @returns Whole minutes since the latest signal was created, or null if no signal found
48940
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
48899
48941
  */
48900
48942
  this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
48901
48943
  backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
@@ -48906,7 +48948,7 @@ class RecentLiveAdapter {
48906
48948
  backtest: backtest$1,
48907
48949
  timestamp,
48908
48950
  });
48909
- const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
48951
+ const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1, new Date(timestamp));
48910
48952
  if (!signal) {
48911
48953
  return null;
48912
48954
  }
@@ -48996,14 +49038,16 @@ class RecentAdapter {
48996
49038
  /**
48997
49039
  * Retrieves the latest active signal for the given symbol and context.
48998
49040
  * Searches backtest storage first, then live storage.
49041
+ * Returns null if the stored signal's `timestamp` is greater than the requested `when`
49042
+ * (look-ahead bias protection).
48999
49043
  *
49000
49044
  * @param symbol - Trading pair symbol
49001
49045
  * @param context - Execution context with strategyName, exchangeName, and frameName
49002
- * @param backtest - Flag indicating if the context is backtest or live
49003
- * @returns The latest signal or null if not found
49046
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
49047
+ * @returns The latest signal or null if not found / shadowed by look-ahead
49004
49048
  * @throws Error if RecentAdapter is not enabled
49005
49049
  */
49006
- this.getLatestSignal = async (symbol, context) => {
49050
+ this.getLatestSignal = async (symbol, context, when) => {
49007
49051
  backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
49008
49052
  symbol,
49009
49053
  context,
@@ -49012,10 +49056,10 @@ class RecentAdapter {
49012
49056
  throw new Error("RecentAdapter is not enabled. Call enable() first.");
49013
49057
  }
49014
49058
  let result = null;
49015
- if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
49059
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
49016
49060
  return result;
49017
49061
  }
49018
- if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
49062
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
49019
49063
  return result;
49020
49064
  }
49021
49065
  return null;
@@ -49023,13 +49067,16 @@ class RecentAdapter {
49023
49067
  /**
49024
49068
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
49025
49069
  * Searches backtest storage first, then live storage.
49026
- * @param timestamp - Current timestamp in milliseconds
49070
+ * `when` doubles as the look-ahead cutoff — a signal whose `timestamp` exceeds
49071
+ * `when.getTime()` is treated as not yet visible — and as the "now" against
49072
+ * which elapsed minutes are computed.
49027
49073
  * @param symbol - Trading pair symbol
49028
49074
  * @param context - Execution context with strategyName, exchangeName, and frameName
49029
- * @returns Whole minutes since the latest signal was created, or null if no signal found
49075
+ * @param when - Logical timestamp at which the read is happening (look-ahead cutoff + "now")
49076
+ * @returns Whole minutes since the latest signal was created, or null if no signal found / shadowed by look-ahead
49030
49077
  * @throws Error if RecentAdapter is not enabled
49031
49078
  */
49032
- this.getMinutesSinceLatestSignalCreated = async (symbol, context) => {
49079
+ this.getMinutesSinceLatestSignalCreated = async (symbol, context, when) => {
49033
49080
  backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
49034
49081
  symbol,
49035
49082
  context,
@@ -49038,13 +49085,11 @@ class RecentAdapter {
49038
49085
  throw new Error("RecentAdapter is not enabled. Call enable() first.");
49039
49086
  }
49040
49087
  let signal = null;
49041
- if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
49042
- const timestamp = await backtest.timeMetaService.getTimestamp(symbol, context, true);
49043
- return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
49088
+ if (signal = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true, when)) {
49089
+ return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
49044
49090
  }
49045
- if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
49046
- const timestamp = await backtest.timeMetaService.getTimestamp(symbol, context, false);
49047
- return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
49091
+ if (signal = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false, when)) {
49092
+ return Math.floor((when.getTime() - signal.timestamp) / (1000 * 60));
49048
49093
  }
49049
49094
  return null;
49050
49095
  };
@@ -49109,41 +49154,54 @@ class StateLocalInstance {
49109
49154
  this.initialValue = initialValue;
49110
49155
  this.signalId = signalId;
49111
49156
  this.bucketName = bucketName;
49157
+ this._when = 0;
49112
49158
  /**
49113
49159
  * Initializes _value from initialValue - local state needs no async setup.
49114
49160
  * @returns Promise that resolves immediately
49115
49161
  */
49116
49162
  this.waitForInit = functoolsKit.singleshot(async (_initial) => {
49117
49163
  this._value = this.initialValue;
49164
+ this._when = 0;
49118
49165
  });
49119
49166
  /**
49120
49167
  * Update the in-memory state value.
49168
+ * Records `when` so future reads with a smaller `when` see `initialValue`.
49169
+ * The dispatch updater receives the look-ahead-guarded current value.
49121
49170
  * @param dispatch - New value or updater function receiving current value
49171
+ * @param when - Logical timestamp this value belongs to
49122
49172
  * @returns Updated state value
49123
49173
  */
49124
- this.setState = functoolsKit.queued(async (dispatch) => {
49174
+ this.setState = functoolsKit.queued(async (dispatch, when) => {
49125
49175
  backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_SET, {
49126
49176
  signalId: this.signalId,
49127
49177
  bucketName: this.bucketName,
49128
49178
  });
49129
49179
  if (typeof dispatch === "function") {
49130
- this._value = await dispatch(this._value);
49180
+ const prev = this._when > when.getTime() ? this.initialValue : this._value;
49181
+ this._value = await dispatch(prev);
49131
49182
  }
49132
49183
  else {
49133
49184
  this._value = dispatch;
49134
49185
  }
49186
+ this._when = when.getTime();
49135
49187
  return this._value;
49136
49188
  });
49137
49189
  }
49138
49190
  /**
49139
49191
  * Read the current in-memory state value.
49192
+ * Returns `initialValue` when the stored `when` is greater than the requested `when`
49193
+ * (look-ahead bias protection).
49194
+ * @param when - Logical timestamp at which the read is happening
49140
49195
  * @returns Current state value
49141
49196
  */
49142
- async getState() {
49197
+ async getState(when) {
49143
49198
  backtest.loggerService.debug(STATE_LOCAL_INSTANCE_METHOD_NAME_GET, {
49144
49199
  signalId: this.signalId,
49145
49200
  bucketName: this.bucketName,
49146
49201
  });
49202
+ if (this._when > when.getTime()) {
49203
+ return this.initialValue;
49204
+ }
49147
49205
  return this._value;
49148
49206
  }
49149
49207
  /** Releases resources held by this instance. */
@@ -49177,14 +49235,14 @@ class StateDummyInstance {
49177
49235
  * No-op read - always returns initialValue.
49178
49236
  * @returns initialValue
49179
49237
  */
49180
- async getState() {
49238
+ async getState(_when) {
49181
49239
  return this.initialValue;
49182
49240
  }
49183
49241
  /**
49184
49242
  * No-op write - discards the value and returns initialValue.
49185
49243
  * @returns initialValue
49186
49244
  */
49187
- async setState(_dispatch) {
49245
+ async setState(_dispatch, _when) {
49188
49246
  return this.initialValue;
49189
49247
  }
49190
49248
  /** No-op. */
@@ -49210,6 +49268,7 @@ class StatePersistInstance {
49210
49268
  this.initialValue = initialValue;
49211
49269
  this.signalId = signalId;
49212
49270
  this.bucketName = bucketName;
49271
+ this._when = 0;
49213
49272
  /**
49214
49273
  * Initialize persistence storage and restore state from disk.
49215
49274
  * @param initial - Whether this is the first initialization
@@ -49224,40 +49283,54 @@ class StatePersistInstance {
49224
49283
  const data = await PersistStateAdapter.readStateData(this.signalId, this.bucketName);
49225
49284
  if (data) {
49226
49285
  this._value = data.data;
49286
+ this._when = data.when;
49227
49287
  return;
49228
49288
  }
49229
49289
  this._value = this.initialValue;
49290
+ this._when = 0;
49230
49291
  });
49231
49292
  /**
49232
49293
  * Update state and persist to disk atomically.
49294
+ * A write with a smaller `when` overwrites an existing record — that lets a
49295
+ * restarted backtest reset live-written state without breaking live.
49296
+ * The dispatch updater receives the look-ahead-guarded current value.
49233
49297
  * @param dispatch - New value or updater function receiving current value
49298
+ * @param when - Logical timestamp this value belongs to
49234
49299
  * @returns Updated state value
49235
49300
  */
49236
- this.setState = functoolsKit.queued(async (dispatch) => {
49301
+ this.setState = functoolsKit.queued(async (dispatch, when) => {
49237
49302
  backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_SET, {
49238
49303
  signalId: this.signalId,
49239
49304
  bucketName: this.bucketName,
49240
49305
  });
49241
49306
  if (typeof dispatch === "function") {
49242
- this._value = await dispatch(this._value);
49307
+ const prev = this._when > when.getTime() ? this.initialValue : this._value;
49308
+ this._value = await dispatch(prev);
49243
49309
  }
49244
49310
  else {
49245
49311
  this._value = dispatch;
49246
49312
  }
49313
+ this._when = when.getTime();
49247
49314
  const id = CREATE_KEY_FN$6(this.signalId, this.bucketName);
49248
- await PersistStateAdapter.writeStateData({ id, data: this._value }, this.signalId, this.bucketName);
49315
+ await PersistStateAdapter.writeStateData({ id, data: this._value, when: this._when }, this.signalId, this.bucketName);
49249
49316
  return this._value;
49250
49317
  });
49251
49318
  }
49252
49319
  /**
49253
49320
  * Read the current persisted state value.
49321
+ * Returns `initialValue` when the stored `when` is greater than the requested `when`
49322
+ * (look-ahead bias protection).
49323
+ * @param when - Logical timestamp at which the read is happening
49254
49324
  * @returns Current state value
49255
49325
  */
49256
- async getState() {
49326
+ async getState(when) {
49257
49327
  backtest.loggerService.debug(STATE_PERSIST_INSTANCE_METHOD_NAME_GET, {
49258
49328
  signalId: this.signalId,
49259
49329
  bucketName: this.bucketName,
49260
49330
  });
49331
+ if (this._when > when.getTime()) {
49332
+ return this.initialValue;
49333
+ }
49261
49334
  return this._value;
49262
49335
  }
49263
49336
  /** Releases resources held by this instance. */
@@ -49310,6 +49383,7 @@ class StateBacktestAdapter {
49310
49383
  * @param dto.signalId - Signal identifier
49311
49384
  * @param dto.bucketName - Bucket name
49312
49385
  * @param dto.initialValue - Default value when no persisted state exists
49386
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49313
49387
  * @returns Current state value
49314
49388
  */
49315
49389
  this.getState = async (dto) => {
@@ -49321,7 +49395,7 @@ class StateBacktestAdapter {
49321
49395
  const isInitial = !this.getInstance.has(key);
49322
49396
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49323
49397
  await instance.waitForInit(isInitial);
49324
- return await instance.getState();
49398
+ return await instance.getState(dto.when);
49325
49399
  };
49326
49400
  /**
49327
49401
  * Update the state value for a signal.
@@ -49329,6 +49403,7 @@ class StateBacktestAdapter {
49329
49403
  * @param dto.signalId - Signal identifier
49330
49404
  * @param dto.bucketName - Bucket name
49331
49405
  * @param dto.initialValue - Default value when no persisted state exists
49406
+ * @param dto.when - Logical timestamp this value belongs to
49332
49407
  * @returns Updated state value
49333
49408
  */
49334
49409
  this.setState = async (dispatch, dto) => {
@@ -49340,7 +49415,7 @@ class StateBacktestAdapter {
49340
49415
  const isInitial = !this.getInstance.has(key);
49341
49416
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49342
49417
  await instance.waitForInit(isInitial);
49343
- return await instance.setState(dispatch);
49418
+ return await instance.setState(dispatch, dto.when);
49344
49419
  };
49345
49420
  /**
49346
49421
  * Switches to in-memory adapter (default).
@@ -49425,6 +49500,7 @@ class StateLiveAdapter {
49425
49500
  * @param dto.signalId - Signal identifier
49426
49501
  * @param dto.bucketName - Bucket name
49427
49502
  * @param dto.initialValue - Default value when no persisted state exists
49503
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49428
49504
  * @returns Current state value
49429
49505
  */
49430
49506
  this.getState = async (dto) => {
@@ -49436,7 +49512,7 @@ class StateLiveAdapter {
49436
49512
  const isInitial = !this.getInstance.has(key);
49437
49513
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49438
49514
  await instance.waitForInit(isInitial);
49439
- return await instance.getState();
49515
+ return await instance.getState(dto.when);
49440
49516
  };
49441
49517
  /**
49442
49518
  * Update the state value for a signal.
@@ -49444,6 +49520,7 @@ class StateLiveAdapter {
49444
49520
  * @param dto.signalId - Signal identifier
49445
49521
  * @param dto.bucketName - Bucket name
49446
49522
  * @param dto.initialValue - Default value when no persisted state exists
49523
+ * @param dto.when - Logical timestamp this value belongs to
49447
49524
  * @returns Updated state value
49448
49525
  */
49449
49526
  this.setState = async (dispatch, dto) => {
@@ -49455,7 +49532,7 @@ class StateLiveAdapter {
49455
49532
  const isInitial = !this.getInstance.has(key);
49456
49533
  const instance = this.getInstance(dto.signalId, dto.bucketName, dto.initialValue);
49457
49534
  await instance.waitForInit(isInitial);
49458
- return await instance.setState(dispatch);
49535
+ return await instance.setState(dispatch, dto.when);
49459
49536
  };
49460
49537
  /**
49461
49538
  * Switches to in-memory adapter.
@@ -49552,6 +49629,7 @@ class StateAdapter {
49552
49629
  * @param dto.bucketName - Bucket name
49553
49630
  * @param dto.initialValue - Default value when no persisted state exists
49554
49631
  * @param dto.backtest - Flag indicating if the context is backtest or live
49632
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
49555
49633
  * @returns Current state value
49556
49634
  * @throws Error if adapter is not enabled
49557
49635
  */
@@ -49577,6 +49655,7 @@ class StateAdapter {
49577
49655
  * @param dto.bucketName - Bucket name
49578
49656
  * @param dto.initialValue - Default value when no persisted state exists
49579
49657
  * @param dto.backtest - Flag indicating if the context is backtest or live
49658
+ * @param dto.when - Logical timestamp this value belongs to
49580
49659
  * @returns Updated state value
49581
49660
  * @throws Error if adapter is not enabled
49582
49661
  */
@@ -49650,8 +49729,9 @@ async function getLatestSignal(symbol) {
49650
49729
  if (!MethodContextService.hasContext()) {
49651
49730
  throw new Error("getLatestSignal requires a method context");
49652
49731
  }
49732
+ const { when } = backtest.executionContextService.context;
49653
49733
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49654
- return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
49734
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName }, when);
49655
49735
  }
49656
49736
  /**
49657
49737
  * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
@@ -49686,8 +49766,9 @@ async function getMinutesSinceLatestSignalCreated(symbol) {
49686
49766
  if (!MethodContextService.hasContext()) {
49687
49767
  throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
49688
49768
  }
49769
+ const { when } = backtest.executionContextService.context;
49689
49770
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49690
- return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName });
49771
+ return await Recent.getMinutesSinceLatestSignalCreated(symbol, { exchangeName, frameName, strategyName }, when);
49691
49772
  }
49692
49773
  /**
49693
49774
  * Reads the state value scoped to the current active signal.
@@ -49732,7 +49813,7 @@ async function getSignalState(symbol, dto) {
49732
49813
  if (!MethodContextService.hasContext()) {
49733
49814
  throw new Error("getSignalState requires a method context");
49734
49815
  }
49735
- const { backtest: isBacktest } = backtest.executionContextService.context;
49816
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
49736
49817
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49737
49818
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49738
49819
  let signal;
@@ -49742,6 +49823,7 @@ async function getSignalState(symbol, dto) {
49742
49823
  bucketName,
49743
49824
  initialValue,
49744
49825
  backtest: isBacktest,
49826
+ when,
49745
49827
  });
49746
49828
  }
49747
49829
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -49750,6 +49832,7 @@ async function getSignalState(symbol, dto) {
49750
49832
  bucketName,
49751
49833
  initialValue,
49752
49834
  backtest: isBacktest,
49835
+ when,
49753
49836
  });
49754
49837
  }
49755
49838
  throw new Error(`getSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -49801,7 +49884,7 @@ async function setSignalState(symbol, dispatch, dto) {
49801
49884
  if (!MethodContextService.hasContext()) {
49802
49885
  throw new Error("setSignalState requires a method context");
49803
49886
  }
49804
- const { backtest: isBacktest } = backtest.executionContextService.context;
49887
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
49805
49888
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49806
49889
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
49807
49890
  let signal;
@@ -49811,6 +49894,7 @@ async function setSignalState(symbol, dispatch, dto) {
49811
49894
  bucketName,
49812
49895
  initialValue,
49813
49896
  backtest: isBacktest,
49897
+ when,
49814
49898
  });
49815
49899
  }
49816
49900
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -49819,6 +49903,7 @@ async function setSignalState(symbol, dispatch, dto) {
49819
49903
  bucketName,
49820
49904
  initialValue,
49821
49905
  backtest: isBacktest,
49906
+ when,
49822
49907
  });
49823
49908
  }
49824
49909
  throw new Error(`setSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -49872,36 +49957,47 @@ class SessionLocalInstance {
49872
49957
  this.frameName = frameName;
49873
49958
  this.backtest = backtest$1;
49874
49959
  this._data = null;
49960
+ this._when = 0;
49875
49961
  /**
49876
49962
  * Initializes _data to null — local session needs no async setup.
49877
49963
  * @returns Promise that resolves immediately
49878
49964
  */
49879
49965
  this.waitForInit = functoolsKit.singleshot(async (_initial) => {
49880
49966
  this._data = null;
49967
+ this._when = 0;
49881
49968
  });
49882
49969
  /**
49883
49970
  * Read the current in-memory session value.
49884
- * @returns Current session value, or null if not set
49971
+ * Returns null if the stored `when` is greater than the requested `when`
49972
+ * (look-ahead bias protection).
49973
+ * @param when - Logical timestamp at which the read is happening
49974
+ * @returns Current session value, or null
49885
49975
  */
49886
- this.getData = async () => {
49976
+ this.getData = async (when) => {
49887
49977
  backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_GET, {
49888
49978
  strategyName: this.strategyName,
49889
49979
  exchangeName: this.exchangeName,
49890
49980
  frameName: this.frameName,
49891
49981
  });
49982
+ if (this._when > when.getTime()) {
49983
+ return null;
49984
+ }
49892
49985
  return this._data;
49893
49986
  };
49894
49987
  /**
49895
49988
  * Update the in-memory session value.
49989
+ * Records `when` so future reads with a smaller `when` see no value.
49896
49990
  * @param value - New value or null to clear
49991
+ * @param when - Logical timestamp this value belongs to
49897
49992
  */
49898
- this.setData = async (value) => {
49993
+ this.setData = async (value, when) => {
49899
49994
  backtest.loggerService.debug(SESSION_LOCAL_INSTANCE_METHOD_NAME_SET, {
49900
49995
  strategyName: this.strategyName,
49901
49996
  exchangeName: this.exchangeName,
49902
49997
  frameName: this.frameName,
49903
49998
  });
49904
49999
  this._data = value;
50000
+ this._when = when.getTime();
49905
50001
  };
49906
50002
  }
49907
50003
  /** Releases resources held by this instance. */
@@ -49937,13 +50033,13 @@ class SessionDummyInstance {
49937
50033
  * No-op read — always returns null.
49938
50034
  * @returns null
49939
50035
  */
49940
- this.getData = async () => {
50036
+ this.getData = async (_when) => {
49941
50037
  return null;
49942
50038
  };
49943
50039
  /**
49944
50040
  * No-op write — discards the value.
49945
50041
  */
49946
- this.setData = async (_value) => {
50042
+ this.setData = async (_value, _when) => {
49947
50043
  };
49948
50044
  }
49949
50045
  /** No-op. */
@@ -49969,6 +50065,7 @@ class SessionPersistInstance {
49969
50065
  this.frameName = frameName;
49970
50066
  this.backtest = backtest$1;
49971
50067
  this._data = null;
50068
+ this._when = 0;
49972
50069
  /**
49973
50070
  * Initialize persistence storage and restore session from disk.
49974
50071
  * @param initial - Whether this is the first initialization
@@ -49984,35 +50081,47 @@ class SessionPersistInstance {
49984
50081
  const data = await PersistSessionAdapter.readSessionData(this.strategyName, this.exchangeName, this.frameName);
49985
50082
  if (data) {
49986
50083
  this._data = data.data;
50084
+ this._when = data.when;
49987
50085
  return;
49988
50086
  }
49989
50087
  this._data = null;
50088
+ this._when = 0;
49990
50089
  });
49991
50090
  /**
49992
50091
  * Read the current persisted session value.
49993
- * @returns Current session value, or null if not set
50092
+ * Returns null if the stored `when` is greater than the requested `when`
50093
+ * (look-ahead bias protection).
50094
+ * @param when - Logical timestamp at which the read is happening
50095
+ * @returns Current session value, or null
49994
50096
  */
49995
- this.getData = async () => {
50097
+ this.getData = async (when) => {
49996
50098
  backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_GET, {
49997
50099
  strategyName: this.strategyName,
49998
50100
  exchangeName: this.exchangeName,
49999
50101
  frameName: this.frameName,
50000
50102
  });
50103
+ if (this._when > when.getTime()) {
50104
+ return null;
50105
+ }
50001
50106
  return this._data;
50002
50107
  };
50003
50108
  /**
50004
50109
  * Update session value and persist to disk atomically.
50110
+ * A write with a smaller `when` overwrites an existing record — that lets
50111
+ * a restarted backtest reset live-written state without breaking live.
50005
50112
  * @param value - New value or null to clear
50113
+ * @param when - Logical timestamp this value belongs to
50006
50114
  */
50007
- this.setData = async (value) => {
50115
+ this.setData = async (value, when) => {
50008
50116
  backtest.loggerService.debug(SESSION_PERSIST_INSTANCE_METHOD_NAME_SET, {
50009
50117
  strategyName: this.strategyName,
50010
50118
  exchangeName: this.exchangeName,
50011
50119
  frameName: this.frameName,
50012
50120
  });
50013
50121
  this._data = value;
50122
+ this._when = when.getTime();
50014
50123
  const id = CREATE_KEY_FN$5(this.symbol, this.strategyName, this.exchangeName, this.frameName, this.backtest);
50015
- await PersistSessionAdapter.writeSessionData({ id, data: value }, this.strategyName, this.exchangeName, this.frameName);
50124
+ await PersistSessionAdapter.writeSessionData({ id, data: value, when: this._when }, this.strategyName, this.exchangeName, this.frameName);
50016
50125
  };
50017
50126
  }
50018
50127
  /** Releases resources held by this instance. */
@@ -50045,9 +50154,10 @@ class SessionBacktestAdapter {
50045
50154
  * @param context.strategyName - Strategy identifier
50046
50155
  * @param context.exchangeName - Exchange identifier
50047
50156
  * @param context.frameName - Frame identifier
50048
- * @returns Current session value, or null if not set
50157
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50158
+ * @returns Current session value, or null if not set / look-ahead
50049
50159
  */
50050
- this.getData = async (symbol, context) => {
50160
+ this.getData = async (symbol, context, when) => {
50051
50161
  backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_GET, {
50052
50162
  strategyName: context.strategyName,
50053
50163
  exchangeName: context.exchangeName,
@@ -50057,7 +50167,7 @@ class SessionBacktestAdapter {
50057
50167
  const isInitial = !this.getInstance.has(key);
50058
50168
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
50059
50169
  await instance.waitForInit(isInitial);
50060
- return await instance.getData();
50170
+ return await instance.getData(when);
50061
50171
  };
50062
50172
  /**
50063
50173
  * Update the session value for a backtest run.
@@ -50066,8 +50176,9 @@ class SessionBacktestAdapter {
50066
50176
  * @param context.strategyName - Strategy identifier
50067
50177
  * @param context.exchangeName - Exchange identifier
50068
50178
  * @param context.frameName - Frame identifier
50179
+ * @param when - Logical timestamp this value belongs to
50069
50180
  */
50070
- this.setData = async (symbol, value, context) => {
50181
+ this.setData = async (symbol, value, context, when) => {
50071
50182
  backtest.loggerService.debug(SESSION_BACKTEST_ADAPTER_METHOD_NAME_SET, {
50072
50183
  strategyName: context.strategyName,
50073
50184
  exchangeName: context.exchangeName,
@@ -50077,7 +50188,7 @@ class SessionBacktestAdapter {
50077
50188
  const isInitial = !this.getInstance.has(key);
50078
50189
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, true);
50079
50190
  await instance.waitForInit(isInitial);
50080
- return await instance.setData(value);
50191
+ return await instance.setData(value, when);
50081
50192
  };
50082
50193
  /**
50083
50194
  * Switches to in-memory adapter (default).
@@ -50141,9 +50252,10 @@ class SessionLiveAdapter {
50141
50252
  * @param context.strategyName - Strategy identifier
50142
50253
  * @param context.exchangeName - Exchange identifier
50143
50254
  * @param context.frameName - Frame identifier
50144
- * @returns Current session value, or null if not set
50255
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50256
+ * @returns Current session value, or null if not set / look-ahead
50145
50257
  */
50146
- this.getData = async (symbol, context) => {
50258
+ this.getData = async (symbol, context, when) => {
50147
50259
  backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_GET, {
50148
50260
  strategyName: context.strategyName,
50149
50261
  exchangeName: context.exchangeName,
@@ -50153,7 +50265,7 @@ class SessionLiveAdapter {
50153
50265
  const isInitial = !this.getInstance.has(key);
50154
50266
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
50155
50267
  await instance.waitForInit(isInitial);
50156
- return await instance.getData();
50268
+ return await instance.getData(when);
50157
50269
  };
50158
50270
  /**
50159
50271
  * Update the session value for a live run.
@@ -50162,8 +50274,9 @@ class SessionLiveAdapter {
50162
50274
  * @param context.strategyName - Strategy identifier
50163
50275
  * @param context.exchangeName - Exchange identifier
50164
50276
  * @param context.frameName - Frame identifier
50277
+ * @param when - Logical timestamp this value belongs to
50165
50278
  */
50166
- this.setData = async (symbol, value, context) => {
50279
+ this.setData = async (symbol, value, context, when) => {
50167
50280
  backtest.loggerService.debug(SESSION_LIVE_ADAPTER_METHOD_NAME_SET, {
50168
50281
  strategyName: context.strategyName,
50169
50282
  exchangeName: context.exchangeName,
@@ -50173,7 +50286,7 @@ class SessionLiveAdapter {
50173
50286
  const isInitial = !this.getInstance.has(key);
50174
50287
  const instance = this.getInstance(symbol, context.strategyName, context.exchangeName, context.frameName, false);
50175
50288
  await instance.waitForInit(isInitial);
50176
- return await instance.setData(value);
50289
+ return await instance.setData(value, when);
50177
50290
  };
50178
50291
  /**
50179
50292
  * Switches to in-memory adapter.
@@ -50233,9 +50346,10 @@ class SessionAdapter {
50233
50346
  * @param context.exchangeName - Exchange identifier
50234
50347
  * @param context.frameName - Frame identifier
50235
50348
  * @param backtest - Flag indicating if the context is backtest or live
50236
- * @returns Current session value, or null if not set
50349
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50350
+ * @returns Current session value, or null if not set / look-ahead
50237
50351
  */
50238
- this.getData = async (symbol, context, backtest$1) => {
50352
+ this.getData = async (symbol, context, backtest$1, when) => {
50239
50353
  backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_GET, {
50240
50354
  strategyName: context.strategyName,
50241
50355
  exchangeName: context.exchangeName,
@@ -50243,9 +50357,9 @@ class SessionAdapter {
50243
50357
  backtest: backtest$1,
50244
50358
  });
50245
50359
  if (backtest$1) {
50246
- return await SessionBacktest.getData(symbol, context);
50360
+ return await SessionBacktest.getData(symbol, context, when);
50247
50361
  }
50248
- return await SessionLive.getData(symbol, context);
50362
+ return await SessionLive.getData(symbol, context, when);
50249
50363
  };
50250
50364
  /**
50251
50365
  * Update the session value for a signal.
@@ -50256,8 +50370,9 @@ class SessionAdapter {
50256
50370
  * @param context.exchangeName - Exchange identifier
50257
50371
  * @param context.frameName - Frame identifier
50258
50372
  * @param backtest - Flag indicating if the context is backtest or live
50373
+ * @param when - Logical timestamp this value belongs to
50259
50374
  */
50260
- this.setData = async (symbol, value, context, backtest$1) => {
50375
+ this.setData = async (symbol, value, context, backtest$1, when) => {
50261
50376
  backtest.loggerService.debug(SESSION_ADAPTER_METHOD_NAME_SET, {
50262
50377
  strategyName: context.strategyName,
50263
50378
  exchangeName: context.exchangeName,
@@ -50265,9 +50380,9 @@ class SessionAdapter {
50265
50380
  backtest: backtest$1,
50266
50381
  });
50267
50382
  if (backtest$1) {
50268
- return await SessionBacktest.setData(symbol, value, context);
50383
+ return await SessionBacktest.setData(symbol, value, context, when);
50269
50384
  }
50270
- return await SessionLive.setData(symbol, value, context);
50385
+ return await SessionLive.setData(symbol, value, context, when);
50271
50386
  };
50272
50387
  }
50273
50388
  }
@@ -50319,9 +50434,9 @@ async function getSessionData(symbol) {
50319
50434
  if (!MethodContextService.hasContext()) {
50320
50435
  throw new Error("getSession requires a method context");
50321
50436
  }
50322
- const { backtest: isBacktest } = backtest.executionContextService.context;
50437
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50323
50438
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50324
- return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest);
50439
+ return await Session.getData(symbol, { exchangeName, frameName, strategyName }, isBacktest, when);
50325
50440
  }
50326
50441
  /**
50327
50442
  * Writes a session value scoped to the current (symbol, strategy, exchange, frame) context.
@@ -50353,9 +50468,9 @@ async function setSessionData(symbol, value) {
50353
50468
  if (!MethodContextService.hasContext()) {
50354
50469
  throw new Error("setSession requires a method context");
50355
50470
  }
50356
- const { backtest: isBacktest } = backtest.executionContextService.context;
50471
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50357
50472
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50358
- await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest);
50473
+ await Session.setData(symbol, value, { exchangeName, frameName, strategyName }, isBacktest, when);
50359
50474
  }
50360
50475
 
50361
50476
  const CREATE_SIGNAL_STATE_METHOD_NAME = "state.createSignalState";
@@ -50366,7 +50481,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50366
50481
  if (!MethodContextService.hasContext()) {
50367
50482
  throw new Error("createSignalState requires a method context");
50368
50483
  }
50369
- const { backtest: isBacktest } = backtest.executionContextService.context;
50484
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50370
50485
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50371
50486
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
50372
50487
  let signal;
@@ -50376,6 +50491,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50376
50491
  bucketName: params.bucketName,
50377
50492
  initialValue: params.initialValue,
50378
50493
  signalId: signal.id,
50494
+ when,
50379
50495
  });
50380
50496
  }
50381
50497
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -50384,6 +50500,7 @@ const CREATE_SET_STATE_FN = (params) => async (symbol, dispatch) => {
50384
50500
  bucketName: params.bucketName,
50385
50501
  initialValue: params.initialValue,
50386
50502
  signalId: signal.id,
50503
+ when,
50387
50504
  });
50388
50505
  }
50389
50506
  throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
@@ -50395,7 +50512,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50395
50512
  if (!MethodContextService.hasContext()) {
50396
50513
  throw new Error("createSignalState requires a method context");
50397
50514
  }
50398
- const { backtest: isBacktest } = backtest.executionContextService.context;
50515
+ const { backtest: isBacktest, when } = backtest.executionContextService.context;
50399
50516
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
50400
50517
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
50401
50518
  let signal;
@@ -50405,6 +50522,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50405
50522
  bucketName: params.bucketName,
50406
50523
  initialValue: params.initialValue,
50407
50524
  signalId: signal.id,
50525
+ when,
50408
50526
  });
50409
50527
  }
50410
50528
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -50413,6 +50531,7 @@ const CREATE_GET_STATE_FN = (params) => async (symbol) => {
50413
50531
  bucketName: params.bucketName,
50414
50532
  initialValue: params.initialValue,
50415
50533
  signalId: signal.id,
50534
+ when,
50416
50535
  });
50417
50536
  }
50418
50537
  throw new Error(`createSignalState requires a pending or scheduled signal for symbol=${symbol} bucketName=${params.bucketName}`);
@@ -50495,7 +50614,7 @@ const createSearchIndex = () => {
50495
50614
  df.set(term, count);
50496
50615
  });
50497
50616
  };
50498
- const upsert = ({ id, content, index = JSON.stringify(content), priority = Date.now(), }) => {
50617
+ const upsert = ({ id, content, when, index = JSON.stringify(content), priority = Date.now(), }) => {
50499
50618
  const existing = docs.get(id);
50500
50619
  {
50501
50620
  existing && subtractDf(existing.tf);
@@ -50504,12 +50623,21 @@ const createSearchIndex = () => {
50504
50623
  const tf = new Map();
50505
50624
  for (const t of tokens)
50506
50625
  tf.set(t, (tf.get(t) ?? 0) + 1);
50507
- docs.set(id, { tf, len: tokens.length, content, priority });
50626
+ docs.set(id, { tf, len: tokens.length, content, priority, when });
50508
50627
  {
50509
50628
  addDf(tf);
50510
50629
  }
50511
50630
  };
50512
- const read = (id) => docs.get(id)?.content;
50631
+ /**
50632
+ * Read a document by id. Returns undefined when the document was written
50633
+ * with a `when` greater than the requested `when` (look-ahead guard).
50634
+ */
50635
+ const read = (id, when) => {
50636
+ const doc = docs.get(id);
50637
+ if (!doc || doc.when > when)
50638
+ return undefined;
50639
+ return doc.content;
50640
+ };
50513
50641
  const remove = (id) => {
50514
50642
  {
50515
50643
  const existing = docs.get(id);
@@ -50517,16 +50645,30 @@ const createSearchIndex = () => {
50517
50645
  }
50518
50646
  docs.delete(id);
50519
50647
  };
50520
- const list = () => Array.from(docs.entries())
50648
+ /**
50649
+ * List documents whose `when` is less than or equal to the requested `when`
50650
+ * (look-ahead guard), sorted by priority.
50651
+ */
50652
+ const list = (when) => Array.from(docs.entries())
50653
+ .filter(([, doc]) => doc.when <= when)
50521
50654
  .sort(([, a], [, b]) => a.priority - b.priority)
50522
50655
  .map(([id, { content }]) => ({ id, content }));
50523
- const search = (query, settings = DEFAULT_SETTINGS) => {
50656
+ /**
50657
+ * BM25 search over documents whose `when` is less than or equal to the
50658
+ * requested `when` (look-ahead guard).
50659
+ *
50660
+ * Document frequency (df) is computed across the whole index — the time-cut
50661
+ * is applied only to the candidate set, so scores stay comparable across
50662
+ * different `when` values.
50663
+ */
50664
+ const search = (query, when, settings = DEFAULT_SETTINGS) => {
50524
50665
  const terms = tokenize(query);
50525
50666
  if (!terms.length || !docs.size)
50526
50667
  return [];
50527
50668
  const N = docs.size;
50528
50669
  const avgLen = [...docs.values()].reduce((s, d) => s + d.len, 0) / N;
50529
50670
  return [...docs.entries()]
50671
+ .filter(([, doc]) => doc.when <= when)
50530
50672
  .map(([id, doc]) => {
50531
50673
  let score = 0;
50532
50674
  for (const term of terms) {
@@ -50623,8 +50765,9 @@ class MemoryLocalInstance {
50623
50765
  * @param memoryId - Unique entry identifier
50624
50766
  * @param value - Value to store and index
50625
50767
  * @param description - BM25 index string
50768
+ * @param when - Logical timestamp this entry belongs to (look-ahead guard)
50626
50769
  */
50627
- async writeMemory(memoryId, value, description) {
50770
+ async writeMemory(memoryId, value, description, when) {
50628
50771
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_WRITE, {
50629
50772
  signalId: this.signalId,
50630
50773
  bucketName: this.bucketName,
@@ -50635,21 +50778,24 @@ class MemoryLocalInstance {
50635
50778
  content: value,
50636
50779
  index: description,
50637
50780
  priority: Date.now(),
50781
+ when: when.getTime(),
50638
50782
  });
50639
50783
  }
50640
50784
  /**
50641
50785
  * Read a single entry from the in-memory index.
50786
+ * Behaves as not-found if the stored `when` is greater than the requested `when`.
50642
50787
  * @param memoryId - Unique entry identifier
50788
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50643
50789
  * @returns Parsed entry value
50644
- * @throws Error if entry not found
50790
+ * @throws Error if entry not found (or shadowed by look-ahead)
50645
50791
  */
50646
- async readMemory(memoryId) {
50792
+ async readMemory(memoryId, when) {
50647
50793
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_READ, {
50648
50794
  signalId: this.signalId,
50649
50795
  bucketName: this.bucketName,
50650
50796
  memoryId,
50651
50797
  });
50652
- const value = this._index.read(memoryId);
50798
+ const value = this._index.read(memoryId, when.getTime());
50653
50799
  if (!value) {
50654
50800
  throw new Error(`MemoryLocalInstance value not found memoryId=${memoryId}`);
50655
50801
  }
@@ -50657,33 +50803,38 @@ class MemoryLocalInstance {
50657
50803
  }
50658
50804
  /**
50659
50805
  * Search entries using BM25 full-text scoring.
50806
+ * Filters out entries whose `when` is greater than the requested `when`.
50660
50807
  * @param query - Search query string
50808
+ * @param when - Logical timestamp at which the search is happening (look-ahead guard)
50661
50809
  * @returns Matching entries sorted by relevance score
50662
50810
  */
50663
- async searchMemory(query, settings) {
50811
+ async searchMemory(query, when, settings) {
50664
50812
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_SEARCH, {
50665
50813
  signalId: this.signalId,
50666
50814
  bucketName: this.bucketName,
50667
50815
  query,
50668
50816
  });
50669
- return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
50817
+ return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
50670
50818
  }
50671
50819
  /**
50672
50820
  * List all entries stored in the in-memory index.
50821
+ * Filters out entries whose `when` is greater than the requested `when`.
50822
+ * @param when - Logical timestamp at which the list is happening (look-ahead guard)
50673
50823
  * @returns Array of all stored entries
50674
50824
  */
50675
- async listMemory() {
50825
+ async listMemory(when) {
50676
50826
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_LIST, {
50677
50827
  signalId: this.signalId,
50678
50828
  bucketName: this.bucketName,
50679
50829
  });
50680
- return this._index.list().map(LIST_MEMORY_FN);
50830
+ return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
50681
50831
  }
50682
50832
  /**
50683
50833
  * Remove an entry from the in-memory index.
50684
50834
  * @param memoryId - Unique entry identifier
50835
+ * @param when - Logical timestamp (kept for API consistency; removal is by UUID)
50685
50836
  */
50686
- async removeMemory(memoryId) {
50837
+ async removeMemory(memoryId, _when) {
50687
50838
  backtest.loggerService.debug(MEMORY_LOCAL_INSTANCE_METHOD_NAME_REMOVE, {
50688
50839
  signalId: this.signalId,
50689
50840
  bucketName: this.bucketName,
@@ -50729,12 +50880,13 @@ class MemoryPersistInstance {
50729
50880
  initial,
50730
50881
  });
50731
50882
  await PersistMemoryAdapter.waitForInit(this.signalId, this.bucketName, initial);
50732
- for await (const { memoryId, data: { data, index, priority } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
50883
+ for await (const { memoryId, data: { data, index, priority, when } } of PersistMemoryAdapter.listMemoryData(this.signalId, this.bucketName)) {
50733
50884
  this._index.upsert({
50734
50885
  id: memoryId,
50735
50886
  content: data,
50736
50887
  index,
50737
50888
  priority,
50889
+ when,
50738
50890
  });
50739
50891
  }
50740
50892
  }
@@ -50743,69 +50895,79 @@ class MemoryPersistInstance {
50743
50895
  * @param memoryId - Unique entry identifier
50744
50896
  * @param value - Value to persist and index
50745
50897
  * @param index - BM25 index string; defaults to JSON.stringify(value)
50898
+ * @param when - Logical timestamp this entry belongs to (look-ahead guard)
50746
50899
  */
50747
- async writeMemory(memoryId, value, index = JSON.stringify(value)) {
50900
+ async writeMemory(memoryId, value, index, when) {
50748
50901
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_WRITE, {
50749
50902
  signalId: this.signalId,
50750
50903
  bucketName: this.bucketName,
50751
50904
  memoryId,
50752
50905
  });
50753
50906
  const priority = Date.now();
50754
- await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index }, this.signalId, this.bucketName, memoryId);
50907
+ const whenMs = when.getTime();
50908
+ await PersistMemoryAdapter.writeMemoryData({ data: value, priority, removed: false, index, when: whenMs }, this.signalId, this.bucketName, memoryId);
50755
50909
  this._index.upsert({
50756
50910
  id: memoryId,
50757
50911
  content: value,
50758
50912
  index,
50759
50913
  priority,
50914
+ when: whenMs,
50760
50915
  });
50761
50916
  }
50762
50917
  /**
50763
50918
  * Read a single entry from disk.
50919
+ * Behaves as not-found if the stored `when` is greater than the requested `when`.
50764
50920
  * @param memoryId - Unique entry identifier
50921
+ * @param when - Logical timestamp at which the read is happening (look-ahead guard)
50765
50922
  * @returns Entry value
50766
- * @throws Error if entry not found
50923
+ * @throws Error if entry not found (or shadowed by look-ahead)
50767
50924
  */
50768
- async readMemory(memoryId) {
50925
+ async readMemory(memoryId, when) {
50769
50926
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_READ, {
50770
50927
  signalId: this.signalId,
50771
50928
  bucketName: this.bucketName,
50772
50929
  memoryId,
50773
50930
  });
50774
50931
  const data = await PersistMemoryAdapter.readMemoryData(this.signalId, this.bucketName, memoryId);
50775
- if (!data) {
50932
+ if (!data || data.when > when.getTime()) {
50776
50933
  throw new Error(`MemoryPersistInstance value not found memoryId=${memoryId}`);
50777
50934
  }
50778
50935
  return data.data;
50779
50936
  }
50780
50937
  /**
50781
50938
  * Search entries using BM25 index rebuilt from disk on init.
50939
+ * Filters out entries whose `when` is greater than the requested `when`.
50782
50940
  * @param query - Search query string
50941
+ * @param when - Logical timestamp at which the search is happening (look-ahead guard)
50783
50942
  * @returns Matching entries sorted by relevance score
50784
50943
  */
50785
- async searchMemory(query, settings) {
50944
+ async searchMemory(query, when, settings) {
50786
50945
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_SEARCH, {
50787
50946
  signalId: this.signalId,
50788
50947
  bucketName: this.bucketName,
50789
50948
  query,
50790
50949
  });
50791
- return this._index.search(query, settings).map(SEARCH_MEMORY_FN);
50950
+ return this._index.search(query, when.getTime(), settings).map(SEARCH_MEMORY_FN);
50792
50951
  }
50793
50952
  /**
50794
50953
  * List all entries from the in-memory index (populated from disk on init).
50954
+ * Filters out entries whose `when` is greater than the requested `when`.
50955
+ * @param when - Logical timestamp at which the list is happening (look-ahead guard)
50795
50956
  * @returns Array of all stored entries
50796
50957
  */
50797
- async listMemory() {
50958
+ async listMemory(when) {
50798
50959
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_LIST, {
50799
50960
  signalId: this.signalId,
50800
50961
  bucketName: this.bucketName,
50801
50962
  });
50802
- return this._index.list().map(LIST_MEMORY_FN);
50963
+ return this._index.list(when.getTime()).map(LIST_MEMORY_FN);
50803
50964
  }
50804
50965
  /**
50805
50966
  * Remove an entry from disk and from the BM25 index.
50806
50967
  * @param memoryId - Unique entry identifier
50968
+ * @param when - Logical timestamp (kept for API consistency; removal is by UUID)
50807
50969
  */
50808
- async removeMemory(memoryId) {
50970
+ async removeMemory(memoryId, _when) {
50809
50971
  backtest.loggerService.debug(MEMORY_PERSIST_INSTANCE_METHOD_NAME_REMOVE, {
50810
50972
  signalId: this.signalId,
50811
50973
  bucketName: this.bucketName,
@@ -50842,34 +51004,34 @@ class MemoryDummyInstance {
50842
51004
  * No-op write - discards the value.
50843
51005
  * @returns Promise that resolves immediately
50844
51006
  */
50845
- async writeMemory() {
51007
+ async writeMemory(_memoryId, _value, _description, _when) {
50846
51008
  }
50847
51009
  /**
50848
51010
  * No-op read - always throws.
50849
51011
  * @throws Error always
50850
51012
  */
50851
- async readMemory(_memoryId) {
51013
+ async readMemory(_memoryId, _when) {
50852
51014
  throw new Error("MemoryDummyInstance: readMemory not supported");
50853
51015
  }
50854
51016
  /**
50855
51017
  * No-op search - returns empty array.
50856
51018
  * @returns Empty array
50857
51019
  */
50858
- async searchMemory() {
51020
+ async searchMemory(_query, _when, _settings) {
50859
51021
  return [];
50860
51022
  }
50861
51023
  /**
50862
51024
  * No-op list - returns empty array.
50863
51025
  * @returns Empty array
50864
51026
  */
50865
- async listMemory() {
51027
+ async listMemory(_when) {
50866
51028
  return [];
50867
51029
  }
50868
51030
  /**
50869
51031
  * No-op remove.
50870
51032
  * @returns Promise that resolves immediately
50871
51033
  */
50872
- async removeMemory() {
51034
+ async removeMemory(_memoryId, _when) {
50873
51035
  }
50874
51036
  /** No-op. */
50875
51037
  dispose() {
@@ -50913,6 +51075,7 @@ class MemoryBacktestAdapter {
50913
51075
  * @param dto.signalId - Signal identifier
50914
51076
  * @param dto.bucketName - Bucket name
50915
51077
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51078
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
50916
51079
  */
50917
51080
  this.writeMemory = async (dto) => {
50918
51081
  backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_WRITE, {
@@ -50924,13 +51087,14 @@ class MemoryBacktestAdapter {
50924
51087
  const isInitial = !this.getInstance.has(key);
50925
51088
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50926
51089
  await instance.waitForInit(isInitial);
50927
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
51090
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
50928
51091
  };
50929
51092
  /**
50930
51093
  * Search memory using BM25 full-text scoring.
50931
51094
  * @param dto.query - Search query string
50932
51095
  * @param dto.signalId - Signal identifier
50933
51096
  * @param dto.bucketName - Bucket name
51097
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
50934
51098
  * @returns Matching entries sorted by relevance score
50935
51099
  */
50936
51100
  this.searchMemory = async (dto) => {
@@ -50943,12 +51107,13 @@ class MemoryBacktestAdapter {
50943
51107
  const isInitial = !this.getInstance.has(key);
50944
51108
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50945
51109
  await instance.waitForInit(isInitial);
50946
- return await instance.searchMemory(dto.query, dto.settings);
51110
+ return await instance.searchMemory(dto.query, dto.when, dto.settings);
50947
51111
  };
50948
51112
  /**
50949
51113
  * List all entries in memory.
50950
51114
  * @param dto.signalId - Signal identifier
50951
51115
  * @param dto.bucketName - Bucket name
51116
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
50952
51117
  * @returns Array of all stored entries
50953
51118
  */
50954
51119
  this.listMemory = async (dto) => {
@@ -50960,13 +51125,14 @@ class MemoryBacktestAdapter {
50960
51125
  const isInitial = !this.getInstance.has(key);
50961
51126
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50962
51127
  await instance.waitForInit(isInitial);
50963
- return await instance.listMemory();
51128
+ return await instance.listMemory(dto.when);
50964
51129
  };
50965
51130
  /**
50966
51131
  * Remove an entry from memory.
50967
51132
  * @param dto.memoryId - Unique entry identifier
50968
51133
  * @param dto.signalId - Signal identifier
50969
51134
  * @param dto.bucketName - Bucket name
51135
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
50970
51136
  */
50971
51137
  this.removeMemory = async (dto) => {
50972
51138
  backtest.loggerService.debug(MEMORY_BACKTEST_ADAPTER_METHOD_NAME_REMOVE, {
@@ -50978,13 +51144,14 @@ class MemoryBacktestAdapter {
50978
51144
  const isInitial = !this.getInstance.has(key);
50979
51145
  const instance = this.getInstance(dto.signalId, dto.bucketName);
50980
51146
  await instance.waitForInit(isInitial);
50981
- return await instance.removeMemory(dto.memoryId);
51147
+ return await instance.removeMemory(dto.memoryId, dto.when);
50982
51148
  };
50983
51149
  /**
50984
51150
  * Read a single entry from memory.
50985
51151
  * @param dto.memoryId - Unique entry identifier
50986
51152
  * @param dto.signalId - Signal identifier
50987
51153
  * @param dto.bucketName - Bucket name
51154
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
50988
51155
  * @returns Entry value
50989
51156
  * @throws Error if entry not found
50990
51157
  */
@@ -50998,7 +51165,7 @@ class MemoryBacktestAdapter {
50998
51165
  const isInitial = !this.getInstance.has(key);
50999
51166
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51000
51167
  await instance.waitForInit(isInitial);
51001
- return await instance.readMemory(dto.memoryId);
51168
+ return await instance.readMemory(dto.memoryId, dto.when);
51002
51169
  };
51003
51170
  /**
51004
51171
  * Switches to in-memory BM25 adapter (default).
@@ -51080,6 +51247,7 @@ class MemoryLiveAdapter {
51080
51247
  * @param dto.signalId - Signal identifier
51081
51248
  * @param dto.bucketName - Bucket name
51082
51249
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51250
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
51083
51251
  */
51084
51252
  this.writeMemory = async (dto) => {
51085
51253
  backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_WRITE, {
@@ -51091,13 +51259,14 @@ class MemoryLiveAdapter {
51091
51259
  const isInitial = !this.getInstance.has(key);
51092
51260
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51093
51261
  await instance.waitForInit(isInitial);
51094
- return await instance.writeMemory(dto.memoryId, dto.value, dto.description);
51262
+ return await instance.writeMemory(dto.memoryId, dto.value, dto.description, dto.when);
51095
51263
  };
51096
51264
  /**
51097
51265
  * Search memory using BM25 full-text scoring.
51098
51266
  * @param dto.query - Search query string
51099
51267
  * @param dto.signalId - Signal identifier
51100
51268
  * @param dto.bucketName - Bucket name
51269
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
51101
51270
  * @returns Matching entries sorted by relevance score
51102
51271
  */
51103
51272
  this.searchMemory = async (dto) => {
@@ -51110,12 +51279,13 @@ class MemoryLiveAdapter {
51110
51279
  const isInitial = !this.getInstance.has(key);
51111
51280
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51112
51281
  await instance.waitForInit(isInitial);
51113
- return await instance.searchMemory(dto.query, dto.settings);
51282
+ return await instance.searchMemory(dto.query, dto.when, dto.settings);
51114
51283
  };
51115
51284
  /**
51116
51285
  * List all entries in memory.
51117
51286
  * @param dto.signalId - Signal identifier
51118
51287
  * @param dto.bucketName - Bucket name
51288
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
51119
51289
  * @returns Array of all stored entries
51120
51290
  */
51121
51291
  this.listMemory = async (dto) => {
@@ -51127,13 +51297,14 @@ class MemoryLiveAdapter {
51127
51297
  const isInitial = !this.getInstance.has(key);
51128
51298
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51129
51299
  await instance.waitForInit(isInitial);
51130
- return await instance.listMemory();
51300
+ return await instance.listMemory(dto.when);
51131
51301
  };
51132
51302
  /**
51133
51303
  * Remove an entry from memory.
51134
51304
  * @param dto.memoryId - Unique entry identifier
51135
51305
  * @param dto.signalId - Signal identifier
51136
51306
  * @param dto.bucketName - Bucket name
51307
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
51137
51308
  */
51138
51309
  this.removeMemory = async (dto) => {
51139
51310
  backtest.loggerService.debug(MEMORY_LIVE_ADAPTER_METHOD_NAME_REMOVE, {
@@ -51145,13 +51316,14 @@ class MemoryLiveAdapter {
51145
51316
  const isInitial = !this.getInstance.has(key);
51146
51317
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51147
51318
  await instance.waitForInit(isInitial);
51148
- return await instance.removeMemory(dto.memoryId);
51319
+ return await instance.removeMemory(dto.memoryId, dto.when);
51149
51320
  };
51150
51321
  /**
51151
51322
  * Read a single entry from memory.
51152
51323
  * @param dto.memoryId - Unique entry identifier
51153
51324
  * @param dto.signalId - Signal identifier
51154
51325
  * @param dto.bucketName - Bucket name
51326
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
51155
51327
  * @returns Entry value
51156
51328
  * @throws Error if entry not found
51157
51329
  */
@@ -51165,7 +51337,7 @@ class MemoryLiveAdapter {
51165
51337
  const isInitial = !this.getInstance.has(key);
51166
51338
  const instance = this.getInstance(dto.signalId, dto.bucketName);
51167
51339
  await instance.waitForInit(isInitial);
51168
- return await instance.readMemory(dto.memoryId);
51340
+ return await instance.readMemory(dto.memoryId, dto.when);
51169
51341
  };
51170
51342
  /**
51171
51343
  * Switches to in-memory BM25 adapter.
@@ -51264,6 +51436,7 @@ class MemoryAdapter {
51264
51436
  * @param dto.bucketName - Bucket name
51265
51437
  * @param dto.description - BM25 index string; defaults to JSON.stringify(value)
51266
51438
  * @param dto.backtest - Flag indicating if the context is backtest or live
51439
+ * @param dto.when - Logical timestamp this entry belongs to (look-ahead guard)
51267
51440
  */
51268
51441
  this.writeMemory = async (dto) => {
51269
51442
  if (!this.enable.hasValue()) {
@@ -51287,6 +51460,7 @@ class MemoryAdapter {
51287
51460
  * @param dto.signalId - Signal identifier
51288
51461
  * @param dto.bucketName - Bucket name
51289
51462
  * @param dto.backtest - Flag indicating if the context is backtest or live
51463
+ * @param dto.when - Logical timestamp at which the search is happening (look-ahead guard)
51290
51464
  * @returns Matching entries sorted by relevance score
51291
51465
  */
51292
51466
  this.searchMemory = async (dto) => {
@@ -51310,6 +51484,7 @@ class MemoryAdapter {
51310
51484
  * @param dto.signalId - Signal identifier
51311
51485
  * @param dto.bucketName - Bucket name
51312
51486
  * @param dto.backtest - Flag indicating if the context is backtest or live
51487
+ * @param dto.when - Logical timestamp at which the list is happening (look-ahead guard)
51313
51488
  * @returns Array of all stored entries
51314
51489
  */
51315
51490
  this.listMemory = async (dto) => {
@@ -51333,6 +51508,7 @@ class MemoryAdapter {
51333
51508
  * @param dto.signalId - Signal identifier
51334
51509
  * @param dto.bucketName - Bucket name
51335
51510
  * @param dto.backtest - Flag indicating if the context is backtest or live
51511
+ * @param dto.when - Logical timestamp (kept for API consistency; removal is by UUID)
51336
51512
  */
51337
51513
  this.removeMemory = async (dto) => {
51338
51514
  if (!this.enable.hasValue()) {
@@ -51356,6 +51532,7 @@ class MemoryAdapter {
51356
51532
  * @param dto.signalId - Signal identifier
51357
51533
  * @param dto.bucketName - Bucket name
51358
51534
  * @param dto.backtest - Flag indicating if the context is backtest or live
51535
+ * @param dto.when - Logical timestamp at which the read is happening (look-ahead guard)
51359
51536
  * @returns Entry value
51360
51537
  * @throws Error if entry not found
51361
51538
  */
@@ -51428,7 +51605,7 @@ async function writeMemory(dto) {
51428
51605
  if (!MethodContextService.hasContext()) {
51429
51606
  throw new Error("writeMemory requires a method context");
51430
51607
  }
51431
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51608
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51432
51609
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51433
51610
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51434
51611
  let signal;
@@ -51440,6 +51617,7 @@ async function writeMemory(dto) {
51440
51617
  bucketName,
51441
51618
  description,
51442
51619
  backtest: isBacktest,
51620
+ when,
51443
51621
  });
51444
51622
  return;
51445
51623
  }
@@ -51451,6 +51629,7 @@ async function writeMemory(dto) {
51451
51629
  bucketName,
51452
51630
  description,
51453
51631
  backtest: isBacktest,
51632
+ when,
51454
51633
  });
51455
51634
  return;
51456
51635
  }
@@ -51486,7 +51665,7 @@ async function readMemory(dto) {
51486
51665
  if (!MethodContextService.hasContext()) {
51487
51666
  throw new Error("readMemory requires a method context");
51488
51667
  }
51489
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51668
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51490
51669
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51491
51670
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51492
51671
  let signal;
@@ -51496,6 +51675,7 @@ async function readMemory(dto) {
51496
51675
  signalId: signal.id,
51497
51676
  bucketName,
51498
51677
  backtest: isBacktest,
51678
+ when,
51499
51679
  });
51500
51680
  }
51501
51681
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51504,6 +51684,7 @@ async function readMemory(dto) {
51504
51684
  signalId: signal.id,
51505
51685
  bucketName,
51506
51686
  backtest: isBacktest,
51687
+ when,
51507
51688
  });
51508
51689
  }
51509
51690
  throw new Error(`readMemory requires a pending or scheduled signal for symbol=${symbol} memoryId=${memoryId}`);
@@ -51538,7 +51719,7 @@ async function searchMemory(dto) {
51538
51719
  if (!MethodContextService.hasContext()) {
51539
51720
  throw new Error("searchMemory requires a method context");
51540
51721
  }
51541
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51722
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51542
51723
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51543
51724
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51544
51725
  let signal;
@@ -51548,6 +51729,7 @@ async function searchMemory(dto) {
51548
51729
  signalId: signal.id,
51549
51730
  bucketName,
51550
51731
  backtest: isBacktest,
51732
+ when,
51551
51733
  });
51552
51734
  }
51553
51735
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51556,6 +51738,7 @@ async function searchMemory(dto) {
51556
51738
  signalId: signal.id,
51557
51739
  bucketName,
51558
51740
  backtest: isBacktest,
51741
+ when,
51559
51742
  });
51560
51743
  }
51561
51744
  throw new Error(`searchMemory requires a pending or scheduled signal for symbol=${symbol} query=${query}`);
@@ -51588,7 +51771,7 @@ async function listMemory(dto) {
51588
51771
  if (!MethodContextService.hasContext()) {
51589
51772
  throw new Error("listMemory requires a method context");
51590
51773
  }
51591
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51774
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51592
51775
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51593
51776
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51594
51777
  let signal;
@@ -51597,6 +51780,7 @@ async function listMemory(dto) {
51597
51780
  signalId: signal.id,
51598
51781
  bucketName,
51599
51782
  backtest: isBacktest,
51783
+ when,
51600
51784
  });
51601
51785
  }
51602
51786
  if (signal = await backtest.strategyCoreService.getScheduledSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName })) {
@@ -51604,6 +51788,7 @@ async function listMemory(dto) {
51604
51788
  signalId: signal.id,
51605
51789
  bucketName,
51606
51790
  backtest: isBacktest,
51791
+ when,
51607
51792
  });
51608
51793
  }
51609
51794
  throw new Error(`listMemory requires a pending or scheduled signal for symbol=${symbol} bucketName=${bucketName}`);
@@ -51638,7 +51823,7 @@ async function removeMemory(dto) {
51638
51823
  if (!MethodContextService.hasContext()) {
51639
51824
  throw new Error("removeMemory requires a method context");
51640
51825
  }
51641
- const { backtest: isBacktest, symbol } = backtest.executionContextService.context;
51826
+ const { backtest: isBacktest, symbol, when } = backtest.executionContextService.context;
51642
51827
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
51643
51828
  const currentPrice = await backtest.exchangeConnectionService.getAveragePrice(symbol);
51644
51829
  let signal;
@@ -51648,6 +51833,7 @@ async function removeMemory(dto) {
51648
51833
  signalId: signal.id,
51649
51834
  bucketName,
51650
51835
  backtest: isBacktest,
51836
+ when,
51651
51837
  });
51652
51838
  return;
51653
51839
  }
@@ -51657,6 +51843,7 @@ async function removeMemory(dto) {
51657
51843
  signalId: signal.id,
51658
51844
  bucketName,
51659
51845
  backtest: isBacktest,
51846
+ when,
51660
51847
  });
51661
51848
  return;
51662
51849
  }
@@ -51922,6 +52109,9 @@ class DumpMemoryInstance {
51922
52109
  value: { messages },
51923
52110
  description,
51924
52111
  backtest: this.backtest,
52112
+ // when=0: dumps are UI artifacts (agent transcripts, markdown reports);
52113
+ // they must stay visible regardless of the reader's logical time.
52114
+ when: new Date(0)
51925
52115
  });
51926
52116
  }
51927
52117
  /**
@@ -51944,6 +52134,7 @@ class DumpMemoryInstance {
51944
52134
  value: record,
51945
52135
  description,
51946
52136
  backtest: this.backtest,
52137
+ when: new Date(0)
51947
52138
  });
51948
52139
  }
51949
52140
  /**
@@ -51967,6 +52158,7 @@ class DumpMemoryInstance {
51967
52158
  value: { rows },
51968
52159
  description,
51969
52160
  backtest: this.backtest,
52161
+ when: new Date(0)
51970
52162
  });
51971
52163
  }
51972
52164
  /**
@@ -51989,6 +52181,7 @@ class DumpMemoryInstance {
51989
52181
  value: { content },
51990
52182
  description,
51991
52183
  backtest: this.backtest,
52184
+ when: new Date(0)
51992
52185
  });
51993
52186
  }
51994
52187
  /**
@@ -52011,6 +52204,7 @@ class DumpMemoryInstance {
52011
52204
  value: { content },
52012
52205
  description,
52013
52206
  backtest: this.backtest,
52207
+ when: new Date(0)
52014
52208
  });
52015
52209
  }
52016
52210
  /**
@@ -52034,6 +52228,7 @@ class DumpMemoryInstance {
52034
52228
  value: json,
52035
52229
  description,
52036
52230
  backtest: this.backtest,
52231
+ when: new Date(0)
52037
52232
  });
52038
52233
  }
52039
52234
  /** Releases resources held by this instance. */
@@ -60995,8 +61190,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
60995
61190
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
60996
61191
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
60997
61192
  const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
60998
- const MS_PER_MINUTE$1 = 60000;
60999
- const INTERVAL_MINUTES$1 = {
61193
+ const MS_PER_MINUTE$2 = 60000;
61194
+ const INTERVAL_MINUTES$2 = {
61000
61195
  "1m": 1,
61001
61196
  "3m": 3,
61002
61197
  "5m": 5,
@@ -61030,11 +61225,11 @@ const INTERVAL_MINUTES$1 = {
61030
61225
  * ```
61031
61226
  */
61032
61227
  const align$1 = (timestamp, interval) => {
61033
- const intervalMinutes = INTERVAL_MINUTES$1[interval];
61228
+ const intervalMinutes = INTERVAL_MINUTES$2[interval];
61034
61229
  if (!intervalMinutes) {
61035
61230
  throw new Error(`align: unknown interval=${interval}`);
61036
61231
  }
61037
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
61232
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
61038
61233
  return Math.floor(timestamp / intervalMs) * intervalMs;
61039
61234
  };
61040
61235
  /**
@@ -61123,7 +61318,7 @@ class CacheFnInstance {
61123
61318
  */
61124
61319
  this.run = (...args) => {
61125
61320
  backtest.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
61126
- const step = INTERVAL_MINUTES$1[this.interval];
61321
+ const step = INTERVAL_MINUTES$2[this.interval];
61127
61322
  {
61128
61323
  if (!MethodContextService.hasContext()) {
61129
61324
  throw new Error("CacheFnInstance run requires method context");
@@ -61318,7 +61513,7 @@ class CacheFileInstance {
61318
61513
  */
61319
61514
  this.run = async (...args) => {
61320
61515
  backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
61321
- const step = INTERVAL_MINUTES$1[this.interval];
61516
+ const step = INTERVAL_MINUTES$2[this.interval];
61322
61517
  {
61323
61518
  if (!MethodContextService.hasContext()) {
61324
61519
  throw new Error("CacheFileInstance run requires method context");
@@ -61340,7 +61535,7 @@ class CacheFileInstance {
61340
61535
  return cached.data;
61341
61536
  }
61342
61537
  const result = await this.fn.call(null, ...args);
61343
- await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
61538
+ await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey, when);
61344
61539
  return result;
61345
61540
  };
61346
61541
  /**
@@ -61629,8 +61824,8 @@ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
61629
61824
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
61630
61825
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
61631
61826
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
61632
- const MS_PER_MINUTE = 60000;
61633
- const INTERVAL_MINUTES = {
61827
+ const MS_PER_MINUTE$1 = 60000;
61828
+ const INTERVAL_MINUTES$1 = {
61634
61829
  "1m": 1,
61635
61830
  "3m": 3,
61636
61831
  "5m": 5,
@@ -61660,11 +61855,11 @@ const INTERVAL_MINUTES = {
61660
61855
  * ```
61661
61856
  */
61662
61857
  const align = (timestamp, interval) => {
61663
- const intervalMinutes = INTERVAL_MINUTES[interval];
61858
+ const intervalMinutes = INTERVAL_MINUTES$1[interval];
61664
61859
  if (!intervalMinutes) {
61665
61860
  throw new Error(`align: unknown interval=${interval}`);
61666
61861
  }
61667
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
61862
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
61668
61863
  return Math.floor(timestamp / intervalMs) * intervalMs;
61669
61864
  };
61670
61865
  /**
@@ -61739,7 +61934,7 @@ class IntervalFnInstance {
61739
61934
  */
61740
61935
  this.run = (...args) => {
61741
61936
  backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
61742
- const step = INTERVAL_MINUTES[this.interval];
61937
+ const step = INTERVAL_MINUTES$1[this.interval];
61743
61938
  {
61744
61939
  if (!MethodContextService.hasContext()) {
61745
61940
  throw new Error("IntervalFnInstance run requires method context");
@@ -61906,7 +62101,7 @@ class IntervalFileInstance {
61906
62101
  */
61907
62102
  this.run = async (...args) => {
61908
62103
  backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
61909
- const step = INTERVAL_MINUTES[this.interval];
62104
+ const step = INTERVAL_MINUTES$1[this.interval];
61910
62105
  {
61911
62106
  if (!MethodContextService.hasContext()) {
61912
62107
  throw new Error("IntervalFileInstance run requires method context");
@@ -61929,7 +62124,7 @@ class IntervalFileInstance {
61929
62124
  }
61930
62125
  const result = await this.fn.call(null, ...args);
61931
62126
  if (result !== null) {
61932
- await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
62127
+ await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, when, removed: false }, bucket, entityKey, when);
61933
62128
  }
61934
62129
  return result;
61935
62130
  };
@@ -63053,6 +63248,31 @@ class ActionBase {
63053
63248
  // @ts-ignore
63054
63249
  ActionBase = functoolsKit.makeExtendable(ActionBase);
63055
63250
 
63251
+ const MS_PER_MINUTE = 60000;
63252
+ const INTERVAL_MINUTES = {
63253
+ "1m": 1,
63254
+ "3m": 3,
63255
+ "5m": 5,
63256
+ "15m": 15,
63257
+ "30m": 30,
63258
+ "1h": 60,
63259
+ "2h": 120,
63260
+ "4h": 240,
63261
+ "6h": 360,
63262
+ "8h": 480,
63263
+ "1d": 1440,
63264
+ };
63265
+ /**
63266
+ * Returns the step in milliseconds for a given candle interval.
63267
+ * For example, for "15m" interval, it returns 900000 (15 * 60 * 1000).
63268
+ *
63269
+ * @param interval - Candle interval (e.g., "1m", "15m", "1h")
63270
+ * @returns Step in milliseconds corresponding to the interval
63271
+ */
63272
+ const intervalStepMs = (interval) => {
63273
+ return INTERVAL_MINUTES[interval] * MS_PER_MINUTE;
63274
+ };
63275
+
63056
63276
  /**
63057
63277
  * Rounds a price to the appropriate precision based on the tick size.
63058
63278
  *
@@ -63511,6 +63731,7 @@ exports.getWalkerSchema = getWalkerSchema;
63511
63731
  exports.hasNoPendingSignal = hasNoPendingSignal;
63512
63732
  exports.hasNoScheduledSignal = hasNoScheduledSignal;
63513
63733
  exports.hasTradeContext = hasTradeContext;
63734
+ exports.intervalStepMs = intervalStepMs;
63514
63735
  exports.investedCostToPercent = investedCostToPercent;
63515
63736
  exports.lib = backtest;
63516
63737
  exports.listExchangeSchema = listExchangeSchema;