backtest-kit 9.6.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +23 -54
- package/build/index.mjs +24 -55
- package/package.json +1 -1
- package/types.d.ts +5 -6
package/build/index.cjs
CHANGED
|
@@ -36127,72 +36127,41 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
36127
36127
|
}
|
|
36128
36128
|
};
|
|
36129
36129
|
/**
|
|
36130
|
-
* Checks cached candle
|
|
36131
|
-
*
|
|
36130
|
+
* Checks cached candle presence via the persist adapter.
|
|
36131
|
+
* Issues one ranged read; adapter-side `hasValue` covers each expected timestamp,
|
|
36132
|
+
* so a single missing or unaligned candle yields a miss without loading the whole dataset.
|
|
36132
36133
|
*
|
|
36133
36134
|
* @param params - Validation parameters
|
|
36134
36135
|
*/
|
|
36135
36136
|
async function checkCandles(params) {
|
|
36136
|
-
const { symbol, exchangeName, interval, from, to
|
|
36137
|
+
const { symbol, exchangeName, interval, from, to } = params;
|
|
36137
36138
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
36138
36139
|
const step = INTERVAL_MINUTES$3[interval];
|
|
36139
36140
|
if (!step) {
|
|
36140
36141
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
36141
36142
|
}
|
|
36142
36143
|
const stepMs = step * MS_PER_MINUTE$3;
|
|
36143
|
-
const dir = path.join(baseDir, exchangeName, symbol, interval);
|
|
36144
36144
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36145
36145
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
36146
|
-
|
|
36147
|
-
|
|
36148
|
-
|
|
36149
|
-
|
|
36150
|
-
|
|
36151
|
-
|
|
36152
|
-
|
|
36153
|
-
|
|
36154
|
-
|
|
36155
|
-
|
|
36156
|
-
|
|
36157
|
-
|
|
36158
|
-
|
|
36159
|
-
|
|
36160
|
-
|
|
36161
|
-
|
|
36162
|
-
|
|
36163
|
-
|
|
36164
|
-
|
|
36165
|
-
}
|
|
36166
|
-
files.sort();
|
|
36167
|
-
let prevTimestamp = null;
|
|
36168
|
-
let prevName = null;
|
|
36169
|
-
PRINT_PROGRESS_FN(0, files.length, symbol, interval);
|
|
36170
|
-
for (let i = 0; i < files.length; i++) {
|
|
36171
|
-
const filePath = path.join(dir, files[i]);
|
|
36172
|
-
const raw = await fs.readFile(filePath, "utf-8");
|
|
36173
|
-
let candle;
|
|
36174
|
-
try {
|
|
36175
|
-
candle = JSON.parse(raw);
|
|
36176
|
-
}
|
|
36177
|
-
catch {
|
|
36178
|
-
throw new Error(`checkCandles: ${files[i]} contains invalid JSON`);
|
|
36179
|
-
}
|
|
36180
|
-
const { timestamp } = candle;
|
|
36181
|
-
const aligned = ALIGN_TO_INTERVAL_FN(timestamp, step);
|
|
36182
|
-
if (timestamp !== aligned) {
|
|
36183
|
-
throw new Error(`checkCandles: ${files[i]} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
|
|
36184
|
-
}
|
|
36185
|
-
if (prevTimestamp !== null) {
|
|
36186
|
-
const gap = timestamp - prevTimestamp;
|
|
36187
|
-
if (gap !== stepMs) {
|
|
36188
|
-
throw new Error(`checkCandles: gap between ${prevName} and ${files[i]} (actual=${gap}ms, expected=${stepMs}ms)`);
|
|
36189
|
-
}
|
|
36190
|
-
}
|
|
36191
|
-
prevTimestamp = timestamp;
|
|
36192
|
-
prevName = files[i];
|
|
36193
|
-
PRINT_PROGRESS_FN(i + 1, files.length, symbol, interval);
|
|
36194
|
-
}
|
|
36195
|
-
console.log(`checkCandles: OK ${files.length} candles ${symbol} ${interval}`);
|
|
36146
|
+
const totalCandles = Math.floor((toTs - fromTs) / stepMs);
|
|
36147
|
+
if (totalCandles <= 0) {
|
|
36148
|
+
throw new Error(`checkCandles: empty range [${fromTs}, ${toTs}) for ${symbol} ${interval}`);
|
|
36149
|
+
}
|
|
36150
|
+
let checked = 0;
|
|
36151
|
+
let currentSince = fromTs;
|
|
36152
|
+
PRINT_PROGRESS_FN(checked, totalCandles, symbol, interval);
|
|
36153
|
+
while (checked < totalCandles) {
|
|
36154
|
+
const chunkLimit = Math.min(totalCandles - checked, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
|
|
36155
|
+
const chunkUntil = currentSince + chunkLimit * stepMs;
|
|
36156
|
+
const candles = await PersistCandleAdapter.readCandlesData(symbol, interval, exchangeName, chunkLimit, currentSince, chunkUntil);
|
|
36157
|
+
if (!candles) {
|
|
36158
|
+
throw new Error(`checkCandles: cache miss for ${symbol} ${interval} [${currentSince}, ${chunkUntil})`);
|
|
36159
|
+
}
|
|
36160
|
+
checked += chunkLimit;
|
|
36161
|
+
currentSince = chunkUntil;
|
|
36162
|
+
PRINT_PROGRESS_FN(checked, totalCandles, symbol, interval);
|
|
36163
|
+
}
|
|
36164
|
+
console.log(`checkCandles: OK ${totalCandles} candles ${symbol} ${interval}`);
|
|
36196
36165
|
}
|
|
36197
36166
|
/**
|
|
36198
36167
|
* Pre-caches candles for a date range into persist storage.
|
package/build/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { scoped } from 'di-scoped';
|
|
|
3
3
|
import { singleton } from 'di-singleton';
|
|
4
4
|
import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, BehaviorSubject, waitForNext, singlerun } from 'functools-kit';
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
|
-
import fs__default
|
|
6
|
+
import fs__default from 'fs/promises';
|
|
7
7
|
import path, { join, dirname } from 'path';
|
|
8
8
|
import crypto from 'crypto';
|
|
9
9
|
import os from 'os';
|
|
@@ -36107,72 +36107,41 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
|
|
|
36107
36107
|
}
|
|
36108
36108
|
};
|
|
36109
36109
|
/**
|
|
36110
|
-
* Checks cached candle
|
|
36111
|
-
*
|
|
36110
|
+
* Checks cached candle presence via the persist adapter.
|
|
36111
|
+
* Issues one ranged read; adapter-side `hasValue` covers each expected timestamp,
|
|
36112
|
+
* so a single missing or unaligned candle yields a miss without loading the whole dataset.
|
|
36112
36113
|
*
|
|
36113
36114
|
* @param params - Validation parameters
|
|
36114
36115
|
*/
|
|
36115
36116
|
async function checkCandles(params) {
|
|
36116
|
-
const { symbol, exchangeName, interval, from, to
|
|
36117
|
+
const { symbol, exchangeName, interval, from, to } = params;
|
|
36117
36118
|
backtest.loggerService.info(CHECK_CANDLES_METHOD_NAME, params);
|
|
36118
36119
|
const step = INTERVAL_MINUTES$3[interval];
|
|
36119
36120
|
if (!step) {
|
|
36120
36121
|
throw new Error(`checkCandles: unsupported interval=${interval}`);
|
|
36121
36122
|
}
|
|
36122
36123
|
const stepMs = step * MS_PER_MINUTE$3;
|
|
36123
|
-
const dir = join(baseDir, exchangeName, symbol, interval);
|
|
36124
36124
|
const fromTs = ALIGN_TO_INTERVAL_FN(from.getTime(), step);
|
|
36125
36125
|
const toTs = ALIGN_TO_INTERVAL_FN(to.getTime(), step);
|
|
36126
|
-
|
|
36127
|
-
|
|
36128
|
-
|
|
36129
|
-
|
|
36130
|
-
|
|
36131
|
-
|
|
36132
|
-
|
|
36133
|
-
|
|
36134
|
-
|
|
36135
|
-
|
|
36136
|
-
|
|
36137
|
-
|
|
36138
|
-
|
|
36139
|
-
|
|
36140
|
-
|
|
36141
|
-
|
|
36142
|
-
|
|
36143
|
-
|
|
36144
|
-
|
|
36145
|
-
}
|
|
36146
|
-
files.sort();
|
|
36147
|
-
let prevTimestamp = null;
|
|
36148
|
-
let prevName = null;
|
|
36149
|
-
PRINT_PROGRESS_FN(0, files.length, symbol, interval);
|
|
36150
|
-
for (let i = 0; i < files.length; i++) {
|
|
36151
|
-
const filePath = join(dir, files[i]);
|
|
36152
|
-
const raw = await readFile(filePath, "utf-8");
|
|
36153
|
-
let candle;
|
|
36154
|
-
try {
|
|
36155
|
-
candle = JSON.parse(raw);
|
|
36156
|
-
}
|
|
36157
|
-
catch {
|
|
36158
|
-
throw new Error(`checkCandles: ${files[i]} contains invalid JSON`);
|
|
36159
|
-
}
|
|
36160
|
-
const { timestamp } = candle;
|
|
36161
|
-
const aligned = ALIGN_TO_INTERVAL_FN(timestamp, step);
|
|
36162
|
-
if (timestamp !== aligned) {
|
|
36163
|
-
throw new Error(`checkCandles: ${files[i]} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
|
|
36164
|
-
}
|
|
36165
|
-
if (prevTimestamp !== null) {
|
|
36166
|
-
const gap = timestamp - prevTimestamp;
|
|
36167
|
-
if (gap !== stepMs) {
|
|
36168
|
-
throw new Error(`checkCandles: gap between ${prevName} and ${files[i]} (actual=${gap}ms, expected=${stepMs}ms)`);
|
|
36169
|
-
}
|
|
36170
|
-
}
|
|
36171
|
-
prevTimestamp = timestamp;
|
|
36172
|
-
prevName = files[i];
|
|
36173
|
-
PRINT_PROGRESS_FN(i + 1, files.length, symbol, interval);
|
|
36174
|
-
}
|
|
36175
|
-
console.log(`checkCandles: OK ${files.length} candles ${symbol} ${interval}`);
|
|
36126
|
+
const totalCandles = Math.floor((toTs - fromTs) / stepMs);
|
|
36127
|
+
if (totalCandles <= 0) {
|
|
36128
|
+
throw new Error(`checkCandles: empty range [${fromTs}, ${toTs}) for ${symbol} ${interval}`);
|
|
36129
|
+
}
|
|
36130
|
+
let checked = 0;
|
|
36131
|
+
let currentSince = fromTs;
|
|
36132
|
+
PRINT_PROGRESS_FN(checked, totalCandles, symbol, interval);
|
|
36133
|
+
while (checked < totalCandles) {
|
|
36134
|
+
const chunkLimit = Math.min(totalCandles - checked, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
|
|
36135
|
+
const chunkUntil = currentSince + chunkLimit * stepMs;
|
|
36136
|
+
const candles = await PersistCandleAdapter.readCandlesData(symbol, interval, exchangeName, chunkLimit, currentSince, chunkUntil);
|
|
36137
|
+
if (!candles) {
|
|
36138
|
+
throw new Error(`checkCandles: cache miss for ${symbol} ${interval} [${currentSince}, ${chunkUntil})`);
|
|
36139
|
+
}
|
|
36140
|
+
checked += chunkLimit;
|
|
36141
|
+
currentSince = chunkUntil;
|
|
36142
|
+
PRINT_PROGRESS_FN(checked, totalCandles, symbol, interval);
|
|
36143
|
+
}
|
|
36144
|
+
console.log(`checkCandles: OK ${totalCandles} candles ${symbol} ${interval}`);
|
|
36176
36145
|
}
|
|
36177
36146
|
/**
|
|
36178
36147
|
* Pre-caches candles for a date range into persist storage.
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -4406,8 +4406,8 @@ interface ICacheCandlesParams {
|
|
|
4406
4406
|
to: Date;
|
|
4407
4407
|
}
|
|
4408
4408
|
/**
|
|
4409
|
-
* Parameters for validating cached candle
|
|
4410
|
-
*
|
|
4409
|
+
* Parameters for validating cached candle presence.
|
|
4410
|
+
* Queries persist storage adapter without scanning files.
|
|
4411
4411
|
*/
|
|
4412
4412
|
interface ICheckCandlesParams {
|
|
4413
4413
|
/** Trading pair symbol (e.g., "BTCUSDT") */
|
|
@@ -4420,12 +4420,11 @@ interface ICheckCandlesParams {
|
|
|
4420
4420
|
from: Date;
|
|
4421
4421
|
/** End date of the validation range (inclusive) */
|
|
4422
4422
|
to: Date;
|
|
4423
|
-
/** Base directory of candle persist storage (default: "./dump/data/candle") */
|
|
4424
|
-
baseDir?: string;
|
|
4425
4423
|
}
|
|
4426
4424
|
/**
|
|
4427
|
-
* Checks cached candle
|
|
4428
|
-
*
|
|
4425
|
+
* Checks cached candle presence via the persist adapter.
|
|
4426
|
+
* Issues one ranged read; adapter-side `hasValue` covers each expected timestamp,
|
|
4427
|
+
* so a single missing or unaligned candle yields a miss without loading the whole dataset.
|
|
4429
4428
|
*
|
|
4430
4429
|
* @param params - Validation parameters
|
|
4431
4430
|
*/
|