backtest-kit 3.0.5 → 3.0.6

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <img src="./assets/triangle.svg" height="45px" align="right">
1
+ <img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/consciousness.svg" height="45px" align="right">
2
2
 
3
3
  # 🧿 Backtest Kit
4
4
 
package/build/index.cjs CHANGED
@@ -727,7 +727,7 @@ const INTERVAL_MINUTES$5 = {
727
727
  "6h": 360,
728
728
  "8h": 480,
729
729
  };
730
- const MS_PER_MINUTE$2 = 60000;
730
+ const MS_PER_MINUTE$3 = 60000;
731
731
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
732
732
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
733
733
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -1625,7 +1625,7 @@ class PersistCandleUtils {
1625
1625
  const isInitial = !this.getCandlesStorage.has(key);
1626
1626
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1627
1627
  await stateStorage.waitForInit(isInitial);
1628
- const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$2;
1628
+ const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$3;
1629
1629
  // Calculate expected timestamps and fetch each candle directly
1630
1630
  const cachedCandles = [];
1631
1631
  for (let i = 0; i < limit; i++) {
@@ -1681,7 +1681,7 @@ class PersistCandleUtils {
1681
1681
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1682
1682
  await stateStorage.waitForInit(isInitial);
1683
1683
  // Calculate step in milliseconds to determine candle close time
1684
- const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$2;
1684
+ const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$3;
1685
1685
  const now = Date.now();
1686
1686
  // Write each candle as a separate file, skipping incomplete candles
1687
1687
  for (const candle of candles) {
@@ -1938,7 +1938,7 @@ class PersistNotificationUtils {
1938
1938
  */
1939
1939
  const PersistNotificationAdapter = new PersistNotificationUtils();
1940
1940
 
1941
- const MS_PER_MINUTE$1 = 60000;
1941
+ const MS_PER_MINUTE$2 = 60000;
1942
1942
  const INTERVAL_MINUTES$4 = {
1943
1943
  "1m": 1,
1944
1944
  "3m": 3,
@@ -1969,7 +1969,7 @@ const INTERVAL_MINUTES$4 = {
1969
1969
  * @returns Aligned timestamp rounded down to interval boundary
1970
1970
  */
1971
1971
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
1972
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
1972
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
1973
1973
  return Math.floor(timestamp / intervalMs) * intervalMs;
1974
1974
  };
1975
1975
  /**
@@ -2116,7 +2116,7 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
2116
2116
  const GET_CANDLES_FN = async (dto, since, self) => {
2117
2117
  const step = INTERVAL_MINUTES$4[dto.interval];
2118
2118
  const sinceTimestamp = since.getTime();
2119
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$1;
2119
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$2;
2120
2120
  // Try to read from cache first
2121
2121
  const cachedCandles = await READ_CANDLES_CACHE_FN$1(dto, sinceTimestamp, untilTimestamp, self);
2122
2122
  if (cachedCandles !== null) {
@@ -2228,7 +2228,7 @@ class ClientExchange {
2228
2228
  if (!step) {
2229
2229
  throw new Error(`ClientExchange unknown interval=${interval}`);
2230
2230
  }
2231
- const stepMs = step * MS_PER_MINUTE$1;
2231
+ const stepMs = step * MS_PER_MINUTE$2;
2232
2232
  // Align when down to interval boundary
2233
2233
  const whenTimestamp = this.params.execution.context.when.getTime();
2234
2234
  const alignedWhen = ALIGN_TO_INTERVAL_FN$1(whenTimestamp, step);
@@ -2309,7 +2309,7 @@ class ClientExchange {
2309
2309
  if (!step) {
2310
2310
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
2311
2311
  }
2312
- const stepMs = step * MS_PER_MINUTE$1;
2312
+ const stepMs = step * MS_PER_MINUTE$2;
2313
2313
  const now = Date.now();
2314
2314
  // Align when down to interval boundary
2315
2315
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -2477,7 +2477,7 @@ class ClientExchange {
2477
2477
  if (!step) {
2478
2478
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
2479
2479
  }
2480
- const stepMs = step * MS_PER_MINUTE$1;
2480
+ const stepMs = step * MS_PER_MINUTE$2;
2481
2481
  const whenTimestamp = this.params.execution.context.when.getTime();
2482
2482
  const alignedWhen = ALIGN_TO_INTERVAL_FN$1(whenTimestamp, step);
2483
2483
  let sinceTimestamp;
@@ -2604,7 +2604,7 @@ class ClientExchange {
2604
2604
  });
2605
2605
  const to = new Date(this.params.execution.context.when.getTime());
2606
2606
  const from = new Date(to.getTime() -
2607
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$1);
2607
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
2608
2608
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
2609
2609
  }
2610
2610
  }
@@ -35527,7 +35527,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
35527
35527
  const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
35528
35528
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
35529
35529
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
35530
- const MS_PER_MINUTE = 60000;
35530
+ const MS_PER_MINUTE$1 = 60000;
35531
35531
  /**
35532
35532
  * Gets current timestamp from execution context if available.
35533
35533
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -35612,7 +35612,7 @@ const INTERVAL_MINUTES$1 = {
35612
35612
  * @returns Aligned timestamp rounded down to interval boundary
35613
35613
  */
35614
35614
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
35615
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
35615
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
35616
35616
  return Math.floor(timestamp / intervalMs) * intervalMs;
35617
35617
  };
35618
35618
  /**
@@ -35754,7 +35754,7 @@ class ExchangeInstance {
35754
35754
  if (!step) {
35755
35755
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
35756
35756
  }
35757
- const stepMs = step * MS_PER_MINUTE;
35757
+ const stepMs = step * MS_PER_MINUTE$1;
35758
35758
  // Align when down to interval boundary
35759
35759
  const when = await GET_TIMESTAMP_FN();
35760
35760
  const whenTimestamp = when.getTime();
@@ -35931,7 +35931,7 @@ class ExchangeInstance {
35931
35931
  depth,
35932
35932
  });
35933
35933
  const to = await GET_TIMESTAMP_FN();
35934
- const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE);
35934
+ const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$1);
35935
35935
  const isBacktest = await GET_BACKTEST_FN();
35936
35936
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
35937
35937
  };
@@ -35978,7 +35978,7 @@ class ExchangeInstance {
35978
35978
  if (!step) {
35979
35979
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
35980
35980
  }
35981
- const stepMs = step * MS_PER_MINUTE;
35981
+ const stepMs = step * MS_PER_MINUTE$1;
35982
35982
  const when = await GET_TIMESTAMP_FN();
35983
35983
  const nowTimestamp = when.getTime();
35984
35984
  const alignedNow = ALIGN_TO_INTERVAL_FN(nowTimestamp, step);
@@ -36251,6 +36251,7 @@ const CACHE_METHOD_NAME_FLUSH = "CacheUtils.flush";
36251
36251
  const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
36252
36252
  const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
36253
36253
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
36254
+ const MS_PER_MINUTE = 60000;
36254
36255
  const INTERVAL_MINUTES = {
36255
36256
  "1m": 1,
36256
36257
  "3m": 3,
@@ -36263,6 +36264,34 @@ const INTERVAL_MINUTES = {
36263
36264
  "6h": 360,
36264
36265
  "8h": 480,
36265
36266
  };
36267
+ /**
36268
+ * Aligns timestamp down to the nearest interval boundary.
36269
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
36270
+ *
36271
+ * @param timestamp - Timestamp in milliseconds
36272
+ * @param interval - Candle interval
36273
+ * @returns Aligned timestamp rounded down to interval boundary
36274
+ * @throws Error if interval is unknown
36275
+ *
36276
+ * @example
36277
+ * ```typescript
36278
+ * // Align to 15-minute boundary
36279
+ * const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
36280
+ * // Returns timestamp for 2025-10-01T00:30:00Z
36281
+ *
36282
+ * // Align to 1-hour boundary
36283
+ * const aligned = align(new Date("2025-10-01T01:47:00Z").getTime(), "1h");
36284
+ * // Returns timestamp for 2025-10-01T01:00:00Z
36285
+ * ```
36286
+ */
36287
+ const align = (timestamp, interval) => {
36288
+ const intervalMinutes = INTERVAL_MINUTES[interval];
36289
+ if (!intervalMinutes) {
36290
+ throw new Error(`align: unknown interval=${interval}`);
36291
+ }
36292
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
36293
+ return Math.floor(timestamp / intervalMs) * intervalMs;
36294
+ };
36266
36295
  /**
36267
36296
  * Create a cache key string from strategy name, exchange name, and backtest mode.
36268
36297
  *
@@ -36357,9 +36386,9 @@ class CacheInstance {
36357
36386
  const currentWhen = bt.executionContextService.context.when;
36358
36387
  const cached = this._cacheMap.get(key);
36359
36388
  if (cached) {
36360
- const stepMs = step * 60 * 1000;
36361
- const elapsed = currentWhen.getTime() - cached.when.getTime();
36362
- if (elapsed >= 0 && elapsed < stepMs) {
36389
+ const currentAligned = align(currentWhen.getTime(), this.interval);
36390
+ const cachedAligned = align(cached.when.getTime(), this.interval);
36391
+ if (currentAligned === cachedAligned) {
36363
36392
  return cached;
36364
36393
  }
36365
36394
  }
package/build/index.mjs CHANGED
@@ -707,7 +707,7 @@ const INTERVAL_MINUTES$5 = {
707
707
  "6h": 360,
708
708
  "8h": 480,
709
709
  };
710
- const MS_PER_MINUTE$2 = 60000;
710
+ const MS_PER_MINUTE$3 = 60000;
711
711
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
712
712
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
713
713
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA = "PersistSignalUtils.writeSignalData";
@@ -1605,7 +1605,7 @@ class PersistCandleUtils {
1605
1605
  const isInitial = !this.getCandlesStorage.has(key);
1606
1606
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1607
1607
  await stateStorage.waitForInit(isInitial);
1608
- const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$2;
1608
+ const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$3;
1609
1609
  // Calculate expected timestamps and fetch each candle directly
1610
1610
  const cachedCandles = [];
1611
1611
  for (let i = 0; i < limit; i++) {
@@ -1661,7 +1661,7 @@ class PersistCandleUtils {
1661
1661
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1662
1662
  await stateStorage.waitForInit(isInitial);
1663
1663
  // Calculate step in milliseconds to determine candle close time
1664
- const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$2;
1664
+ const stepMs = INTERVAL_MINUTES$5[interval] * MS_PER_MINUTE$3;
1665
1665
  const now = Date.now();
1666
1666
  // Write each candle as a separate file, skipping incomplete candles
1667
1667
  for (const candle of candles) {
@@ -1918,7 +1918,7 @@ class PersistNotificationUtils {
1918
1918
  */
1919
1919
  const PersistNotificationAdapter = new PersistNotificationUtils();
1920
1920
 
1921
- const MS_PER_MINUTE$1 = 60000;
1921
+ const MS_PER_MINUTE$2 = 60000;
1922
1922
  const INTERVAL_MINUTES$4 = {
1923
1923
  "1m": 1,
1924
1924
  "3m": 3,
@@ -1949,7 +1949,7 @@ const INTERVAL_MINUTES$4 = {
1949
1949
  * @returns Aligned timestamp rounded down to interval boundary
1950
1950
  */
1951
1951
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
1952
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
1952
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
1953
1953
  return Math.floor(timestamp / intervalMs) * intervalMs;
1954
1954
  };
1955
1955
  /**
@@ -2096,7 +2096,7 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
2096
2096
  const GET_CANDLES_FN = async (dto, since, self) => {
2097
2097
  const step = INTERVAL_MINUTES$4[dto.interval];
2098
2098
  const sinceTimestamp = since.getTime();
2099
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$1;
2099
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$2;
2100
2100
  // Try to read from cache first
2101
2101
  const cachedCandles = await READ_CANDLES_CACHE_FN$1(dto, sinceTimestamp, untilTimestamp, self);
2102
2102
  if (cachedCandles !== null) {
@@ -2208,7 +2208,7 @@ class ClientExchange {
2208
2208
  if (!step) {
2209
2209
  throw new Error(`ClientExchange unknown interval=${interval}`);
2210
2210
  }
2211
- const stepMs = step * MS_PER_MINUTE$1;
2211
+ const stepMs = step * MS_PER_MINUTE$2;
2212
2212
  // Align when down to interval boundary
2213
2213
  const whenTimestamp = this.params.execution.context.when.getTime();
2214
2214
  const alignedWhen = ALIGN_TO_INTERVAL_FN$1(whenTimestamp, step);
@@ -2289,7 +2289,7 @@ class ClientExchange {
2289
2289
  if (!step) {
2290
2290
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
2291
2291
  }
2292
- const stepMs = step * MS_PER_MINUTE$1;
2292
+ const stepMs = step * MS_PER_MINUTE$2;
2293
2293
  const now = Date.now();
2294
2294
  // Align when down to interval boundary
2295
2295
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -2457,7 +2457,7 @@ class ClientExchange {
2457
2457
  if (!step) {
2458
2458
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
2459
2459
  }
2460
- const stepMs = step * MS_PER_MINUTE$1;
2460
+ const stepMs = step * MS_PER_MINUTE$2;
2461
2461
  const whenTimestamp = this.params.execution.context.when.getTime();
2462
2462
  const alignedWhen = ALIGN_TO_INTERVAL_FN$1(whenTimestamp, step);
2463
2463
  let sinceTimestamp;
@@ -2584,7 +2584,7 @@ class ClientExchange {
2584
2584
  });
2585
2585
  const to = new Date(this.params.execution.context.when.getTime());
2586
2586
  const from = new Date(to.getTime() -
2587
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$1);
2587
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
2588
2588
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
2589
2589
  }
2590
2590
  }
@@ -35507,7 +35507,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
35507
35507
  const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
35508
35508
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
35509
35509
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
35510
- const MS_PER_MINUTE = 60000;
35510
+ const MS_PER_MINUTE$1 = 60000;
35511
35511
  /**
35512
35512
  * Gets current timestamp from execution context if available.
35513
35513
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -35592,7 +35592,7 @@ const INTERVAL_MINUTES$1 = {
35592
35592
  * @returns Aligned timestamp rounded down to interval boundary
35593
35593
  */
35594
35594
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
35595
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
35595
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
35596
35596
  return Math.floor(timestamp / intervalMs) * intervalMs;
35597
35597
  };
35598
35598
  /**
@@ -35734,7 +35734,7 @@ class ExchangeInstance {
35734
35734
  if (!step) {
35735
35735
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
35736
35736
  }
35737
- const stepMs = step * MS_PER_MINUTE;
35737
+ const stepMs = step * MS_PER_MINUTE$1;
35738
35738
  // Align when down to interval boundary
35739
35739
  const when = await GET_TIMESTAMP_FN();
35740
35740
  const whenTimestamp = when.getTime();
@@ -35911,7 +35911,7 @@ class ExchangeInstance {
35911
35911
  depth,
35912
35912
  });
35913
35913
  const to = await GET_TIMESTAMP_FN();
35914
- const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE);
35914
+ const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$1);
35915
35915
  const isBacktest = await GET_BACKTEST_FN();
35916
35916
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
35917
35917
  };
@@ -35958,7 +35958,7 @@ class ExchangeInstance {
35958
35958
  if (!step) {
35959
35959
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
35960
35960
  }
35961
- const stepMs = step * MS_PER_MINUTE;
35961
+ const stepMs = step * MS_PER_MINUTE$1;
35962
35962
  const when = await GET_TIMESTAMP_FN();
35963
35963
  const nowTimestamp = when.getTime();
35964
35964
  const alignedNow = ALIGN_TO_INTERVAL_FN(nowTimestamp, step);
@@ -36231,6 +36231,7 @@ const CACHE_METHOD_NAME_FLUSH = "CacheUtils.flush";
36231
36231
  const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
36232
36232
  const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
36233
36233
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
36234
+ const MS_PER_MINUTE = 60000;
36234
36235
  const INTERVAL_MINUTES = {
36235
36236
  "1m": 1,
36236
36237
  "3m": 3,
@@ -36243,6 +36244,34 @@ const INTERVAL_MINUTES = {
36243
36244
  "6h": 360,
36244
36245
  "8h": 480,
36245
36246
  };
36247
+ /**
36248
+ * Aligns timestamp down to the nearest interval boundary.
36249
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
36250
+ *
36251
+ * @param timestamp - Timestamp in milliseconds
36252
+ * @param interval - Candle interval
36253
+ * @returns Aligned timestamp rounded down to interval boundary
36254
+ * @throws Error if interval is unknown
36255
+ *
36256
+ * @example
36257
+ * ```typescript
36258
+ * // Align to 15-minute boundary
36259
+ * const aligned = align(new Date("2025-10-01T00:35:00Z").getTime(), "15m");
36260
+ * // Returns timestamp for 2025-10-01T00:30:00Z
36261
+ *
36262
+ * // Align to 1-hour boundary
36263
+ * const aligned = align(new Date("2025-10-01T01:47:00Z").getTime(), "1h");
36264
+ * // Returns timestamp for 2025-10-01T01:00:00Z
36265
+ * ```
36266
+ */
36267
+ const align = (timestamp, interval) => {
36268
+ const intervalMinutes = INTERVAL_MINUTES[interval];
36269
+ if (!intervalMinutes) {
36270
+ throw new Error(`align: unknown interval=${interval}`);
36271
+ }
36272
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
36273
+ return Math.floor(timestamp / intervalMs) * intervalMs;
36274
+ };
36246
36275
  /**
36247
36276
  * Create a cache key string from strategy name, exchange name, and backtest mode.
36248
36277
  *
@@ -36337,9 +36366,9 @@ class CacheInstance {
36337
36366
  const currentWhen = bt.executionContextService.context.when;
36338
36367
  const cached = this._cacheMap.get(key);
36339
36368
  if (cached) {
36340
- const stepMs = step * 60 * 1000;
36341
- const elapsed = currentWhen.getTime() - cached.when.getTime();
36342
- if (elapsed >= 0 && elapsed < stepMs) {
36369
+ const currentAligned = align(currentWhen.getTime(), this.interval);
36370
+ const cachedAligned = align(cached.when.getTime(), this.interval);
36371
+ if (currentAligned === cachedAligned) {
36343
36372
  return cached;
36344
36373
  }
36345
36374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",