backtest-kit 3.0.14 → 3.0.16

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
@@ -281,6 +281,53 @@ According to this `timestamp` of a candle in backtest-kit is exactly the `openTi
281
281
  - Adapter must return exactly `limit` candles
282
282
  - Sequential timestamps: `since + i * stepMs`
283
283
 
284
+
285
+ ### 🔍 How getOrderBook Works
286
+
287
+ Order book fetching uses the same temporal alignment as candles, but with a configurable time offset window instead of candle intervals.
288
+
289
+ <details>
290
+ <summary>
291
+ The Math
292
+ </summary>
293
+
294
+ **Time range calculation:**
295
+ - `when` = current execution context time (from AsyncLocalStorage)
296
+ - `offsetMinutes` = `CC_ORDER_BOOK_TIME_OFFSET_MINUTES` (configurable)
297
+ - `alignedTo` = `Math.floor(when / (offsetMinutes * 60000)) * (offsetMinutes * 60000)`
298
+ - `to` = `alignedTo` (aligned down to offset boundary)
299
+ - `from` = `alignedTo - offsetMinutes * 60000`
300
+
301
+ **Adapter contract:**
302
+ - `getOrderBook(symbol, depth, from, to, backtest)` is called on the exchange schema
303
+ - `depth` defaults to `CC_ORDER_BOOK_MAX_DEPTH_LEVELS`
304
+ - The `from`/`to` range represents a time window of exactly `offsetMinutes` duration
305
+ - Schema implementation may use the time range (backtest) or ignore it (live trading)
306
+
307
+ **Example with CC_ORDER_BOOK_TIME_OFFSET_MINUTES = 10:**
308
+ ```
309
+ when = 1704067920000 // 2024-01-01 00:12:00 UTC
310
+ offsetMinutes = 10
311
+ offsetMs = 10 * 60000 // 600000ms
312
+
313
+ alignedTo = Math.floor(1704067920000 / 600000) * 600000
314
+ = 1704067800000 // 2024-01-01 00:10:00 UTC
315
+
316
+ to = 1704067800000 // 00:10:00 UTC
317
+ from = 1704067200000 // 00:00:00 UTC
318
+ ```
319
+ </details>
320
+
321
+ #### Order Book Timestamp Convention:
322
+
323
+ The `from`/`to` range is a **lookback window**. The adapter selects the closest snapshot to `to` in backtest mode, or returns real-time data in live mode. Unlike candles, most exchanges (e.g. Binance `GET /api/v3/depth`) only expose the **current** order book with no historical query support — for backtest you must provide your own snapshot storage.
324
+
325
+ **Key principles:**
326
+ - Time range is aligned down to `CC_ORDER_BOOK_TIME_OFFSET_MINUTES` boundary
327
+ - `to` = aligned timestamp, `from` = `to - offsetMinutes * 60000`
328
+ - `depth` defaults to `CC_ORDER_BOOK_MAX_DEPTH_LEVELS`
329
+ - Adapter receives `(symbol, depth, from, to, backtest)` — may ignore `from`/`to` in live mode
330
+
284
331
  ### 🔬 Technical Details: Timestamp Alignment
285
332
 
286
333
  **Why align timestamps to interval boundaries?**
package/build/index.cjs CHANGED
@@ -715,7 +715,7 @@ async function writeFileAtomic(file, data, options = {}) {
715
715
  var _a$2;
716
716
  const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
717
717
  // Calculate step in milliseconds for candle close time validation
718
- const INTERVAL_MINUTES$6 = {
718
+ const INTERVAL_MINUTES$7 = {
719
719
  "1m": 1,
720
720
  "3m": 3,
721
721
  "5m": 5,
@@ -727,7 +727,7 @@ const INTERVAL_MINUTES$6 = {
727
727
  "6h": 360,
728
728
  "8h": 480,
729
729
  };
730
- const MS_PER_MINUTE$4 = 60000;
730
+ const MS_PER_MINUTE$5 = 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";
@@ -1627,7 +1627,7 @@ class PersistCandleUtils {
1627
1627
  const isInitial = !this.getCandlesStorage.has(key);
1628
1628
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1629
1629
  await stateStorage.waitForInit(isInitial);
1630
- const stepMs = INTERVAL_MINUTES$6[interval] * MS_PER_MINUTE$4;
1630
+ const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
1631
1631
  // Calculate expected timestamps and fetch each candle directly
1632
1632
  const cachedCandles = [];
1633
1633
  for (let i = 0; i < limit; i++) {
@@ -1683,7 +1683,7 @@ class PersistCandleUtils {
1683
1683
  const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
1684
1684
  await stateStorage.waitForInit(isInitial);
1685
1685
  // Calculate step in milliseconds to determine candle close time
1686
- const stepMs = INTERVAL_MINUTES$6[interval] * MS_PER_MINUTE$4;
1686
+ const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
1687
1687
  const now = Date.now();
1688
1688
  // Write each candle as a separate file, skipping incomplete candles
1689
1689
  for (const candle of candles) {
@@ -1940,8 +1940,8 @@ class PersistNotificationUtils {
1940
1940
  */
1941
1941
  const PersistNotificationAdapter = new PersistNotificationUtils();
1942
1942
 
1943
- const MS_PER_MINUTE$3 = 60000;
1944
- const INTERVAL_MINUTES$5 = {
1943
+ const MS_PER_MINUTE$4 = 60000;
1944
+ const INTERVAL_MINUTES$6 = {
1945
1945
  "1m": 1,
1946
1946
  "3m": 3,
1947
1947
  "5m": 5,
@@ -1971,7 +1971,7 @@ const INTERVAL_MINUTES$5 = {
1971
1971
  * @returns Aligned timestamp rounded down to interval boundary
1972
1972
  */
1973
1973
  const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
1974
- const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
1974
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
1975
1975
  return Math.floor(timestamp / intervalMs) * intervalMs;
1976
1976
  };
1977
1977
  /**
@@ -2116,9 +2116,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
2116
2116
  * @returns Promise resolving to array of candle data
2117
2117
  */
2118
2118
  const GET_CANDLES_FN = async (dto, since, self) => {
2119
- const step = INTERVAL_MINUTES$5[dto.interval];
2119
+ const step = INTERVAL_MINUTES$6[dto.interval];
2120
2120
  const sinceTimestamp = since.getTime();
2121
- const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$3;
2121
+ const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$4;
2122
2122
  // Try to read from cache first
2123
2123
  const cachedCandles = await READ_CANDLES_CACHE_FN$1(dto, sinceTimestamp, untilTimestamp, self);
2124
2124
  if (cachedCandles !== null) {
@@ -2226,11 +2226,11 @@ class ClientExchange {
2226
2226
  interval,
2227
2227
  limit,
2228
2228
  });
2229
- const step = INTERVAL_MINUTES$5[interval];
2229
+ const step = INTERVAL_MINUTES$6[interval];
2230
2230
  if (!step) {
2231
2231
  throw new Error(`ClientExchange unknown interval=${interval}`);
2232
2232
  }
2233
- const stepMs = step * MS_PER_MINUTE$3;
2233
+ const stepMs = step * MS_PER_MINUTE$4;
2234
2234
  // Align when down to interval boundary
2235
2235
  const whenTimestamp = this.params.execution.context.when.getTime();
2236
2236
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
@@ -2307,11 +2307,11 @@ class ClientExchange {
2307
2307
  if (!this.params.execution.context.backtest) {
2308
2308
  throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
2309
2309
  }
2310
- const step = INTERVAL_MINUTES$5[interval];
2310
+ const step = INTERVAL_MINUTES$6[interval];
2311
2311
  if (!step) {
2312
2312
  throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
2313
2313
  }
2314
- const stepMs = step * MS_PER_MINUTE$3;
2314
+ const stepMs = step * MS_PER_MINUTE$4;
2315
2315
  const now = Date.now();
2316
2316
  // Align when down to interval boundary
2317
2317
  const whenTimestamp = this.params.execution.context.when.getTime();
@@ -2475,11 +2475,11 @@ class ClientExchange {
2475
2475
  sDate,
2476
2476
  eDate,
2477
2477
  });
2478
- const step = INTERVAL_MINUTES$5[interval];
2478
+ const step = INTERVAL_MINUTES$6[interval];
2479
2479
  if (!step) {
2480
2480
  throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
2481
2481
  }
2482
- const stepMs = step * MS_PER_MINUTE$3;
2482
+ const stepMs = step * MS_PER_MINUTE$4;
2483
2483
  const whenTimestamp = this.params.execution.context.when.getTime();
2484
2484
  const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
2485
2485
  let sinceTimestamp;
@@ -2608,7 +2608,7 @@ class ClientExchange {
2608
2608
  const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
2609
2609
  const to = new Date(alignedTo);
2610
2610
  const from = new Date(alignedTo -
2611
- GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
2611
+ GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
2612
2612
  return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
2613
2613
  }
2614
2614
  }
@@ -3073,7 +3073,7 @@ const beginTime = (run) => (...args) => {
3073
3073
  return fn();
3074
3074
  };
3075
3075
 
3076
- const INTERVAL_MINUTES$4 = {
3076
+ const INTERVAL_MINUTES$5 = {
3077
3077
  "1m": 1,
3078
3078
  "3m": 3,
3079
3079
  "5m": 5,
@@ -3586,7 +3586,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
3586
3586
  }
3587
3587
  const currentTime = self.params.execution.context.when.getTime();
3588
3588
  {
3589
- const intervalMinutes = INTERVAL_MINUTES$4[self.params.interval];
3589
+ const intervalMinutes = INTERVAL_MINUTES$5[self.params.interval];
3590
3590
  const intervalMs = intervalMinutes * 60 * 1000;
3591
3591
  // Проверяем что прошел нужный интервал с последнего getSignal
3592
3592
  if (self._lastSignalTimestamp !== null &&
@@ -8197,7 +8197,7 @@ class StrategyConnectionService {
8197
8197
  * Maps FrameInterval to minutes for timestamp calculation.
8198
8198
  * Used to generate timeframe arrays with proper spacing.
8199
8199
  */
8200
- const INTERVAL_MINUTES$3 = {
8200
+ const INTERVAL_MINUTES$4 = {
8201
8201
  "1m": 1,
8202
8202
  "3m": 3,
8203
8203
  "5m": 5,
@@ -8252,7 +8252,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
8252
8252
  symbol,
8253
8253
  });
8254
8254
  const { interval, startDate, endDate } = self.params;
8255
- const intervalMinutes = INTERVAL_MINUTES$3[interval];
8255
+ const intervalMinutes = INTERVAL_MINUTES$4[interval];
8256
8256
  if (!intervalMinutes) {
8257
8257
  throw new Error(`ClientFrame unknown interval: ${interval}`);
8258
8258
  }
@@ -26048,7 +26048,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
26048
26048
  const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
26049
26049
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
26050
26050
  const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
26051
- const MS_PER_MINUTE$2 = 60000;
26051
+ const MS_PER_MINUTE$3 = 60000;
26052
26052
  /**
26053
26053
  * Gets current timestamp from execution context if available.
26054
26054
  * Returns current Date() if no execution context exists (non-trading GUI).
@@ -26103,7 +26103,7 @@ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
26103
26103
  const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
26104
26104
  throw new Error(`getOrderBook is not implemented for this exchange`);
26105
26105
  };
26106
- const INTERVAL_MINUTES$2 = {
26106
+ const INTERVAL_MINUTES$3 = {
26107
26107
  "1m": 1,
26108
26108
  "3m": 3,
26109
26109
  "5m": 5,
@@ -26133,7 +26133,7 @@ const INTERVAL_MINUTES$2 = {
26133
26133
  * @returns Aligned timestamp rounded down to interval boundary
26134
26134
  */
26135
26135
  const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
26136
- const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
26136
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
26137
26137
  return Math.floor(timestamp / intervalMs) * intervalMs;
26138
26138
  };
26139
26139
  /**
@@ -26271,11 +26271,11 @@ class ExchangeInstance {
26271
26271
  limit,
26272
26272
  });
26273
26273
  const getCandles = this._methods.getCandles;
26274
- const step = INTERVAL_MINUTES$2[interval];
26274
+ const step = INTERVAL_MINUTES$3[interval];
26275
26275
  if (!step) {
26276
26276
  throw new Error(`ExchangeInstance unknown interval=${interval}`);
26277
26277
  }
26278
- const stepMs = step * MS_PER_MINUTE$2;
26278
+ const stepMs = step * MS_PER_MINUTE$3;
26279
26279
  // Align when down to interval boundary
26280
26280
  const when = await GET_TIMESTAMP_FN();
26281
26281
  const whenTimestamp = when.getTime();
@@ -26454,7 +26454,7 @@ class ExchangeInstance {
26454
26454
  const when = await GET_TIMESTAMP_FN();
26455
26455
  const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
26456
26456
  const to = new Date(alignedTo);
26457
- const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$2);
26457
+ const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
26458
26458
  const isBacktest = await GET_BACKTEST_FN();
26459
26459
  return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
26460
26460
  };
@@ -26497,11 +26497,11 @@ class ExchangeInstance {
26497
26497
  sDate,
26498
26498
  eDate,
26499
26499
  });
26500
- const step = INTERVAL_MINUTES$2[interval];
26500
+ const step = INTERVAL_MINUTES$3[interval];
26501
26501
  if (!step) {
26502
26502
  throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
26503
26503
  }
26504
- const stepMs = step * MS_PER_MINUTE$2;
26504
+ const stepMs = step * MS_PER_MINUTE$3;
26505
26505
  const when = await GET_TIMESTAMP_FN();
26506
26506
  const nowTimestamp = when.getTime();
26507
26507
  const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
@@ -26772,8 +26772,8 @@ const Exchange = new ExchangeUtils();
26772
26772
 
26773
26773
  const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
26774
26774
  const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
26775
- const MS_PER_MINUTE$1 = 60000;
26776
- const INTERVAL_MINUTES$1 = {
26775
+ const MS_PER_MINUTE$2 = 60000;
26776
+ const INTERVAL_MINUTES$2 = {
26777
26777
  "1m": 1,
26778
26778
  "3m": 3,
26779
26779
  "5m": 5,
@@ -26786,7 +26786,7 @@ const INTERVAL_MINUTES$1 = {
26786
26786
  "8h": 480,
26787
26787
  };
26788
26788
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
26789
- const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
26789
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
26790
26790
  return Math.floor(timestamp / intervalMs) * intervalMs;
26791
26791
  };
26792
26792
  const BAR_LENGTH = 30;
@@ -26811,11 +26811,11 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
26811
26811
  async function checkCandles(params) {
26812
26812
  const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
26813
26813
  bt.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
26814
- const step = INTERVAL_MINUTES$1[interval];
26814
+ const step = INTERVAL_MINUTES$2[interval];
26815
26815
  if (!step) {
26816
26816
  throw new Error(`checkCandles: unsupported interval=${interval}`);
26817
26817
  }
26818
- const stepMs = step * MS_PER_MINUTE$1;
26818
+ const stepMs = step * MS_PER_MINUTE$2;
26819
26819
  const dir = path.join(baseDir, exchangeName, symbol, interval);
26820
26820
  const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
26821
26821
  const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -26885,11 +26885,11 @@ async function warmCandles(params) {
26885
26885
  from,
26886
26886
  to,
26887
26887
  });
26888
- const step = INTERVAL_MINUTES$1[interval];
26888
+ const step = INTERVAL_MINUTES$2[interval];
26889
26889
  if (!step) {
26890
26890
  throw new Error(`warmCandles: unsupported interval=${interval}`);
26891
26891
  }
26892
- const stepMs = step * MS_PER_MINUTE$1;
26892
+ const stepMs = step * MS_PER_MINUTE$2;
26893
26893
  const instance = new ExchangeInstance(exchangeName);
26894
26894
  const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
26895
26895
  const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
@@ -36397,8 +36397,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
36397
36397
  const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
36398
36398
  const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
36399
36399
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
36400
- const MS_PER_MINUTE = 60000;
36401
- const INTERVAL_MINUTES = {
36400
+ const MS_PER_MINUTE$1 = 60000;
36401
+ const INTERVAL_MINUTES$1 = {
36402
36402
  "1m": 1,
36403
36403
  "3m": 3,
36404
36404
  "5m": 5,
@@ -36431,11 +36431,11 @@ const INTERVAL_MINUTES = {
36431
36431
  * ```
36432
36432
  */
36433
36433
  const align = (timestamp, interval) => {
36434
- const intervalMinutes = INTERVAL_MINUTES[interval];
36434
+ const intervalMinutes = INTERVAL_MINUTES$1[interval];
36435
36435
  if (!intervalMinutes) {
36436
36436
  throw new Error(`align: unknown interval=${interval}`);
36437
36437
  }
36438
- const intervalMs = intervalMinutes * MS_PER_MINUTE;
36438
+ const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
36439
36439
  return Math.floor(timestamp / intervalMs) * intervalMs;
36440
36440
  };
36441
36441
  /**
@@ -36524,7 +36524,7 @@ class CacheInstance {
36524
36524
  */
36525
36525
  this.run = (...args) => {
36526
36526
  bt.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
36527
- const step = INTERVAL_MINUTES[this.interval];
36527
+ const step = INTERVAL_MINUTES$1[this.interval];
36528
36528
  {
36529
36529
  if (!MethodContextService.hasContext()) {
36530
36530
  throw new Error("CacheInstance run requires method context");
@@ -37190,6 +37190,45 @@ class StrategyUtils {
37190
37190
  */
37191
37191
  const Strategy = new StrategyUtils();
37192
37192
 
37193
+ const MS_PER_MINUTE = 60000;
37194
+ const INTERVAL_MINUTES = {
37195
+ "1m": 1,
37196
+ "3m": 3,
37197
+ "5m": 5,
37198
+ "15m": 15,
37199
+ "30m": 30,
37200
+ "1h": 60,
37201
+ "2h": 120,
37202
+ "4h": 240,
37203
+ "6h": 360,
37204
+ "8h": 480,
37205
+ };
37206
+ /**
37207
+ * Aligns timestamp down to the nearest interval boundary.
37208
+ * For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
37209
+ *
37210
+ * Candle timestamp convention:
37211
+ * - Candle timestamp = openTime (when candle opens)
37212
+ * - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
37213
+ *
37214
+ * Adapter contract:
37215
+ * - Adapter must return candles with timestamp = openTime
37216
+ * - First returned candle.timestamp must equal aligned since
37217
+ * - Adapter must return exactly `limit` candles
37218
+ *
37219
+ * @param date - Date to align
37220
+ * @param interval - Candle interval (e.g., "1m", "15m", "1h")
37221
+ * @returns New Date aligned down to interval boundary
37222
+ */
37223
+ const alignToInterval = (date, interval) => {
37224
+ const minutes = INTERVAL_MINUTES[interval];
37225
+ if (minutes === undefined) {
37226
+ throw new Error(`alignToInterval: unknown interval=${interval}`);
37227
+ }
37228
+ const intervalMs = minutes * MS_PER_MINUTE;
37229
+ return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
37230
+ };
37231
+
37193
37232
  /**
37194
37233
  * Rounds a price to the appropriate precision based on the tick size.
37195
37234
  *
@@ -37366,6 +37405,7 @@ exports.addRiskSchema = addRiskSchema;
37366
37405
  exports.addSizingSchema = addSizingSchema;
37367
37406
  exports.addStrategySchema = addStrategySchema;
37368
37407
  exports.addWalkerSchema = addWalkerSchema;
37408
+ exports.alignToInterval = alignToInterval;
37369
37409
  exports.checkCandles = checkCandles;
37370
37410
  exports.commitActivateScheduled = commitActivateScheduled;
37371
37411
  exports.commitBreakeven = commitBreakeven;