backtest-kit 3.0.13 → 3.0.15
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 +47 -0
- package/build/index.cjs +116 -67
- package/build/index.mjs +116 -68
- package/package.json +1 -1
- package/types.d.ts +24 -1
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$
|
|
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$
|
|
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";
|
|
@@ -914,11 +914,13 @@ class PersistBase {
|
|
|
914
914
|
entityName: this.entityName,
|
|
915
915
|
});
|
|
916
916
|
try {
|
|
917
|
-
const
|
|
918
|
-
const
|
|
919
|
-
.
|
|
920
|
-
|
|
921
|
-
|
|
917
|
+
const entityIds = [];
|
|
918
|
+
for await (const entry of await fs.opendir(this._directory)) {
|
|
919
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
920
|
+
entityIds.push(entry.name.slice(0, -5));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
entityIds.sort((a, b) => a.localeCompare(b, undefined, {
|
|
922
924
|
numeric: true,
|
|
923
925
|
sensitivity: "base",
|
|
924
926
|
}));
|
|
@@ -1625,7 +1627,7 @@ class PersistCandleUtils {
|
|
|
1625
1627
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1626
1628
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1627
1629
|
await stateStorage.waitForInit(isInitial);
|
|
1628
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1630
|
+
const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
|
|
1629
1631
|
// Calculate expected timestamps and fetch each candle directly
|
|
1630
1632
|
const cachedCandles = [];
|
|
1631
1633
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1681,7 +1683,7 @@ class PersistCandleUtils {
|
|
|
1681
1683
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1682
1684
|
await stateStorage.waitForInit(isInitial);
|
|
1683
1685
|
// Calculate step in milliseconds to determine candle close time
|
|
1684
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1686
|
+
const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
|
|
1685
1687
|
const now = Date.now();
|
|
1686
1688
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1687
1689
|
for (const candle of candles) {
|
|
@@ -1938,8 +1940,8 @@ class PersistNotificationUtils {
|
|
|
1938
1940
|
*/
|
|
1939
1941
|
const PersistNotificationAdapter = new PersistNotificationUtils();
|
|
1940
1942
|
|
|
1941
|
-
const MS_PER_MINUTE$
|
|
1942
|
-
const INTERVAL_MINUTES$
|
|
1943
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
1944
|
+
const INTERVAL_MINUTES$6 = {
|
|
1943
1945
|
"1m": 1,
|
|
1944
1946
|
"3m": 3,
|
|
1945
1947
|
"5m": 5,
|
|
@@ -1969,7 +1971,7 @@ const INTERVAL_MINUTES$5 = {
|
|
|
1969
1971
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
1970
1972
|
*/
|
|
1971
1973
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
1972
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
1974
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
|
|
1973
1975
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
1974
1976
|
};
|
|
1975
1977
|
/**
|
|
@@ -2114,9 +2116,9 @@ const WRITE_CANDLES_CACHE_FN$1 = functoolsKit.trycatch(functoolsKit.queued(async
|
|
|
2114
2116
|
* @returns Promise resolving to array of candle data
|
|
2115
2117
|
*/
|
|
2116
2118
|
const GET_CANDLES_FN = async (dto, since, self) => {
|
|
2117
|
-
const step = INTERVAL_MINUTES$
|
|
2119
|
+
const step = INTERVAL_MINUTES$6[dto.interval];
|
|
2118
2120
|
const sinceTimestamp = since.getTime();
|
|
2119
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
2121
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$4;
|
|
2120
2122
|
// Try to read from cache first
|
|
2121
2123
|
const cachedCandles = await READ_CANDLES_CACHE_FN$1(dto, sinceTimestamp, untilTimestamp, self);
|
|
2122
2124
|
if (cachedCandles !== null) {
|
|
@@ -2224,11 +2226,11 @@ class ClientExchange {
|
|
|
2224
2226
|
interval,
|
|
2225
2227
|
limit,
|
|
2226
2228
|
});
|
|
2227
|
-
const step = INTERVAL_MINUTES$
|
|
2229
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2228
2230
|
if (!step) {
|
|
2229
2231
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
2230
2232
|
}
|
|
2231
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2233
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2232
2234
|
// Align when down to interval boundary
|
|
2233
2235
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
2234
2236
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -2305,11 +2307,11 @@ class ClientExchange {
|
|
|
2305
2307
|
if (!this.params.execution.context.backtest) {
|
|
2306
2308
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
2307
2309
|
}
|
|
2308
|
-
const step = INTERVAL_MINUTES$
|
|
2310
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2309
2311
|
if (!step) {
|
|
2310
2312
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
2311
2313
|
}
|
|
2312
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2314
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2313
2315
|
const now = Date.now();
|
|
2314
2316
|
// Align when down to interval boundary
|
|
2315
2317
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -2473,11 +2475,11 @@ class ClientExchange {
|
|
|
2473
2475
|
sDate,
|
|
2474
2476
|
eDate,
|
|
2475
2477
|
});
|
|
2476
|
-
const step = INTERVAL_MINUTES$
|
|
2478
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2477
2479
|
if (!step) {
|
|
2478
2480
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
2479
2481
|
}
|
|
2480
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2482
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2481
2483
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
2482
2484
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
2483
2485
|
let sinceTimestamp;
|
|
@@ -2606,7 +2608,7 @@ class ClientExchange {
|
|
|
2606
2608
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
2607
2609
|
const to = new Date(alignedTo);
|
|
2608
2610
|
const from = new Date(alignedTo -
|
|
2609
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
2611
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
|
|
2610
2612
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
2611
2613
|
}
|
|
2612
2614
|
}
|
|
@@ -3071,7 +3073,7 @@ const beginTime = (run) => (...args) => {
|
|
|
3071
3073
|
return fn();
|
|
3072
3074
|
};
|
|
3073
3075
|
|
|
3074
|
-
const INTERVAL_MINUTES$
|
|
3076
|
+
const INTERVAL_MINUTES$5 = {
|
|
3075
3077
|
"1m": 1,
|
|
3076
3078
|
"3m": 3,
|
|
3077
3079
|
"5m": 5,
|
|
@@ -3584,7 +3586,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
3584
3586
|
}
|
|
3585
3587
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3586
3588
|
{
|
|
3587
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
3589
|
+
const intervalMinutes = INTERVAL_MINUTES$5[self.params.interval];
|
|
3588
3590
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
3589
3591
|
// Проверяем что прошел нужный интервал с последнего getSignal
|
|
3590
3592
|
if (self._lastSignalTimestamp !== null &&
|
|
@@ -8195,7 +8197,7 @@ class StrategyConnectionService {
|
|
|
8195
8197
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
8196
8198
|
* Used to generate timeframe arrays with proper spacing.
|
|
8197
8199
|
*/
|
|
8198
|
-
const INTERVAL_MINUTES$
|
|
8200
|
+
const INTERVAL_MINUTES$4 = {
|
|
8199
8201
|
"1m": 1,
|
|
8200
8202
|
"3m": 3,
|
|
8201
8203
|
"5m": 5,
|
|
@@ -8250,7 +8252,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
8250
8252
|
symbol,
|
|
8251
8253
|
});
|
|
8252
8254
|
const { interval, startDate, endDate } = self.params;
|
|
8253
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
8255
|
+
const intervalMinutes = INTERVAL_MINUTES$4[interval];
|
|
8254
8256
|
if (!intervalMinutes) {
|
|
8255
8257
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
8256
8258
|
}
|
|
@@ -26046,7 +26048,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
|
26046
26048
|
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
26047
26049
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
26048
26050
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
26049
|
-
const MS_PER_MINUTE$
|
|
26051
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
26050
26052
|
/**
|
|
26051
26053
|
* Gets current timestamp from execution context if available.
|
|
26052
26054
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -26101,7 +26103,7 @@ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
|
|
|
26101
26103
|
const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
|
|
26102
26104
|
throw new Error(`getOrderBook is not implemented for this exchange`);
|
|
26103
26105
|
};
|
|
26104
|
-
const INTERVAL_MINUTES$
|
|
26106
|
+
const INTERVAL_MINUTES$3 = {
|
|
26105
26107
|
"1m": 1,
|
|
26106
26108
|
"3m": 3,
|
|
26107
26109
|
"5m": 5,
|
|
@@ -26131,7 +26133,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
26131
26133
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
26132
26134
|
*/
|
|
26133
26135
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
26134
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
26136
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
26135
26137
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
26136
26138
|
};
|
|
26137
26139
|
/**
|
|
@@ -26269,11 +26271,11 @@ class ExchangeInstance {
|
|
|
26269
26271
|
limit,
|
|
26270
26272
|
});
|
|
26271
26273
|
const getCandles = this._methods.getCandles;
|
|
26272
|
-
const step = INTERVAL_MINUTES$
|
|
26274
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
26273
26275
|
if (!step) {
|
|
26274
26276
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
26275
26277
|
}
|
|
26276
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26278
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
26277
26279
|
// Align when down to interval boundary
|
|
26278
26280
|
const when = await GET_TIMESTAMP_FN();
|
|
26279
26281
|
const whenTimestamp = when.getTime();
|
|
@@ -26452,7 +26454,7 @@ class ExchangeInstance {
|
|
|
26452
26454
|
const when = await GET_TIMESTAMP_FN();
|
|
26453
26455
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
26454
26456
|
const to = new Date(alignedTo);
|
|
26455
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
26457
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
26456
26458
|
const isBacktest = await GET_BACKTEST_FN();
|
|
26457
26459
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
26458
26460
|
};
|
|
@@ -26495,11 +26497,11 @@ class ExchangeInstance {
|
|
|
26495
26497
|
sDate,
|
|
26496
26498
|
eDate,
|
|
26497
26499
|
});
|
|
26498
|
-
const step = INTERVAL_MINUTES$
|
|
26500
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
26499
26501
|
if (!step) {
|
|
26500
26502
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
26501
26503
|
}
|
|
26502
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26504
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
26503
26505
|
const when = await GET_TIMESTAMP_FN();
|
|
26504
26506
|
const nowTimestamp = when.getTime();
|
|
26505
26507
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -26770,8 +26772,8 @@ const Exchange = new ExchangeUtils();
|
|
|
26770
26772
|
|
|
26771
26773
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
26772
26774
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
26773
|
-
const MS_PER_MINUTE$
|
|
26774
|
-
const INTERVAL_MINUTES$
|
|
26775
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
26776
|
+
const INTERVAL_MINUTES$2 = {
|
|
26775
26777
|
"1m": 1,
|
|
26776
26778
|
"3m": 3,
|
|
26777
26779
|
"5m": 5,
|
|
@@ -26784,7 +26786,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
26784
26786
|
"8h": 480,
|
|
26785
26787
|
};
|
|
26786
26788
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
26787
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
26789
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
26788
26790
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
26789
26791
|
};
|
|
26790
26792
|
const BAR_LENGTH = 30;
|
|
@@ -26807,59 +26809,66 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
26807
26809
|
* @param params - Validation parameters
|
|
26808
26810
|
*/
|
|
26809
26811
|
async function checkCandles(params) {
|
|
26810
|
-
const { symbol, exchangeName, interval, baseDir = "./dump/data/candle" } = params;
|
|
26812
|
+
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
26811
26813
|
bt.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
26812
|
-
const step = INTERVAL_MINUTES$
|
|
26814
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
26813
26815
|
if (!step) {
|
|
26814
26816
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
26815
26817
|
}
|
|
26816
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26818
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
26817
26819
|
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
26820
|
+
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
26821
|
+
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
26818
26822
|
try {
|
|
26819
26823
|
await fs.stat(dir);
|
|
26820
26824
|
}
|
|
26821
26825
|
catch {
|
|
26822
26826
|
throw new Error(`checkCandles: cache directory not found: ${dir}`);
|
|
26823
26827
|
}
|
|
26824
|
-
//
|
|
26825
|
-
|
|
26828
|
+
// Collect only filenames (strings) in range via async iterator — no full readdir in memory
|
|
26829
|
+
const files = [];
|
|
26826
26830
|
for await (const entry of await fs.opendir(dir)) {
|
|
26827
|
-
if (entry.isFile()
|
|
26828
|
-
|
|
26831
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
26832
|
+
continue;
|
|
26833
|
+
}
|
|
26834
|
+
const ts = Number(entry.name.replace(".json", ""));
|
|
26835
|
+
if (ts >= fromTs && ts < toTs) {
|
|
26836
|
+
files.push(entry.name);
|
|
26829
26837
|
}
|
|
26830
26838
|
}
|
|
26831
|
-
if (
|
|
26832
|
-
throw new Error(`checkCandles: no cached candles in ${dir}`);
|
|
26839
|
+
if (files.length === 0) {
|
|
26840
|
+
throw new Error(`checkCandles: no cached candles in range [${fromTs}, ${toTs}) in ${dir}`);
|
|
26833
26841
|
}
|
|
26834
|
-
|
|
26835
|
-
let checkd = 0;
|
|
26842
|
+
files.sort();
|
|
26836
26843
|
let prevTimestamp = null;
|
|
26837
26844
|
let prevName = null;
|
|
26838
|
-
PRINT_PROGRESS_FN(
|
|
26839
|
-
for
|
|
26840
|
-
|
|
26841
|
-
continue;
|
|
26842
|
-
}
|
|
26843
|
-
const filePath = path.join(dir, entry.name);
|
|
26845
|
+
PRINT_PROGRESS_FN(0, files.length, symbol, interval);
|
|
26846
|
+
for (let i = 0; i < files.length; i++) {
|
|
26847
|
+
const filePath = path.join(dir, files[i]);
|
|
26844
26848
|
const raw = await fs.readFile(filePath, "utf-8");
|
|
26845
|
-
|
|
26849
|
+
let candle;
|
|
26850
|
+
try {
|
|
26851
|
+
candle = JSON.parse(raw);
|
|
26852
|
+
}
|
|
26853
|
+
catch {
|
|
26854
|
+
throw new Error(`checkCandles: ${files[i]} contains invalid JSON`);
|
|
26855
|
+
}
|
|
26846
26856
|
const { timestamp } = candle;
|
|
26847
26857
|
const aligned = ALIGN_TO_INTERVAL_FN(timestamp, step);
|
|
26848
26858
|
if (timestamp !== aligned) {
|
|
26849
|
-
throw new Error(`checkCandles: ${
|
|
26859
|
+
throw new Error(`checkCandles: ${files[i]} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
|
|
26850
26860
|
}
|
|
26851
26861
|
if (prevTimestamp !== null) {
|
|
26852
26862
|
const gap = timestamp - prevTimestamp;
|
|
26853
26863
|
if (gap !== stepMs) {
|
|
26854
|
-
throw new Error(`checkCandles: gap between ${prevName} and ${
|
|
26864
|
+
throw new Error(`checkCandles: gap between ${prevName} and ${files[i]} (actual=${gap}ms, expected=${stepMs}ms)`);
|
|
26855
26865
|
}
|
|
26856
26866
|
}
|
|
26857
26867
|
prevTimestamp = timestamp;
|
|
26858
|
-
prevName =
|
|
26859
|
-
|
|
26860
|
-
PRINT_PROGRESS_FN(checkd, totalFiles, symbol, interval);
|
|
26868
|
+
prevName = files[i];
|
|
26869
|
+
PRINT_PROGRESS_FN(i + 1, files.length, symbol, interval);
|
|
26861
26870
|
}
|
|
26862
|
-
console.log(`checkCandles: OK ${
|
|
26871
|
+
console.log(`checkCandles: OK ${files.length} candles ${symbol} ${interval}`);
|
|
26863
26872
|
}
|
|
26864
26873
|
/**
|
|
26865
26874
|
* Pre-caches candles for a date range into persist storage.
|
|
@@ -26876,11 +26885,11 @@ async function warmCandles(params) {
|
|
|
26876
26885
|
from,
|
|
26877
26886
|
to,
|
|
26878
26887
|
});
|
|
26879
|
-
const step = INTERVAL_MINUTES$
|
|
26888
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
26880
26889
|
if (!step) {
|
|
26881
26890
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
26882
26891
|
}
|
|
26883
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26892
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
26884
26893
|
const instance = new ExchangeInstance(exchangeName);
|
|
26885
26894
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
26886
26895
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -36388,8 +36397,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
|
|
|
36388
36397
|
const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
|
|
36389
36398
|
const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
|
|
36390
36399
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
36391
|
-
const MS_PER_MINUTE = 60000;
|
|
36392
|
-
const INTERVAL_MINUTES = {
|
|
36400
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
36401
|
+
const INTERVAL_MINUTES$1 = {
|
|
36393
36402
|
"1m": 1,
|
|
36394
36403
|
"3m": 3,
|
|
36395
36404
|
"5m": 5,
|
|
@@ -36422,11 +36431,11 @@ const INTERVAL_MINUTES = {
|
|
|
36422
36431
|
* ```
|
|
36423
36432
|
*/
|
|
36424
36433
|
const align = (timestamp, interval) => {
|
|
36425
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
36434
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
36426
36435
|
if (!intervalMinutes) {
|
|
36427
36436
|
throw new Error(`align: unknown interval=${interval}`);
|
|
36428
36437
|
}
|
|
36429
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
36438
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
36430
36439
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
36431
36440
|
};
|
|
36432
36441
|
/**
|
|
@@ -36515,7 +36524,7 @@ class CacheInstance {
|
|
|
36515
36524
|
*/
|
|
36516
36525
|
this.run = (...args) => {
|
|
36517
36526
|
bt.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
36518
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
36527
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
36519
36528
|
{
|
|
36520
36529
|
if (!MethodContextService.hasContext()) {
|
|
36521
36530
|
throw new Error("CacheInstance run requires method context");
|
|
@@ -37181,6 +37190,45 @@ class StrategyUtils {
|
|
|
37181
37190
|
*/
|
|
37182
37191
|
const Strategy = new StrategyUtils();
|
|
37183
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
|
+
|
|
37184
37232
|
/**
|
|
37185
37233
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
37186
37234
|
*
|
|
@@ -37357,6 +37405,7 @@ exports.addRiskSchema = addRiskSchema;
|
|
|
37357
37405
|
exports.addSizingSchema = addSizingSchema;
|
|
37358
37406
|
exports.addStrategySchema = addStrategySchema;
|
|
37359
37407
|
exports.addWalkerSchema = addWalkerSchema;
|
|
37408
|
+
exports.alignToInterval = alignToInterval;
|
|
37360
37409
|
exports.checkCandles = checkCandles;
|
|
37361
37410
|
exports.commitActivateScheduled = commitActivateScheduled;
|
|
37362
37411
|
exports.commitBreakeven = commitBreakeven;
|
package/build/index.mjs
CHANGED
|
@@ -695,7 +695,7 @@ async function writeFileAtomic(file, data, options = {}) {
|
|
|
695
695
|
var _a$2;
|
|
696
696
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
697
697
|
// Calculate step in milliseconds for candle close time validation
|
|
698
|
-
const INTERVAL_MINUTES$
|
|
698
|
+
const INTERVAL_MINUTES$7 = {
|
|
699
699
|
"1m": 1,
|
|
700
700
|
"3m": 3,
|
|
701
701
|
"5m": 5,
|
|
@@ -707,7 +707,7 @@ const INTERVAL_MINUTES$6 = {
|
|
|
707
707
|
"6h": 360,
|
|
708
708
|
"8h": 480,
|
|
709
709
|
};
|
|
710
|
-
const MS_PER_MINUTE$
|
|
710
|
+
const MS_PER_MINUTE$5 = 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";
|
|
@@ -894,11 +894,13 @@ class PersistBase {
|
|
|
894
894
|
entityName: this.entityName,
|
|
895
895
|
});
|
|
896
896
|
try {
|
|
897
|
-
const
|
|
898
|
-
const
|
|
899
|
-
.
|
|
900
|
-
|
|
901
|
-
|
|
897
|
+
const entityIds = [];
|
|
898
|
+
for await (const entry of await fs__default.opendir(this._directory)) {
|
|
899
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
900
|
+
entityIds.push(entry.name.slice(0, -5));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
entityIds.sort((a, b) => a.localeCompare(b, undefined, {
|
|
902
904
|
numeric: true,
|
|
903
905
|
sensitivity: "base",
|
|
904
906
|
}));
|
|
@@ -1605,7 +1607,7 @@ class PersistCandleUtils {
|
|
|
1605
1607
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1606
1608
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1607
1609
|
await stateStorage.waitForInit(isInitial);
|
|
1608
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1610
|
+
const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
|
|
1609
1611
|
// Calculate expected timestamps and fetch each candle directly
|
|
1610
1612
|
const cachedCandles = [];
|
|
1611
1613
|
for (let i = 0; i < limit; i++) {
|
|
@@ -1661,7 +1663,7 @@ class PersistCandleUtils {
|
|
|
1661
1663
|
const stateStorage = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
1662
1664
|
await stateStorage.waitForInit(isInitial);
|
|
1663
1665
|
// Calculate step in milliseconds to determine candle close time
|
|
1664
|
-
const stepMs = INTERVAL_MINUTES$
|
|
1666
|
+
const stepMs = INTERVAL_MINUTES$7[interval] * MS_PER_MINUTE$5;
|
|
1665
1667
|
const now = Date.now();
|
|
1666
1668
|
// Write each candle as a separate file, skipping incomplete candles
|
|
1667
1669
|
for (const candle of candles) {
|
|
@@ -1918,8 +1920,8 @@ class PersistNotificationUtils {
|
|
|
1918
1920
|
*/
|
|
1919
1921
|
const PersistNotificationAdapter = new PersistNotificationUtils();
|
|
1920
1922
|
|
|
1921
|
-
const MS_PER_MINUTE$
|
|
1922
|
-
const INTERVAL_MINUTES$
|
|
1923
|
+
const MS_PER_MINUTE$4 = 60000;
|
|
1924
|
+
const INTERVAL_MINUTES$6 = {
|
|
1923
1925
|
"1m": 1,
|
|
1924
1926
|
"3m": 3,
|
|
1925
1927
|
"5m": 5,
|
|
@@ -1949,7 +1951,7 @@ const INTERVAL_MINUTES$5 = {
|
|
|
1949
1951
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
1950
1952
|
*/
|
|
1951
1953
|
const ALIGN_TO_INTERVAL_FN$2 = (timestamp, intervalMinutes) => {
|
|
1952
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
1954
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$4;
|
|
1953
1955
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
1954
1956
|
};
|
|
1955
1957
|
/**
|
|
@@ -2094,9 +2096,9 @@ const WRITE_CANDLES_CACHE_FN$1 = trycatch(queued(async (candles, dto, self) => {
|
|
|
2094
2096
|
* @returns Promise resolving to array of candle data
|
|
2095
2097
|
*/
|
|
2096
2098
|
const GET_CANDLES_FN = async (dto, since, self) => {
|
|
2097
|
-
const step = INTERVAL_MINUTES$
|
|
2099
|
+
const step = INTERVAL_MINUTES$6[dto.interval];
|
|
2098
2100
|
const sinceTimestamp = since.getTime();
|
|
2099
|
-
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$
|
|
2101
|
+
const untilTimestamp = sinceTimestamp + dto.limit * step * MS_PER_MINUTE$4;
|
|
2100
2102
|
// Try to read from cache first
|
|
2101
2103
|
const cachedCandles = await READ_CANDLES_CACHE_FN$1(dto, sinceTimestamp, untilTimestamp, self);
|
|
2102
2104
|
if (cachedCandles !== null) {
|
|
@@ -2204,11 +2206,11 @@ class ClientExchange {
|
|
|
2204
2206
|
interval,
|
|
2205
2207
|
limit,
|
|
2206
2208
|
});
|
|
2207
|
-
const step = INTERVAL_MINUTES$
|
|
2209
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2208
2210
|
if (!step) {
|
|
2209
2211
|
throw new Error(`ClientExchange unknown interval=${interval}`);
|
|
2210
2212
|
}
|
|
2211
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2213
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2212
2214
|
// Align when down to interval boundary
|
|
2213
2215
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
2214
2216
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
@@ -2285,11 +2287,11 @@ class ClientExchange {
|
|
|
2285
2287
|
if (!this.params.execution.context.backtest) {
|
|
2286
2288
|
throw new Error(`ClientExchange getNextCandles: cannot fetch future candles in live mode`);
|
|
2287
2289
|
}
|
|
2288
|
-
const step = INTERVAL_MINUTES$
|
|
2290
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2289
2291
|
if (!step) {
|
|
2290
2292
|
throw new Error(`ClientExchange getNextCandles: unknown interval=${interval}`);
|
|
2291
2293
|
}
|
|
2292
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2294
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2293
2295
|
const now = Date.now();
|
|
2294
2296
|
// Align when down to interval boundary
|
|
2295
2297
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
@@ -2453,11 +2455,11 @@ class ClientExchange {
|
|
|
2453
2455
|
sDate,
|
|
2454
2456
|
eDate,
|
|
2455
2457
|
});
|
|
2456
|
-
const step = INTERVAL_MINUTES$
|
|
2458
|
+
const step = INTERVAL_MINUTES$6[interval];
|
|
2457
2459
|
if (!step) {
|
|
2458
2460
|
throw new Error(`ClientExchange getRawCandles: unknown interval=${interval}`);
|
|
2459
2461
|
}
|
|
2460
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
2462
|
+
const stepMs = step * MS_PER_MINUTE$4;
|
|
2461
2463
|
const whenTimestamp = this.params.execution.context.when.getTime();
|
|
2462
2464
|
const alignedWhen = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, step);
|
|
2463
2465
|
let sinceTimestamp;
|
|
@@ -2586,7 +2588,7 @@ class ClientExchange {
|
|
|
2586
2588
|
const alignedTo = ALIGN_TO_INTERVAL_FN$2(whenTimestamp, GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
2587
2589
|
const to = new Date(alignedTo);
|
|
2588
2590
|
const from = new Date(alignedTo -
|
|
2589
|
-
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
2591
|
+
GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$4);
|
|
2590
2592
|
return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
|
|
2591
2593
|
}
|
|
2592
2594
|
}
|
|
@@ -3051,7 +3053,7 @@ const beginTime = (run) => (...args) => {
|
|
|
3051
3053
|
return fn();
|
|
3052
3054
|
};
|
|
3053
3055
|
|
|
3054
|
-
const INTERVAL_MINUTES$
|
|
3056
|
+
const INTERVAL_MINUTES$5 = {
|
|
3055
3057
|
"1m": 1,
|
|
3056
3058
|
"3m": 3,
|
|
3057
3059
|
"5m": 5,
|
|
@@ -3564,7 +3566,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
3564
3566
|
}
|
|
3565
3567
|
const currentTime = self.params.execution.context.when.getTime();
|
|
3566
3568
|
{
|
|
3567
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
3569
|
+
const intervalMinutes = INTERVAL_MINUTES$5[self.params.interval];
|
|
3568
3570
|
const intervalMs = intervalMinutes * 60 * 1000;
|
|
3569
3571
|
// Проверяем что прошел нужный интервал с последнего getSignal
|
|
3570
3572
|
if (self._lastSignalTimestamp !== null &&
|
|
@@ -8175,7 +8177,7 @@ class StrategyConnectionService {
|
|
|
8175
8177
|
* Maps FrameInterval to minutes for timestamp calculation.
|
|
8176
8178
|
* Used to generate timeframe arrays with proper spacing.
|
|
8177
8179
|
*/
|
|
8178
|
-
const INTERVAL_MINUTES$
|
|
8180
|
+
const INTERVAL_MINUTES$4 = {
|
|
8179
8181
|
"1m": 1,
|
|
8180
8182
|
"3m": 3,
|
|
8181
8183
|
"5m": 5,
|
|
@@ -8230,7 +8232,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
|
|
|
8230
8232
|
symbol,
|
|
8231
8233
|
});
|
|
8232
8234
|
const { interval, startDate, endDate } = self.params;
|
|
8233
|
-
const intervalMinutes = INTERVAL_MINUTES$
|
|
8235
|
+
const intervalMinutes = INTERVAL_MINUTES$4[interval];
|
|
8234
8236
|
if (!intervalMinutes) {
|
|
8235
8237
|
throw new Error(`ClientFrame unknown interval: ${interval}`);
|
|
8236
8238
|
}
|
|
@@ -26026,7 +26028,7 @@ const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
|
26026
26028
|
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
26027
26029
|
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
26028
26030
|
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
26029
|
-
const MS_PER_MINUTE$
|
|
26031
|
+
const MS_PER_MINUTE$3 = 60000;
|
|
26030
26032
|
/**
|
|
26031
26033
|
* Gets current timestamp from execution context if available.
|
|
26032
26034
|
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
@@ -26081,7 +26083,7 @@ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
|
|
|
26081
26083
|
const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
|
|
26082
26084
|
throw new Error(`getOrderBook is not implemented for this exchange`);
|
|
26083
26085
|
};
|
|
26084
|
-
const INTERVAL_MINUTES$
|
|
26086
|
+
const INTERVAL_MINUTES$3 = {
|
|
26085
26087
|
"1m": 1,
|
|
26086
26088
|
"3m": 3,
|
|
26087
26089
|
"5m": 5,
|
|
@@ -26111,7 +26113,7 @@ const INTERVAL_MINUTES$2 = {
|
|
|
26111
26113
|
* @returns Aligned timestamp rounded down to interval boundary
|
|
26112
26114
|
*/
|
|
26113
26115
|
const ALIGN_TO_INTERVAL_FN$1 = (timestamp, intervalMinutes) => {
|
|
26114
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
26116
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$3;
|
|
26115
26117
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
26116
26118
|
};
|
|
26117
26119
|
/**
|
|
@@ -26249,11 +26251,11 @@ class ExchangeInstance {
|
|
|
26249
26251
|
limit,
|
|
26250
26252
|
});
|
|
26251
26253
|
const getCandles = this._methods.getCandles;
|
|
26252
|
-
const step = INTERVAL_MINUTES$
|
|
26254
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
26253
26255
|
if (!step) {
|
|
26254
26256
|
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
26255
26257
|
}
|
|
26256
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26258
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
26257
26259
|
// Align when down to interval boundary
|
|
26258
26260
|
const when = await GET_TIMESTAMP_FN();
|
|
26259
26261
|
const whenTimestamp = when.getTime();
|
|
@@ -26432,7 +26434,7 @@ class ExchangeInstance {
|
|
|
26432
26434
|
const when = await GET_TIMESTAMP_FN();
|
|
26433
26435
|
const alignedTo = ALIGN_TO_INTERVAL_FN$1(when.getTime(), GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES);
|
|
26434
26436
|
const to = new Date(alignedTo);
|
|
26435
|
-
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$
|
|
26437
|
+
const from = new Date(alignedTo - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * MS_PER_MINUTE$3);
|
|
26436
26438
|
const isBacktest = await GET_BACKTEST_FN();
|
|
26437
26439
|
return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
|
|
26438
26440
|
};
|
|
@@ -26475,11 +26477,11 @@ class ExchangeInstance {
|
|
|
26475
26477
|
sDate,
|
|
26476
26478
|
eDate,
|
|
26477
26479
|
});
|
|
26478
|
-
const step = INTERVAL_MINUTES$
|
|
26480
|
+
const step = INTERVAL_MINUTES$3[interval];
|
|
26479
26481
|
if (!step) {
|
|
26480
26482
|
throw new Error(`ExchangeInstance getRawCandles: unknown interval=${interval}`);
|
|
26481
26483
|
}
|
|
26482
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26484
|
+
const stepMs = step * MS_PER_MINUTE$3;
|
|
26483
26485
|
const when = await GET_TIMESTAMP_FN();
|
|
26484
26486
|
const nowTimestamp = when.getTime();
|
|
26485
26487
|
const alignedNow = ALIGN_TO_INTERVAL_FN$1(nowTimestamp, step);
|
|
@@ -26750,8 +26752,8 @@ const Exchange = new ExchangeUtils();
|
|
|
26750
26752
|
|
|
26751
26753
|
const WARM_CANDLES_METHOD_NAME = "cache.warmCandles";
|
|
26752
26754
|
const CHECK_CANDLES_METHOD_NAME = "cache.checkCandles";
|
|
26753
|
-
const MS_PER_MINUTE$
|
|
26754
|
-
const INTERVAL_MINUTES$
|
|
26755
|
+
const MS_PER_MINUTE$2 = 60000;
|
|
26756
|
+
const INTERVAL_MINUTES$2 = {
|
|
26755
26757
|
"1m": 1,
|
|
26756
26758
|
"3m": 3,
|
|
26757
26759
|
"5m": 5,
|
|
@@ -26764,7 +26766,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
26764
26766
|
"8h": 480,
|
|
26765
26767
|
};
|
|
26766
26768
|
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
26767
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE$
|
|
26769
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
|
|
26768
26770
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
26769
26771
|
};
|
|
26770
26772
|
const BAR_LENGTH = 30;
|
|
@@ -26787,59 +26789,66 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
26787
26789
|
* @param params - Validation parameters
|
|
26788
26790
|
*/
|
|
26789
26791
|
async function checkCandles(params) {
|
|
26790
|
-
const { symbol, exchangeName, interval, baseDir = "./dump/data/candle" } = params;
|
|
26792
|
+
const { symbol, exchangeName, interval, from, to, baseDir = "./dump/data/candle" } = params;
|
|
26791
26793
|
bt.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
26792
|
-
const step = INTERVAL_MINUTES$
|
|
26794
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
26793
26795
|
if (!step) {
|
|
26794
26796
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
26795
26797
|
}
|
|
26796
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26798
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
26797
26799
|
const dir = join(baseDir, exchangeName, symbol, interval);
|
|
26800
|
+
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
26801
|
+
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
26798
26802
|
try {
|
|
26799
26803
|
await stat(dir);
|
|
26800
26804
|
}
|
|
26801
26805
|
catch {
|
|
26802
26806
|
throw new Error(`checkCandles: cache directory not found: ${dir}`);
|
|
26803
26807
|
}
|
|
26804
|
-
//
|
|
26805
|
-
|
|
26808
|
+
// Collect only filenames (strings) in range via async iterator — no full readdir in memory
|
|
26809
|
+
const files = [];
|
|
26806
26810
|
for await (const entry of await opendir(dir)) {
|
|
26807
|
-
if (entry.isFile()
|
|
26808
|
-
|
|
26811
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
26812
|
+
continue;
|
|
26813
|
+
}
|
|
26814
|
+
const ts = Number(entry.name.replace(".json", ""));
|
|
26815
|
+
if (ts >= fromTs && ts < toTs) {
|
|
26816
|
+
files.push(entry.name);
|
|
26809
26817
|
}
|
|
26810
26818
|
}
|
|
26811
|
-
if (
|
|
26812
|
-
throw new Error(`checkCandles: no cached candles in ${dir}`);
|
|
26819
|
+
if (files.length === 0) {
|
|
26820
|
+
throw new Error(`checkCandles: no cached candles in range [${fromTs}, ${toTs}) in ${dir}`);
|
|
26813
26821
|
}
|
|
26814
|
-
|
|
26815
|
-
let checkd = 0;
|
|
26822
|
+
files.sort();
|
|
26816
26823
|
let prevTimestamp = null;
|
|
26817
26824
|
let prevName = null;
|
|
26818
|
-
PRINT_PROGRESS_FN(
|
|
26819
|
-
for
|
|
26820
|
-
|
|
26821
|
-
continue;
|
|
26822
|
-
}
|
|
26823
|
-
const filePath = join(dir, entry.name);
|
|
26825
|
+
PRINT_PROGRESS_FN(0, files.length, symbol, interval);
|
|
26826
|
+
for (let i = 0; i < files.length; i++) {
|
|
26827
|
+
const filePath = join(dir, files[i]);
|
|
26824
26828
|
const raw = await readFile(filePath, "utf-8");
|
|
26825
|
-
|
|
26829
|
+
let candle;
|
|
26830
|
+
try {
|
|
26831
|
+
candle = JSON.parse(raw);
|
|
26832
|
+
}
|
|
26833
|
+
catch {
|
|
26834
|
+
throw new Error(`checkCandles: ${files[i]} contains invalid JSON`);
|
|
26835
|
+
}
|
|
26826
26836
|
const { timestamp } = candle;
|
|
26827
26837
|
const aligned = ALIGN_TO_INTERVAL_FN(timestamp, step);
|
|
26828
26838
|
if (timestamp !== aligned) {
|
|
26829
|
-
throw new Error(`checkCandles: ${
|
|
26839
|
+
throw new Error(`checkCandles: ${files[i]} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
|
|
26830
26840
|
}
|
|
26831
26841
|
if (prevTimestamp !== null) {
|
|
26832
26842
|
const gap = timestamp - prevTimestamp;
|
|
26833
26843
|
if (gap !== stepMs) {
|
|
26834
|
-
throw new Error(`checkCandles: gap between ${prevName} and ${
|
|
26844
|
+
throw new Error(`checkCandles: gap between ${prevName} and ${files[i]} (actual=${gap}ms, expected=${stepMs}ms)`);
|
|
26835
26845
|
}
|
|
26836
26846
|
}
|
|
26837
26847
|
prevTimestamp = timestamp;
|
|
26838
|
-
prevName =
|
|
26839
|
-
|
|
26840
|
-
PRINT_PROGRESS_FN(checkd, totalFiles, symbol, interval);
|
|
26848
|
+
prevName = files[i];
|
|
26849
|
+
PRINT_PROGRESS_FN(i + 1, files.length, symbol, interval);
|
|
26841
26850
|
}
|
|
26842
|
-
console.log(`checkCandles: OK ${
|
|
26851
|
+
console.log(`checkCandles: OK ${files.length} candles ${symbol} ${interval}`);
|
|
26843
26852
|
}
|
|
26844
26853
|
/**
|
|
26845
26854
|
* Pre-caches candles for a date range into persist storage.
|
|
@@ -26856,11 +26865,11 @@ async function warmCandles(params) {
|
|
|
26856
26865
|
from,
|
|
26857
26866
|
to,
|
|
26858
26867
|
});
|
|
26859
|
-
const step = INTERVAL_MINUTES$
|
|
26868
|
+
const step = INTERVAL_MINUTES$2[interval];
|
|
26860
26869
|
if (!step) {
|
|
26861
26870
|
throw new Error(`warmCandles: unsupported interval=${interval}`);
|
|
26862
26871
|
}
|
|
26863
|
-
const stepMs = step * MS_PER_MINUTE$
|
|
26872
|
+
const stepMs = step * MS_PER_MINUTE$2;
|
|
26864
26873
|
const instance = new ExchangeInstance(exchangeName);
|
|
26865
26874
|
const sinceTimestamp = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
26866
26875
|
const untilTimestamp = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
@@ -36368,8 +36377,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
|
|
|
36368
36377
|
const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
|
|
36369
36378
|
const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
|
|
36370
36379
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
36371
|
-
const MS_PER_MINUTE = 60000;
|
|
36372
|
-
const INTERVAL_MINUTES = {
|
|
36380
|
+
const MS_PER_MINUTE$1 = 60000;
|
|
36381
|
+
const INTERVAL_MINUTES$1 = {
|
|
36373
36382
|
"1m": 1,
|
|
36374
36383
|
"3m": 3,
|
|
36375
36384
|
"5m": 5,
|
|
@@ -36402,11 +36411,11 @@ const INTERVAL_MINUTES = {
|
|
|
36402
36411
|
* ```
|
|
36403
36412
|
*/
|
|
36404
36413
|
const align = (timestamp, interval) => {
|
|
36405
|
-
const intervalMinutes = INTERVAL_MINUTES[interval];
|
|
36414
|
+
const intervalMinutes = INTERVAL_MINUTES$1[interval];
|
|
36406
36415
|
if (!intervalMinutes) {
|
|
36407
36416
|
throw new Error(`align: unknown interval=${interval}`);
|
|
36408
36417
|
}
|
|
36409
|
-
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
36418
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE$1;
|
|
36410
36419
|
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
36411
36420
|
};
|
|
36412
36421
|
/**
|
|
@@ -36495,7 +36504,7 @@ class CacheInstance {
|
|
|
36495
36504
|
*/
|
|
36496
36505
|
this.run = (...args) => {
|
|
36497
36506
|
bt.loggerService.debug(CACHE_METHOD_NAME_RUN, { args });
|
|
36498
|
-
const step = INTERVAL_MINUTES[this.interval];
|
|
36507
|
+
const step = INTERVAL_MINUTES$1[this.interval];
|
|
36499
36508
|
{
|
|
36500
36509
|
if (!MethodContextService.hasContext()) {
|
|
36501
36510
|
throw new Error("CacheInstance run requires method context");
|
|
@@ -37161,6 +37170,45 @@ class StrategyUtils {
|
|
|
37161
37170
|
*/
|
|
37162
37171
|
const Strategy = new StrategyUtils();
|
|
37163
37172
|
|
|
37173
|
+
const MS_PER_MINUTE = 60000;
|
|
37174
|
+
const INTERVAL_MINUTES = {
|
|
37175
|
+
"1m": 1,
|
|
37176
|
+
"3m": 3,
|
|
37177
|
+
"5m": 5,
|
|
37178
|
+
"15m": 15,
|
|
37179
|
+
"30m": 30,
|
|
37180
|
+
"1h": 60,
|
|
37181
|
+
"2h": 120,
|
|
37182
|
+
"4h": 240,
|
|
37183
|
+
"6h": 360,
|
|
37184
|
+
"8h": 480,
|
|
37185
|
+
};
|
|
37186
|
+
/**
|
|
37187
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
37188
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
37189
|
+
*
|
|
37190
|
+
* Candle timestamp convention:
|
|
37191
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
37192
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
37193
|
+
*
|
|
37194
|
+
* Adapter contract:
|
|
37195
|
+
* - Adapter must return candles with timestamp = openTime
|
|
37196
|
+
* - First returned candle.timestamp must equal aligned since
|
|
37197
|
+
* - Adapter must return exactly `limit` candles
|
|
37198
|
+
*
|
|
37199
|
+
* @param date - Date to align
|
|
37200
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
37201
|
+
* @returns New Date aligned down to interval boundary
|
|
37202
|
+
*/
|
|
37203
|
+
const alignToInterval = (date, interval) => {
|
|
37204
|
+
const minutes = INTERVAL_MINUTES[interval];
|
|
37205
|
+
if (minutes === undefined) {
|
|
37206
|
+
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
37207
|
+
}
|
|
37208
|
+
const intervalMs = minutes * MS_PER_MINUTE;
|
|
37209
|
+
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
37210
|
+
};
|
|
37211
|
+
|
|
37164
37212
|
/**
|
|
37165
37213
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
37166
37214
|
*
|
|
@@ -37293,4 +37341,4 @@ const set = (object, path, value) => {
|
|
|
37293
37341
|
}
|
|
37294
37342
|
};
|
|
37295
37343
|
|
|
37296
|
-
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, warmCandles };
|
|
37344
|
+
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, warmCandles };
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -337,6 +337,10 @@ interface ICheckCandlesParams {
|
|
|
337
337
|
exchangeName: ExchangeName;
|
|
338
338
|
/** Candle time interval (e.g., "1m", "4h") */
|
|
339
339
|
interval: CandleInterval;
|
|
340
|
+
/** Start date of the validation range (inclusive) */
|
|
341
|
+
from: Date;
|
|
342
|
+
/** End date of the validation range (inclusive) */
|
|
343
|
+
to: Date;
|
|
340
344
|
/** Base directory of candle persist storage (default: "./dump/data/candle") */
|
|
341
345
|
baseDir?: string;
|
|
342
346
|
}
|
|
@@ -16178,6 +16182,25 @@ declare namespace emitters {
|
|
|
16178
16182
|
export { emitters_activePingSubject as activePingSubject, emitters_backtestScheduleOpenSubject as backtestScheduleOpenSubject, emitters_breakevenSubject as breakevenSubject, emitters_doneBacktestSubject as doneBacktestSubject, emitters_doneLiveSubject as doneLiveSubject, emitters_doneWalkerSubject as doneWalkerSubject, emitters_errorEmitter as errorEmitter, emitters_exitEmitter as exitEmitter, emitters_partialLossSubject as partialLossSubject, emitters_partialProfitSubject as partialProfitSubject, emitters_performanceEmitter as performanceEmitter, emitters_progressBacktestEmitter as progressBacktestEmitter, emitters_progressWalkerEmitter as progressWalkerEmitter, emitters_riskSubject as riskSubject, emitters_schedulePingSubject as schedulePingSubject, emitters_signalBacktestEmitter as signalBacktestEmitter, emitters_signalEmitter as signalEmitter, emitters_signalLiveEmitter as signalLiveEmitter, emitters_strategyCommitSubject as strategyCommitSubject, emitters_validationSubject as validationSubject, emitters_walkerCompleteSubject as walkerCompleteSubject, emitters_walkerEmitter as walkerEmitter, emitters_walkerStopSubject as walkerStopSubject };
|
|
16179
16183
|
}
|
|
16180
16184
|
|
|
16185
|
+
/**
|
|
16186
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
16187
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
16188
|
+
*
|
|
16189
|
+
* Candle timestamp convention:
|
|
16190
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
16191
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
16192
|
+
*
|
|
16193
|
+
* Adapter contract:
|
|
16194
|
+
* - Adapter must return candles with timestamp = openTime
|
|
16195
|
+
* - First returned candle.timestamp must equal aligned since
|
|
16196
|
+
* - Adapter must return exactly `limit` candles
|
|
16197
|
+
*
|
|
16198
|
+
* @param date - Date to align
|
|
16199
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
16200
|
+
* @returns New Date aligned down to interval boundary
|
|
16201
|
+
*/
|
|
16202
|
+
declare const alignToInterval: (date: Date, interval: CandleInterval) => Date;
|
|
16203
|
+
|
|
16181
16204
|
/**
|
|
16182
16205
|
* Rounds a price to the appropriate precision based on the tick size.
|
|
16183
16206
|
*
|
|
@@ -20819,4 +20842,4 @@ declare const backtest: {
|
|
|
20819
20842
|
loggerService: LoggerService;
|
|
20820
20843
|
};
|
|
20821
20844
|
|
|
20822
|
-
export { ActionBase, type ActivateScheduledCommit, type ActivateScheduledCommitNotification, type ActivePingContract, Backtest, type BacktestStatisticsModel, Breakeven, type BreakevenAvailableNotification, type BreakevenCommit, type BreakevenCommitNotification, type BreakevenContract, type BreakevenData, Cache, type CancelScheduledCommit, type CandleData, type CandleInterval, type ClosePendingCommit, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IActivateScheduledCommitRow, type IBidData, type IBreakevenCommitRow, type ICandleData, type ICommitRow, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type INotificationUtils, type IOrderBookData, type IPartialLossCommitRow, type IPartialProfitCommitRow, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskSignalRow, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingParams, type ISizingParamsATR, type ISizingParamsFixedPercentage, type ISizingParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStorageSignalRow, type IStorageUtils, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IStrategyTickResultWaiting, type ITrailingStopCommitRow, type ITrailingTakeCommitRow, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, MethodContextService, type MetricStats, Notification, NotificationBacktest, type NotificationData, NotificationLive, type NotificationModel, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossAvailableNotification, type PartialLossCommit, type PartialLossCommitNotification, type PartialLossContract, type PartialProfitAvailableNotification, type PartialProfitCommit, type PartialProfitCommitNotification, type PartialProfitContract, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, type ProgressBacktestContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type SchedulePingContract, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, Storage, StorageBacktest, type StorageData, StorageLive, Strategy, type StrategyActionType, type StrategyCancelReason, type StrategyCloseReason, type StrategyCommitContract, type StrategyEvent, type StrategyStatisticsModel, type TMarkdownBase, type TNotificationUtilsCtor, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TStorageUtilsCtor, type TickEvent, type TrailingStopCommit, type TrailingStopCommitNotification, type TrailingTakeCommit, type TrailingTakeCommitNotification, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, warmCandles };
|
|
20845
|
+
export { ActionBase, type ActivateScheduledCommit, type ActivateScheduledCommitNotification, type ActivePingContract, Backtest, type BacktestStatisticsModel, Breakeven, type BreakevenAvailableNotification, type BreakevenCommit, type BreakevenCommitNotification, type BreakevenContract, type BreakevenData, Cache, type CancelScheduledCommit, type CandleData, type CandleInterval, type ClosePendingCommit, type ColumnConfig, type ColumnModel, Constant, type CriticalErrorNotification, type DoneContract, type EntityId, Exchange, ExecutionContextService, type FrameInterval, type GlobalConfig, Heat, type HeatmapStatisticsModel, type IActivateScheduledCommitRow, type IBidData, type IBreakevenCommitRow, type ICandleData, type ICommitRow, type IExchangeSchema, type IFrameSchema, type IHeatmapRow, type IMarkdownDumpOptions, type INotificationUtils, type IOrderBookData, type IPartialLossCommitRow, type IPartialProfitCommitRow, type IPersistBase, type IPositionSizeATRParams, type IPositionSizeFixedPercentageParams, type IPositionSizeKellyParams, type IPublicSignalRow, type IReportDumpOptions, type IRiskActivePosition, type IRiskCheckArgs, type IRiskSchema, type IRiskSignalRow, type IRiskValidation, type IRiskValidationFn, type IRiskValidationPayload, type IScheduledSignalCancelRow, type IScheduledSignalRow, type ISignalDto, type ISignalRow, type ISizingCalculateParams, type ISizingCalculateParamsATR, type ISizingCalculateParamsFixedPercentage, type ISizingCalculateParamsKelly, type ISizingParams, type ISizingParamsATR, type ISizingParamsFixedPercentage, type ISizingParamsKelly, type ISizingSchema, type ISizingSchemaATR, type ISizingSchemaFixedPercentage, type ISizingSchemaKelly, type IStorageSignalRow, type IStorageUtils, type IStrategyPnL, type IStrategyResult, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultCancelled, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, type IStrategyTickResultScheduled, type IStrategyTickResultWaiting, type ITrailingStopCommitRow, type ITrailingTakeCommitRow, type IWalkerResults, type IWalkerSchema, type IWalkerStrategyResult, type InfoErrorNotification, Live, type LiveStatisticsModel, Markdown, MarkdownFileBase, MarkdownFolderBase, type MarkdownName, MethodContextService, type MetricStats, Notification, NotificationBacktest, type NotificationData, NotificationLive, type NotificationModel, Partial$1 as Partial, type PartialData, type PartialEvent, type PartialLossAvailableNotification, type PartialLossCommit, type PartialLossCommitNotification, type PartialLossContract, type PartialProfitAvailableNotification, type PartialProfitCommit, type PartialProfitCommitNotification, type PartialProfitContract, type PartialStatisticsModel, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatisticsModel, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, type ProgressBacktestContract, type ProgressWalkerContract, Report, ReportBase, type ReportName, Risk, type RiskContract, type RiskData, type RiskEvent, type RiskRejectionNotification, type RiskStatisticsModel, Schedule, type ScheduleData, type SchedulePingContract, type ScheduleStatisticsModel, type ScheduledEvent, type SignalCancelledNotification, type SignalClosedNotification, type SignalData, type SignalInterval, type SignalOpenedNotification, type SignalScheduledNotification, Storage, StorageBacktest, type StorageData, StorageLive, Strategy, type StrategyActionType, type StrategyCancelReason, type StrategyCloseReason, type StrategyCommitContract, type StrategyEvent, type StrategyStatisticsModel, type TMarkdownBase, type TNotificationUtilsCtor, type TPersistBase, type TPersistBaseCtor, type TReportBase, type TStorageUtilsCtor, type TickEvent, type TrailingStopCommit, type TrailingStopCommitNotification, type TrailingTakeCommit, type TrailingTakeCommitNotification, type ValidationErrorNotification, Walker, type WalkerCompleteContract, type WalkerContract, type WalkerMetric, type SignalData$1 as WalkerSignalData, type WalkerStatisticsModel, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, warmCandles };
|