backtest-kit 3.0.4 → 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 +3 -3
- package/build/index.cjs +47 -18
- package/build/index.mjs +47 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<img src="
|
|
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
|
|
|
5
5
|
> A TypeScript framework for backtesting and live trading strategies on multi-asset, crypto, forex or [DEX (peer-to-peer marketplace)](https://en.wikipedia.org/wiki/Decentralized_finance#Decentralized_exchanges), spot, futures with crash-safe persistence, signal validation, and AI optimization.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
10
10
|
[](https://npmjs.org/package/backtest-kit)
|
|
@@ -364,7 +364,7 @@ Unlike cloud-based platforms, backtest-kit runs entirely in your environment. Yo
|
|
|
364
364
|
- Self-hosted - your code, your data, your infrastructure
|
|
365
365
|
- No platform fees or hidden costs
|
|
366
366
|
- Full control over execution and data sources
|
|
367
|
-
- [GUI](https://backtest-kit
|
|
367
|
+
- [GUI](https://npmjs.com/package/@backtest-kit/ui) for visualization and monitoring
|
|
368
368
|
|
|
369
369
|
## 🌍 Ecosystem
|
|
370
370
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
36361
|
-
const
|
|
36362
|
-
if (
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
36341
|
-
const
|
|
36342
|
-
if (
|
|
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
|
}
|