backtest-kit 5.5.1 → 5.5.3

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
@@ -10538,6 +10538,45 @@ const get = (object, path) => {
10538
10538
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
10539
10539
  };
10540
10540
 
10541
+ const MS_PER_MINUTE$4 = 60000;
10542
+ const INTERVAL_MINUTES$4 = {
10543
+ "1m": 1,
10544
+ "3m": 3,
10545
+ "5m": 5,
10546
+ "15m": 15,
10547
+ "30m": 30,
10548
+ "1h": 60,
10549
+ "2h": 120,
10550
+ "4h": 240,
10551
+ "6h": 360,
10552
+ "8h": 480,
10553
+ };
10554
+ /**
10555
+ * Aligns timestamp down to the nearest interval boundary.
10556
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
10557
+ *
10558
+ * Candle timestamp convention:
10559
+ * - Candle timestamp = openTime (when candle opens)
10560
+ * - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
10561
+ *
10562
+ * Adapter contract:
10563
+ * - Adapter must return candles with timestamp = openTime
10564
+ * - First returned candle.timestamp must equal aligned since
10565
+ * - Adapter must return exactly `limit` candles
10566
+ *
10567
+ * @param date - Date to align
10568
+ * @param interval - Candle interval (e.g., "1m", "15m", "1h")
10569
+ * @returns New Date aligned down to interval boundary
10570
+ */
10571
+ const alignToInterval = (date, interval) => {
10572
+ const minutes = INTERVAL_MINUTES$4[interval];
10573
+ if (minutes === undefined) {
10574
+ throw new Error(`alignToInterval: unknown interval=${interval}`);
10575
+ }
10576
+ const intervalMs = minutes * MS_PER_MINUTE$4;
10577
+ return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
10578
+ };
10579
+
10541
10580
  /**
10542
10581
  * Retrieves the current timestamp based on the execution context.
10543
10582
  * If an execution context is active (e.g., during a backtest), it returns the timestamp from the context to ensure consistency with the simulated time.
@@ -10549,7 +10588,7 @@ const getContextTimestamp = () => {
10549
10588
  if (ExecutionContextService.hasContext()) {
10550
10589
  return bt.executionContextService.context.when.getTime();
10551
10590
  }
10552
- return Date.now();
10591
+ return alignToInterval(new Date(), "1m").getTime();
10553
10592
  };
10554
10593
 
10555
10594
  /** Symbol indicating that positions need to be fetched from persistence */
@@ -15534,8 +15573,8 @@ class BacktestLogicPrivateService {
15534
15573
  }
15535
15574
 
15536
15575
  const EMITTER_CHECK_INTERVAL = 5000;
15537
- const MS_PER_MINUTE$4 = 60000;
15538
- const INTERVAL_MINUTES$4 = {
15576
+ const MS_PER_MINUTE$3 = 60000;
15577
+ const INTERVAL_MINUTES$3 = {
15539
15578
  "1m": 1,
15540
15579
  "3m": 3,
15541
15580
  "5m": 5,
@@ -15549,7 +15588,7 @@ const INTERVAL_MINUTES$4 = {
15549
15588
  };
15550
15589
  const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
15551
15590
  const tickSubject = new functoolsKit.Subject();
15552
- const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
15591
+ const intervalMs = INTERVAL_MINUTES$3[interval] * MS_PER_MINUTE$3;
15553
15592
  {
15554
15593
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
15555
15594
  functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -15627,7 +15666,7 @@ class LiveLogicPrivateService {
15627
15666
  let previousEventTimestamp = null;
15628
15667
  while (true) {
15629
15668
  const tickStartTime = performance.now();
15630
- const when = new Date();
15669
+ const when = alignToInterval(new Date(), "1m");
15631
15670
  let result;
15632
15671
  try {
15633
15672
  result = await this.strategyCoreService.tick(symbol, when, false, {
@@ -29498,7 +29537,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
29498
29537
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
29499
29538
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
29500
29539
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
29501
- const MS_PER_MINUTE$3 = 60000;
29540
+ const MS_PER_MINUTE$2 = 60000;
29502
29541
  /**
29503
29542
  * Gets current timestamp from execution context if available.
29504
29543
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -29507,7 +29546,7 @@ const GET_TIMESTAMP_FN = async () => {
29507
29546
  if (ExecutionContextService.hasContext()) {
29508
29547
  return new Date(bt.executionContextService.context.when);
29509
29548
  }
29510
- return new Date();
29549
+ return alignToInterval(new Date(), "1m");
29511
29550
  };
29512
29551
  /**
29513
29552
  * Gets backtest mode flag from execution context if available.
@@ -29565,7 +29604,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
29565
29604
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
29566
29605
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
29567
29606
  };
29568
- const INTERVAL_MINUTES$3 = {
29607
+ const INTERVAL_MINUTES$2 = {
29569
29608
  "1m": 1,
29570
29609
  "3m": 3,
29571
29610
  "5m": 5,
@@ -29595,7 +29634,7 @@ const INTERVAL_MINUTES$3 = {
29595
29634
  * @returns Aligned timestamp rounded down to interval boundary
29596
29635
  */
29597
29636
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
29598
- const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
29637
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
29599
29638
  return Math.floor(timestamp / intervalMs) * intervalMs;
29600
29639
  };
29601
29640
  /**
@@ -29735,11 +29774,11 @@ class ExchangeInstance {
29735
29774
  limit,
29736
29775
  });
29737
29776
  const getCandles = this._methods.getCandles;
29738
- const step = INTERVAL_MINUTES$3[interval];
29777
+ const step = INTERVAL_MINUTES$2[interval];
29739
29778
  if (!step) {
29740
29779
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
29741
29780
  }
29742
- const stepMs = step * MS_PER_MINUTE$3;
29781
+ const stepMs = step * MS_PER_MINUTE$2;
29743
29782
  // Align when down to interval boundary
29744
29783
  const when = await GET_TIMESTAMP_FN();
29745
29784
  const whenTimestamp = when.getTime();
@@ -29924,7 +29963,7 @@ class ExchangeInstance {
29924
29963
  const when = await GET_TIMESTAMP_FN();
29925
29964
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
29926
29965
  const to = new Date(alignedTo);
29927
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
29966
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
29928
29967
  const isBacktest = await GET_BACKTEST_FN();
29929
29968
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
29930
29969
  };
@@ -29956,7 +29995,7 @@ class ExchangeInstance {
29956
29995
  const when = await GET_TIMESTAMP_FN();
29957
29996
  // Align to 1-minute boundary to prevent look-ahead bias
29958
29997
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
29959
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
29998
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$2 - MS_PER_MINUTE$2;
29960
29999
  const isBacktest = await GET_BACKTEST_FN();
29961
30000
  // No limit: fetch a single window and return as-is
29962
30001
  if (limit === undefined) {
@@ -30019,11 +30058,11 @@ class ExchangeInstance {
30019
30058
  sDate,
30020
30059
  eDate,
30021
30060
  });
30022
- const step = INTERVAL_MINUTES$3[interval];
30061
+ const step = INTERVAL_MINUTES$2[interval];
30023
30062
  if (!step) {
30024
30063
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
30025
30064
  }
30026
- const stepMs = step * MS_PER_MINUTE$3;
30065
+ const stepMs = step * MS_PER_MINUTE$2;
30027
30066
  const when = await GET_TIMESTAMP_FN();
30028
30067
  const nowTimestamp = when.getTime();
30029
30068
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -30313,8 +30352,8 @@ const Exchange = new ExchangeUtils();
30313
30352
 
30314
30353
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
30315
30354
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
30316
- const MS_PER_MINUTE$2 = 60000;
30317
- const INTERVAL_MINUTES$2 = {
30355
+ const MS_PER_MINUTE$1 = 60000;
30356
+ const INTERVAL_MINUTES$1 = {
30318
30357
  "1m": 1,
30319
30358
  "3m": 3,
30320
30359
  "5m": 5,
@@ -30327,7 +30366,7 @@ const INTERVAL_MINUTES$2 = {
30327
30366
  "8h": 480,
30328
30367
  };
30329
30368
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
30330
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
30369
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
30331
30370
  return Math.floor(timestamp / intervalMs) * intervalMs;
30332
30371
  };
30333
30372
  const BAR_LENGTH = 30;
@@ -30352,11 +30391,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
30352
30391
  async function checkCandles(params) {
30353
30392
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
30354
30393
  bt.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
30355
- const step = INTERVAL_MINUTES$2[interval];
30394
+ const step = INTERVAL_MINUTES$1[interval];
30356
30395
  if (!step) {
30357
30396
  throw new Error(`checkCandles: unsupported interval=${interval}`);
30358
30397
  }
30359
- const stepMs = step * MS_PER_MINUTE$2;
30398
+ const stepMs = step * MS_PER_MINUTE$1;
30360
30399
  const dir = path.join(baseDir, exchangeName, symbol, interval);
30361
30400
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
30362
30401
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -30426,11 +30465,11 @@ async function warmCandles(params) {
30426
30465
  from,
30427
30466
  to,
30428
30467
  });
30429
- const step = INTERVAL_MINUTES$2[interval];
30468
+ const step = INTERVAL_MINUTES$1[interval];
30430
30469
  if (!step) {
30431
30470
  throw new Error(`warmCandles: unsupported interval=${interval}`);
30432
30471
  }
30433
- const stepMs = step * MS_PER_MINUTE$2;
30472
+ const stepMs = step * MS_PER_MINUTE$1;
30434
30473
  const instance = new ExchangeInstance(exchangeName);
30435
30474
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
30436
30475
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -39976,7 +40015,7 @@ const GET_DATE_FN = () => {
39976
40015
  if (ExecutionContextService.hasContext()) {
39977
40016
  return new Date(bt.executionContextService.context.when);
39978
40017
  }
39979
- return new Date();
40018
+ return alignToInterval(new Date(), "1m");
39980
40019
  };
39981
40020
  /**
39982
40021
  * Method context retrieval function.
@@ -45318,8 +45357,8 @@ const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
45318
45357
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
45319
45358
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
45320
45359
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
45321
- const MS_PER_MINUTE$1 = 60000;
45322
- const INTERVAL_MINUTES$1 = {
45360
+ const MS_PER_MINUTE = 60000;
45361
+ const INTERVAL_MINUTES = {
45323
45362
  "1m": 1,
45324
45363
  "3m": 3,
45325
45364
  "5m": 5,
@@ -45352,11 +45391,11 @@ const INTERVAL_MINUTES$1 = {
45352
45391
  * ```
45353
45392
  */
45354
45393
  const align = (timestamp, interval) => {
45355
- const intervalMinutes = INTERVAL_MINUTES$1[interval];
45394
+ const intervalMinutes = INTERVAL_MINUTES[interval];
45356
45395
  if (!intervalMinutes) {
45357
45396
  throw new Error(`align: unknown interval=${interval}`);
45358
45397
  }
45359
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
45398
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
45360
45399
  return Math.floor(timestamp / intervalMs) * intervalMs;
45361
45400
  };
45362
45401
  /**
@@ -45445,7 +45484,7 @@ class CacheInstance {
45445
45484
  */
45446
45485
  this.run = (...args) => {
45447
45486
  bt.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
45448
- const step = INTERVAL_MINUTES$1[this.interval];
45487
+ const step = INTERVAL_MINUTES[this.interval];
45449
45488
  {
45450
45489
  if (!MethodContextService.hasContext()) {
45451
45490
  throw new Error("CacheInstance run requires method context");
@@ -45598,7 +45637,7 @@ class CacheFileInstance {
45598
45637
  */
45599
45638
  this.run = async (...args) => {
45600
45639
  bt.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
45601
- const step = INTERVAL_MINUTES$1[this.interval];
45640
+ const step = INTERVAL_MINUTES[this.interval];
45602
45641
  {
45603
45642
  if (!MethodContextService.hasContext()) {
45604
45643
  throw new Error("CacheFileInstance run requires method context");
@@ -46251,45 +46290,6 @@ class StrategyUtils {
46251
46290
  */
46252
46291
  const Strategy = new StrategyUtils();
46253
46292
 
46254
- const MS_PER_MINUTE = 60000;
46255
- const INTERVAL_MINUTES = {
46256
- "1m": 1,
46257
- "3m": 3,
46258
- "5m": 5,
46259
- "15m": 15,
46260
- "30m": 30,
46261
- "1h": 60,
46262
- "2h": 120,
46263
- "4h": 240,
46264
- "6h": 360,
46265
- "8h": 480,
46266
- };
46267
- /**
46268
- * Aligns timestamp down to the nearest interval boundary.
46269
- * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
46270
- *
46271
- * Candle timestamp convention:
46272
- * - Candle timestamp = openTime (when candle opens)
46273
- * - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
46274
- *
46275
- * Adapter contract:
46276
- * - Adapter must return candles with timestamp = openTime
46277
- * - First returned candle.timestamp must equal aligned since
46278
- * - Adapter must return exactly `limit` candles
46279
- *
46280
- * @param date - Date to align
46281
- * @param interval - Candle interval (e.g., "1m", "15m", "1h")
46282
- * @returns New Date aligned down to interval boundary
46283
- */
46284
- const alignToInterval = (date, interval) => {
46285
- const minutes = INTERVAL_MINUTES[interval];
46286
- if (minutes === undefined) {
46287
- throw new Error(`alignToInterval: unknown interval=${interval}`);
46288
- }
46289
- const intervalMs = minutes * MS_PER_MINUTE;
46290
- return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
46291
- };
46292
-
46293
46293
  /**
46294
46294
  * Rounds a price to the appropriate precision based on the tick size.
46295
46295
  *
package/build/index.mjs CHANGED
@@ -10518,6 +10518,45 @@ const get = (object, path) => {
10518
10518
  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
10519
10519
  };
10520
10520
 
10521
+ const MS_PER_MINUTE$4 = 60000;
10522
+ const INTERVAL_MINUTES$4 = {
10523
+ "1m": 1,
10524
+ "3m": 3,
10525
+ "5m": 5,
10526
+ "15m": 15,
10527
+ "30m": 30,
10528
+ "1h": 60,
10529
+ "2h": 120,
10530
+ "4h": 240,
10531
+ "6h": 360,
10532
+ "8h": 480,
10533
+ };
10534
+ /**
10535
+ * Aligns timestamp down to the nearest interval boundary.
10536
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
10537
+ *
10538
+ * Candle timestamp convention:
10539
+ * - Candle timestamp = openTime (when candle opens)
10540
+ * - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
10541
+ *
10542
+ * Adapter contract:
10543
+ * - Adapter must return candles with timestamp = openTime
10544
+ * - First returned candle.timestamp must equal aligned since
10545
+ * - Adapter must return exactly `limit` candles
10546
+ *
10547
+ * @param date - Date to align
10548
+ * @param interval - Candle interval (e.g., "1m", "15m", "1h")
10549
+ * @returns New Date aligned down to interval boundary
10550
+ */
10551
+ const alignToInterval = (date, interval) => {
10552
+ const minutes = INTERVAL_MINUTES$4[interval];
10553
+ if (minutes === undefined) {
10554
+ throw new Error(`alignToInterval: unknown interval=${interval}`);
10555
+ }
10556
+ const intervalMs = minutes * MS_PER_MINUTE$4;
10557
+ return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
10558
+ };
10559
+
10521
10560
  /**
10522
10561
  * Retrieves the current timestamp based on the execution context.
10523
10562
  * If an execution context is active (e.g., during a backtest), it returns the timestamp from the context to ensure consistency with the simulated time.
@@ -10529,7 +10568,7 @@ const getContextTimestamp = () => {
10529
10568
  if (ExecutionContextService.hasContext()) {
10530
10569
  return bt.executionContextService.context.when.getTime();
10531
10570
  }
10532
- return Date.now();
10571
+ return alignToInterval(new Date(), "1m").getTime();
10533
10572
  };
10534
10573
 
10535
10574
  /** Symbol indicating that positions need to be fetched from persistence */
@@ -15514,8 +15553,8 @@ class BacktestLogicPrivateService {
15514
15553
  }
15515
15554
 
15516
15555
  const EMITTER_CHECK_INTERVAL = 5000;
15517
- const MS_PER_MINUTE$4 = 60000;
15518
- const INTERVAL_MINUTES$4 = {
15556
+ const MS_PER_MINUTE$3 = 60000;
15557
+ const INTERVAL_MINUTES$3 = {
15519
15558
  "1m": 1,
15520
15559
  "3m": 3,
15521
15560
  "5m": 5,
@@ -15529,7 +15568,7 @@ const INTERVAL_MINUTES$4 = {
15529
15568
  };
15530
15569
  const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
15531
15570
  const tickSubject = new Subject();
15532
- const intervalMs = INTERVAL_MINUTES$4[interval] * MS_PER_MINUTE$4;
15571
+ const intervalMs = INTERVAL_MINUTES$3[interval] * MS_PER_MINUTE$3;
15533
15572
  {
15534
15573
  let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
15535
15574
  Source.fromInterval(EMITTER_CHECK_INTERVAL)
@@ -15607,7 +15646,7 @@ class LiveLogicPrivateService {
15607
15646
  let previousEventTimestamp = null;
15608
15647
  while (true) {
15609
15648
  const tickStartTime = performance.now();
15610
- const when = new Date();
15649
+ const when = alignToInterval(new Date(), "1m");
15611
15650
  let result;
15612
15651
  try {
15613
15652
  result = await this.strategyCoreService.tick(symbol, when, false, {
@@ -29478,7 +29517,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
29478
29517
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
29479
29518
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
29480
29519
  const EXCHANGE_METHOD_NAME_GET_AGGREGATED_TRADES = "ExchangeUtils.getAggregatedTrades";
29481
- const MS_PER_MINUTE$3 = 60000;
29520
+ const MS_PER_MINUTE$2 = 60000;
29482
29521
  /**
29483
29522
  * Gets current timestamp from execution context if available.
29484
29523
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -29487,7 +29526,7 @@ const GET_TIMESTAMP_FN = async () => {
29487
29526
  if (ExecutionContextService.hasContext()) {
29488
29527
  return new Date(bt.executionContextService.context.when);
29489
29528
  }
29490
- return new Date();
29529
+ return alignToInterval(new Date(), "1m");
29491
29530
  };
29492
29531
  /**
29493
29532
  * Gets backtest mode flag from execution context if available.
@@ -29545,7 +29584,7 @@ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest)
29545
29584
  const DEFAULT_GET_AGGREGATED_TRADES_FN = async (_symbol, _from, _to, _backtest) => {
29546
29585
  throw new Error(`getAggregatedTrades is not implemented for this exchange`);
29547
29586
  };
29548
- const INTERVAL_MINUTES$3 = {
29587
+ const INTERVAL_MINUTES$2 = {
29549
29588
  "1m": 1,
29550
29589
  "3m": 3,
29551
29590
  "5m": 5,
@@ -29575,7 +29614,7 @@ const INTERVAL_MINUTES$3 = {
29575
29614
  * @returns Aligned timestamp rounded down to interval boundary
29576
29615
  */
29577
29616
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
29578
- const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
29617
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
29579
29618
  return Math.floor(timestamp / intervalMs) * intervalMs;
29580
29619
  };
29581
29620
  /**
@@ -29715,11 +29754,11 @@ class ExchangeInstance {
29715
29754
  limit,
29716
29755
  });
29717
29756
  const getCandles = this._methods.getCandles;
29718
- const step = INTERVAL_MINUTES$3[interval];
29757
+ const step = INTERVAL_MINUTES$2[interval];
29719
29758
  if (!step) {
29720
29759
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
29721
29760
  }
29722
- const stepMs = step * MS_PER_MINUTE$3;
29761
+ const stepMs = step * MS_PER_MINUTE$2;
29723
29762
  // Align when down to interval boundary
29724
29763
  const when = await GET_TIMESTAMP_FN();
29725
29764
  const whenTimestamp = when.getTime();
@@ -29904,7 +29943,7 @@ class ExchangeInstance {
29904
29943
  const when = await GET_TIMESTAMP_FN();
29905
29944
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
29906
29945
  const to = new Date(alignedTo);
29907
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
29946
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
29908
29947
  const isBacktest = await GET_BACKTEST_FN();
29909
29948
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
29910
29949
  };
@@ -29936,7 +29975,7 @@ class ExchangeInstance {
29936
29975
  const when = await GET_TIMESTAMP_FN();
29937
29976
  // Align to 1-minute boundary to prevent look-ahead bias
29938
29977
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), 1);
29939
- const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$3 - MS_PER_MINUTE$3;
29978
+ const windowMs = GLOBAL_CONFIG.CC_AGGREGATED_TRADES_MAX_MINUTES * MS_PER_MINUTE$2 - MS_PER_MINUTE$2;
29940
29979
  const isBacktest = await GET_BACKTEST_FN();
29941
29980
  // No limit: fetch a single window and return as-is
29942
29981
  if (limit === undefined) {
@@ -29999,11 +30038,11 @@ class ExchangeInstance {
29999
30038
  sDate,
30000
30039
  eDate,
30001
30040
  });
30002
- const step = INTERVAL_MINUTES$3[interval];
30041
+ const step = INTERVAL_MINUTES$2[interval];
30003
30042
  if (!step) {
30004
30043
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
30005
30044
  }
30006
- const stepMs = step * MS_PER_MINUTE$3;
30045
+ const stepMs = step * MS_PER_MINUTE$2;
30007
30046
  const when = await GET_TIMESTAMP_FN();
30008
30047
  const nowTimestamp = when.getTime();
30009
30048
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -30293,8 +30332,8 @@ const Exchange = new ExchangeUtils();
30293
30332
 
30294
30333
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
30295
30334
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
30296
- const MS_PER_MINUTE$2 = 60000;
30297
- const INTERVAL_MINUTES$2 = {
30335
+ const MS_PER_MINUTE$1 = 60000;
30336
+ const INTERVAL_MINUTES$1 = {
30298
30337
  "1m": 1,
30299
30338
  "3m": 3,
30300
30339
  "5m": 5,
@@ -30307,7 +30346,7 @@ const INTERVAL_MINUTES$2 = {
30307
30346
  "8h": 480,
30308
30347
  };
30309
30348
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
30310
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
30349
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
30311
30350
  return Math.floor(timestamp / intervalMs) * intervalMs;
30312
30351
  };
30313
30352
  const BAR_LENGTH = 30;
@@ -30332,11 +30371,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
30332
30371
  async function checkCandles(params) {
30333
30372
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
30334
30373
  bt.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
30335
- const step = INTERVAL_MINUTES$2[interval];
30374
+ const step = INTERVAL_MINUTES$1[interval];
30336
30375
  if (!step) {
30337
30376
  throw new Error(`checkCandles: unsupported interval=${interval}`);
30338
30377
  }
30339
- const stepMs = step * MS_PER_MINUTE$2;
30378
+ const stepMs = step * MS_PER_MINUTE$1;
30340
30379
  const dir = join(baseDir, exchangeName, symbol, interval);
30341
30380
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
30342
30381
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -30406,11 +30445,11 @@ async function warmCandles(params) {
30406
30445
  from,
30407
30446
  to,
30408
30447
  });
30409
- const step = INTERVAL_MINUTES$2[interval];
30448
+ const step = INTERVAL_MINUTES$1[interval];
30410
30449
  if (!step) {
30411
30450
  throw new Error(`warmCandles: unsupported interval=${interval}`);
30412
30451
  }
30413
- const stepMs = step * MS_PER_MINUTE$2;
30452
+ const stepMs = step * MS_PER_MINUTE$1;
30414
30453
  const instance = new ExchangeInstance(exchangeName);
30415
30454
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
30416
30455
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -39956,7 +39995,7 @@ const GET_DATE_FN = () => {
39956
39995
  if (ExecutionContextService.hasContext()) {
39957
39996
  return new Date(bt.executionContextService.context.when);
39958
39997
  }
39959
- return new Date();
39998
+ return alignToInterval(new Date(), "1m");
39960
39999
  };
39961
40000
  /**
39962
40001
  * Method context retrieval function.
@@ -45298,8 +45337,8 @@ const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
45298
45337
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
45299
45338
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
45300
45339
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
45301
- const MS_PER_MINUTE$1 = 60000;
45302
- const INTERVAL_MINUTES$1 = {
45340
+ const MS_PER_MINUTE = 60000;
45341
+ const INTERVAL_MINUTES = {
45303
45342
  "1m": 1,
45304
45343
  "3m": 3,
45305
45344
  "5m": 5,
@@ -45332,11 +45371,11 @@ const INTERVAL_MINUTES$1 = {
45332
45371
  * ```
45333
45372
  */
45334
45373
  const align = (timestamp, interval) => {
45335
- const intervalMinutes = INTERVAL_MINUTES$1[interval];
45374
+ const intervalMinutes = INTERVAL_MINUTES[interval];
45336
45375
  if (!intervalMinutes) {
45337
45376
  throw new Error(`align: unknown interval=${interval}`);
45338
45377
  }
45339
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
45378
+ const intervalMs = intervalMinutes * MS_PER_MINUTE;
45340
45379
  return Math.floor(timestamp / intervalMs) * intervalMs;
45341
45380
  };
45342
45381
  /**
@@ -45425,7 +45464,7 @@ class CacheInstance {
45425
45464
  */
45426
45465
  this.run = (...args) => {
45427
45466
  bt.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
45428
- const step = INTERVAL_MINUTES$1[this.interval];
45467
+ const step = INTERVAL_MINUTES[this.interval];
45429
45468
  {
45430
45469
  if (!MethodContextService.hasContext()) {
45431
45470
  throw new Error("CacheInstance run requires method context");
@@ -45578,7 +45617,7 @@ class CacheFileInstance {
45578
45617
  */
45579
45618
  this.run = async (...args) => {
45580
45619
  bt.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
45581
- const step = INTERVAL_MINUTES$1[this.interval];
45620
+ const step = INTERVAL_MINUTES[this.interval];
45582
45621
  {
45583
45622
  if (!MethodContextService.hasContext()) {
45584
45623
  throw new Error("CacheFileInstance run requires method context");
@@ -46231,45 +46270,6 @@ class StrategyUtils {
46231
46270
  */
46232
46271
  const Strategy = new StrategyUtils();
46233
46272
 
46234
- const MS_PER_MINUTE = 60000;
46235
- const INTERVAL_MINUTES = {
46236
- "1m": 1,
46237
- "3m": 3,
46238
- "5m": 5,
46239
- "15m": 15,
46240
- "30m": 30,
46241
- "1h": 60,
46242
- "2h": 120,
46243
- "4h": 240,
46244
- "6h": 360,
46245
- "8h": 480,
46246
- };
46247
- /**
46248
- * Aligns timestamp down to the nearest interval boundary.
46249
- * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
46250
- *
46251
- * Candle timestamp convention:
46252
- * - Candle timestamp = openTime (when candle opens)
46253
- * - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
46254
- *
46255
- * Adapter contract:
46256
- * - Adapter must return candles with timestamp = openTime
46257
- * - First returned candle.timestamp must equal aligned since
46258
- * - Adapter must return exactly `limit` candles
46259
- *
46260
- * @param date - Date to align
46261
- * @param interval - Candle interval (e.g., "1m", "15m", "1h")
46262
- * @returns New Date aligned down to interval boundary
46263
- */
46264
- const alignToInterval = (date, interval) => {
46265
- const minutes = INTERVAL_MINUTES[interval];
46266
- if (minutes === undefined) {
46267
- throw new Error(`alignToInterval: unknown interval=${interval}`);
46268
- }
46269
- const intervalMs = minutes * MS_PER_MINUTE;
46270
- return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
46271
- };
46272
-
46273
46273
  /**
46274
46274
  * Rounds a price to the appropriate precision based on the tick size.
46275
46275
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "5.5.1",
3
+ "version": "5.5.3",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",