backtest-kit 3.0.13 → 3.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -914,11 +914,13 @@ class PersistBase {
914
914
  entityName: this.entityName,
915
915
  });
916
916
  try {
917
- const files = await fs.readdir(this._directory);
918
- const entityIds = files
919
- .filter((file) => file.endsWith(".json"))
920
- .map((file) => file.slice(0, -5))
921
- .sort((a, b) => a.localeCompare(b, undefined, {
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
  }));
@@ -26807,7 +26809,7 @@ 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
26814
  const step = INTERVAL_MINUTES$1[interval];
26813
26815
  if (!step) {
@@ -26815,51 +26817,58 @@ async function checkCandles(params) {
26815
26817
  }
26816
26818
  const stepMs = step * MS_PER_MINUTE$1;
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
- // First pass: count files via async iterator (no memory allocation for file list)
26825
- let totalFiles = 0;
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() && entry.name.endsWith(".json")) {
26828
- totalFiles++;
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 (totalFiles === 0) {
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
- // Second pass: check each file via async iterator with progress
26835
- let checkd = 0;
26842
+ files.sort();
26836
26843
  let prevTimestamp = null;
26837
26844
  let prevName = null;
26838
- PRINT_PROGRESS_FN(checkd, totalFiles, symbol, interval);
26839
- for await (const entry of await fs.opendir(dir)) {
26840
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
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
- const candle = JSON.parse(raw);
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: ${entry.name} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
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 ${entry.name} (actual=${gap}ms, expected=${stepMs}ms)`);
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 = entry.name;
26859
- checkd++;
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 ${totalFiles} candles ${symbol} ${interval}`);
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.
package/build/index.mjs CHANGED
@@ -894,11 +894,13 @@ class PersistBase {
894
894
  entityName: this.entityName,
895
895
  });
896
896
  try {
897
- const files = await fs__default.readdir(this._directory);
898
- const entityIds = files
899
- .filter((file) => file.endsWith(".json"))
900
- .map((file) => file.slice(0, -5))
901
- .sort((a, b) => a.localeCompare(b, undefined, {
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
  }));
@@ -26787,7 +26789,7 @@ 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
26794
  const step = INTERVAL_MINUTES$1[interval];
26793
26795
  if (!step) {
@@ -26795,51 +26797,58 @@ async function checkCandles(params) {
26795
26797
  }
26796
26798
  const stepMs = step * MS_PER_MINUTE$1;
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
- // First pass: count files via async iterator (no memory allocation for file list)
26805
- let totalFiles = 0;
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() && entry.name.endsWith(".json")) {
26808
- totalFiles++;
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 (totalFiles === 0) {
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
- // Second pass: check each file via async iterator with progress
26815
- let checkd = 0;
26822
+ files.sort();
26816
26823
  let prevTimestamp = null;
26817
26824
  let prevName = null;
26818
- PRINT_PROGRESS_FN(checkd, totalFiles, symbol, interval);
26819
- for await (const entry of await opendir(dir)) {
26820
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
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
- const candle = JSON.parse(raw);
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: ${entry.name} timestamp not aligned to ${interval} boundary (actual=${timestamp}, expected=${aligned})`);
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 ${entry.name} (actual=${gap}ms, expected=${stepMs}ms)`);
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 = entry.name;
26839
- checkd++;
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 ${totalFiles} candles ${symbol} ${interval}`);
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "3.0.13",
3
+ "version": "3.0.14",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
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
  }