@tradejs/cli 2.0.0 → 2.0.2

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.
@@ -3114,7 +3114,7 @@ var import_userSettings = require("@tradejs/infra/userSettings");
3114
3114
  var HOUR_MS = 60 * 60 * 1e3;
3115
3115
  var DAY_MS2 = 24 * HOUR_MS;
3116
3116
  var DEFAULT_LOOKBACK_HOURS = 48;
3117
- var DEFAULT_INTERVALS = ["15m", "1h"];
3117
+ var SOURCE_INTERVALS = ["15m"];
3118
3118
  var coinalyzeIntervalMap = {
3119
3119
  "15m": "15min",
3120
3120
  "1h": "1hour"
@@ -3175,10 +3175,7 @@ var isDerivativesContextBackfillEnabled = (env) => {
3175
3175
  var shouldBackfillDerivativesContextForBacktest = (params) => !params.cacheOnly && (params.aiEnabled || params.mlEnabled) && isBacktestDerivativesContextEnabled();
3176
3176
  var shouldBackfillDerivativesContextForSignals = (params) => !params.cacheOnly && isSignalsDerivativesContextEnabled();
3177
3177
  var resolveDerivativesContextIntervals = () => {
3178
- const intervals = (0, import_indicators.normalizeDerivativesIntervals)(
3179
- process.env.DERIVATIVES_CONTEXT_INTERVALS
3180
- );
3181
- return intervals.length ? intervals : DEFAULT_INTERVALS;
3178
+ return [...SOURCE_INTERVALS];
3182
3179
  };
3183
3180
  var resolveDerivativesContextLookbackMs = () => {
3184
3181
  const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
@@ -3213,9 +3210,10 @@ var resolveDerivativesContextBackfillWindow = (params) => {
3213
3210
  };
3214
3211
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
3215
3212
  const intervalMs = derivativesIntervalMs(params.interval);
3213
+ const alignedToMs = params.closedOnly ? (0, import_indicators.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
3216
3214
  return {
3217
3215
  fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
3218
- toMs: Math.floor(params.toMs / intervalMs) * intervalMs,
3216
+ toMs: alignedToMs,
3219
3217
  intervalMs
3220
3218
  };
3221
3219
  };
@@ -3233,6 +3231,39 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
3233
3231
  }
3234
3232
  return null;
3235
3233
  };
3234
+ var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
3235
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
3236
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
3237
+ ranges: params.dataCoverageRanges,
3238
+ fromMs: params.fromMs,
3239
+ toMs: params.toMs,
3240
+ intervalMs: params.intervalMs
3241
+ });
3242
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
3243
+ edges: params.edges,
3244
+ fromMs: params.fromMs,
3245
+ toMs: params.toMs,
3246
+ intervalMs: params.intervalMs,
3247
+ refreshClosedTail: params.mode === "signals"
3248
+ });
3249
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
3250
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3251
+ interval: params.interval,
3252
+ fromMs: params.fromMs,
3253
+ toMs: params.toMs,
3254
+ nowMs: params.nowMs
3255
+ });
3256
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
3257
+ ranges: params.liquidationCoverageRanges,
3258
+ fromMs: confirmedLiquidationWindow.fromMs,
3259
+ toMs: confirmedLiquidationWindow.toMs,
3260
+ intervalMs: params.intervalMs
3261
+ }) : null;
3262
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
3263
+ (value) => value != null
3264
+ );
3265
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
3266
+ };
3236
3267
  var countBackfillWindows = (params) => {
3237
3268
  let count = 0;
3238
3269
  let cursor = params.fromMs;
@@ -3296,6 +3327,14 @@ var groupDerivativesContextMissingFetchRanges = (ranges) => {
3296
3327
  }
3297
3328
  return [...groups.entries()].sort(([a], [b]) => a - b).map(([fromMs, items]) => ({ fromMs, items }));
3298
3329
  };
3330
+ var getMissingClosedDerivativesSymbols = (params) => {
3331
+ const availableSymbols = new Set(
3332
+ params.rows.filter((row) => row.ts.getTime() === params.expectedTimestamp).map((row) => row.symbol.trim().toUpperCase())
3333
+ );
3334
+ return params.symbols.filter(
3335
+ (symbol) => !availableSymbols.has(symbol.trim().toUpperCase())
3336
+ );
3337
+ };
3299
3338
  var coverageKey = (params) => [
3300
3339
  params.symbol.trim().toUpperCase(),
3301
3340
  params.interval,
@@ -3560,7 +3599,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3560
3599
  mode,
3561
3600
  startMs,
3562
3601
  endMs,
3563
- preloadStartMs: params.preloadStartMs
3602
+ preloadStartMs: params.preloadStartMs,
3603
+ nowMs: Date.now()
3564
3604
  });
3565
3605
  if (safeEndMs <= fromMs) {
3566
3606
  return skippedBackfillResult2();
@@ -3575,7 +3615,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3575
3615
  ...resolveDerivativesContextIntervalBackfillWindow({
3576
3616
  fromMs,
3577
3617
  toMs: safeEndMs,
3578
- interval: interval2
3618
+ interval: interval2,
3619
+ closedOnly: mode === "signals"
3579
3620
  })
3580
3621
  })).filter((item) => item.toMs > item.fromMs);
3581
3622
  if (!intervalWindows.length) {
@@ -3593,15 +3634,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3593
3634
  );
3594
3635
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
3595
3636
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
3637
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
3638
+ const coverageNowMs = Date.now();
3596
3639
  await Promise.all(
3597
3640
  intervalWindows.map(async (window2) => {
3598
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
3599
- source: "coinalyze",
3600
- symbols,
3641
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3601
3642
  interval: window2.interval,
3602
3643
  fromMs: window2.fromMs,
3603
- toMs: window2.toMs
3644
+ toMs: window2.toMs,
3645
+ nowMs: coverageNowMs
3604
3646
  });
3647
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
3648
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
3649
+ source: "coinalyze",
3650
+ symbols,
3651
+ interval: window2.interval,
3652
+ fromMs: window2.fromMs,
3653
+ toMs: window2.toMs
3654
+ }),
3655
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
3656
+ source: "coinalyze",
3657
+ metric: "liquidation",
3658
+ symbols,
3659
+ interval: window2.interval,
3660
+ fromMs: confirmedLiquidationWindow.fromMs,
3661
+ toMs: confirmedLiquidationWindow.toMs
3662
+ }) : Promise.resolve([])
3663
+ ]);
3605
3664
  coverageKeysByInterval.set(
3606
3665
  window2.interval,
3607
3666
  new Set(
@@ -3623,6 +3682,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3623
3682
  rangesBySymbol.set(symbol, ranges);
3624
3683
  }
3625
3684
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3685
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3686
+ for (const row of liquidationCoverageRows) {
3687
+ const symbol = row.symbol.toUpperCase();
3688
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3689
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3690
+ liquidationRangesBySymbol.set(symbol, ranges);
3691
+ }
3692
+ liquidationCoverageRangesByInterval.set(
3693
+ window2.interval,
3694
+ liquidationRangesBySymbol
3695
+ );
3626
3696
  })
3627
3697
  );
3628
3698
  const cachedWindows = intervalWindows.reduce(
@@ -3634,7 +3704,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3634
3704
  }),
3635
3705
  0
3636
3706
  );
3637
- const allBackfillWindowsCached = intervalWindows.every((window2) => {
3707
+ const allBackfillWindowsCached = mode !== "signals" && intervalWindows.every((window2) => {
3638
3708
  const edgesBySymbol = edgesByInterval.get(window2.interval);
3639
3709
  const coverageKeys = coverageKeysByInterval.get(window2.interval);
3640
3710
  const backfillWindows = buildBackfillWindows({
@@ -3647,7 +3717,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3647
3717
  (symbol) => backfillWindows.every((backfillWindow) => {
3648
3718
  const normalizedSymbol = symbol.toUpperCase();
3649
3719
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
3650
- return hasDerivativesWindowCoverage({
3720
+ const dataCovered = hasDerivativesWindowCoverage({
3651
3721
  edges: edgesBySymbol?.get(normalizedSymbol),
3652
3722
  fromMs: backfillWindow.fromMs,
3653
3723
  toMs: backfillWindow.toMs
@@ -3664,6 +3734,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3664
3734
  toMs: backfillWindow.toMs
3665
3735
  })
3666
3736
  );
3737
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3738
+ interval: window2.interval,
3739
+ fromMs: backfillWindow.fromMs,
3740
+ toMs: backfillWindow.toMs,
3741
+ nowMs: coverageNowMs
3742
+ });
3743
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3744
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3745
+ fromMs: confirmedLiquidationWindow.fromMs,
3746
+ toMs: confirmedLiquidationWindow.toMs,
3747
+ intervalMs: window2.intervalMs
3748
+ }) == null;
3749
+ return dataCovered && liquidationCovered;
3667
3750
  })
3668
3751
  );
3669
3752
  });
@@ -3748,6 +3831,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3748
3831
  coverageKeysByInterval.set(interval2, coverageKeys);
3749
3832
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval2) ?? /* @__PURE__ */ new Map();
3750
3833
  coverageRangesByInterval.set(interval2, coverageRangesBySymbol);
3834
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval2) ?? /* @__PURE__ */ new Map();
3835
+ liquidationCoverageRangesByInterval.set(
3836
+ interval2,
3837
+ liquidationCoverageRangesBySymbol
3838
+ );
3751
3839
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3752
3840
  const batch = symbolBatches[batchIdx];
3753
3841
  let cursor = window2.fromMs;
@@ -3760,30 +3848,26 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3760
3848
  fromMs: cursor,
3761
3849
  toMs
3762
3850
  });
3763
- if (coverageKeys.has(key)) {
3764
- return null;
3765
- }
3766
3851
  const normalizedSymbol = item.symbol.toUpperCase();
3767
- const coverageFromMs = resolveDerivativesContextMissingCoverageFetchFromMs({
3768
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3769
- fromMs: cursor,
3770
- toMs,
3771
- intervalMs
3772
- });
3773
- if (coverageFromMs == null) {
3774
- return null;
3775
- }
3776
- const edges = edgesBySymbol.get(normalizedSymbol);
3777
- const edgesFromMs = resolveDerivativesContextMissingFetchFromMs({
3778
- edges,
3779
- fromMs: cursor,
3780
- toMs,
3781
- intervalMs
3782
- });
3783
- if (edgesFromMs == null) {
3784
- return null;
3785
- }
3786
- return { item, fromMs: Math.max(coverageFromMs, edgesFromMs) };
3852
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3853
+ {
3854
+ mode,
3855
+ interval: interval2,
3856
+ intervalMs,
3857
+ fromMs: cursor,
3858
+ toMs,
3859
+ nowMs: coverageNowMs,
3860
+ dataCoverageKeyExists: coverageKeys.has(key),
3861
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3862
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3863
+ edges: edgesBySymbol.get(normalizedSymbol)
3864
+ }
3865
+ );
3866
+ if (requiredFromMs == null) return null;
3867
+ return {
3868
+ item,
3869
+ fromMs: requiredFromMs
3870
+ };
3787
3871
  }).filter(
3788
3872
  (item) => item != null
3789
3873
  );
@@ -3797,85 +3881,150 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3797
3881
  const marketSymbols = missingBatch.map(
3798
3882
  (item) => item.marketSymbol
3799
3883
  );
3800
- const oiMap = await fetchMetricBatch({
3801
- endpoint: oiPath,
3802
- metric: "oi",
3803
- marketSymbols,
3804
- apiKey,
3805
- interval: interval2,
3806
- fromMs: group.fromMs,
3807
- toMs
3808
- });
3809
- const fundingMap = await fetchMetricBatch({
3810
- endpoint: fundingPath,
3811
- metric: "funding",
3812
- marketSymbols,
3813
- apiKey,
3814
- interval: interval2,
3815
- fromMs: group.fromMs,
3816
- toMs
3817
- });
3818
- const liqMap = await fetchMetricBatch({
3819
- endpoint: liqPath,
3820
- metric: "liq",
3821
- marketSymbols,
3822
- apiKey,
3823
- interval: interval2,
3824
- fromMs: group.fromMs,
3825
- toMs
3826
- });
3827
- const rows = missingBatch.flatMap((item) => {
3828
- const marketSymbol = item.marketSymbol.toUpperCase();
3829
- const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3830
- symbol: item.symbol,
3831
- oiRaw: oiMap.get(marketSymbol) ?? [],
3832
- fundingRaw: fundingMap.get(marketSymbol) ?? [],
3833
- liqRaw: liqMap.get(marketSymbol) ?? []
3884
+ const maxClosedBarAttempts = mode === "signals" ? asInt2(
3885
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_MAX_ATTEMPTS,
3886
+ 3
3887
+ ) : 1;
3888
+ const closedBarRetryDelayMs = asInt2(
3889
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_RETRY_DELAY_MS,
3890
+ 2e3
3891
+ );
3892
+ let rows = [];
3893
+ let missingClosedSymbols = [];
3894
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3895
+ for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3896
+ const oiMap = await fetchMetricBatch({
3897
+ endpoint: oiPath,
3898
+ metric: "oi",
3899
+ marketSymbols,
3900
+ apiKey,
3901
+ interval: interval2,
3902
+ fromMs: group.fromMs,
3903
+ toMs
3834
3904
  });
3835
- return (0, import_indicators.coinalyzePointsToRows)(points, interval2, "coinalyze");
3836
- });
3905
+ const fundingMap = await fetchMetricBatch({
3906
+ endpoint: fundingPath,
3907
+ metric: "funding",
3908
+ marketSymbols,
3909
+ apiKey,
3910
+ interval: interval2,
3911
+ fromMs: group.fromMs,
3912
+ toMs
3913
+ });
3914
+ liquidationRowsByMarket = await fetchMetricBatch({
3915
+ endpoint: liqPath,
3916
+ metric: "liq",
3917
+ marketSymbols,
3918
+ apiKey,
3919
+ interval: interval2,
3920
+ fromMs: group.fromMs,
3921
+ toMs
3922
+ });
3923
+ rows = missingBatch.flatMap((item) => {
3924
+ const marketSymbol = item.marketSymbol.toUpperCase();
3925
+ const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3926
+ symbol: item.symbol,
3927
+ oiRaw: oiMap.get(marketSymbol) ?? [],
3928
+ fundingRaw: fundingMap.get(marketSymbol) ?? [],
3929
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3930
+ });
3931
+ return (0, import_indicators.coinalyzePointsToRows)(points, interval2, "coinalyze");
3932
+ });
3933
+ missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
3934
+ symbols: missingBatch.map((item) => item.symbol),
3935
+ rows,
3936
+ expectedTimestamp: toMs
3937
+ }) : [];
3938
+ if (!missingClosedSymbols.length) break;
3939
+ if (attempt < maxClosedBarAttempts) {
3940
+ await (0, import_async.delay)(closedBarRetryDelayMs);
3941
+ }
3942
+ }
3943
+ if (missingClosedSymbols.length) {
3944
+ throw new Error(
3945
+ `Coinalyze closed ${interval2} bar ${new Date(toMs).toISOString()} unavailable for ${missingClosedSymbols.join(",")}`
3946
+ );
3947
+ }
3837
3948
  if (rows.length) {
3838
3949
  await (0, import_timescale2.upsertDerivatives)(rows);
3839
3950
  totalRows += rows.length;
3840
3951
  }
3841
- const rowsCountBySymbol = /* @__PURE__ */ new Map();
3842
- for (const row of rows) {
3843
- const symbol = row.symbol.toUpperCase();
3844
- rowsCountBySymbol.set(
3845
- symbol,
3846
- (rowsCountBySymbol.get(symbol) ?? 0) + 1
3847
- );
3848
- }
3849
- const coverageRows = missingBatch.map((item) => {
3850
- const normalizedSymbol = item.symbol.toUpperCase();
3851
- const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3852
- return {
3853
- source: "coinalyze",
3854
- symbol: item.symbol,
3855
- interval: interval2,
3856
- fromMs: cursor,
3857
- toMs,
3858
- rowsCount
3859
- };
3952
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3953
+ interval: interval2,
3954
+ fromMs: group.fromMs,
3955
+ toMs,
3956
+ nowMs: coverageNowMs
3860
3957
  });
3861
- await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
3862
- for (const coverageRow of coverageRows) {
3863
- const symbol = coverageRow.symbol.toUpperCase();
3864
- edgesBySymbol.set(
3865
- symbol,
3866
- extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
3867
- );
3868
- coverageKeys.add(
3869
- coverageKey({
3958
+ if (confirmedLiquidationWindow) {
3959
+ const metricCoverageRows = missingBatch.map((item) => {
3960
+ const marketSymbol = item.marketSymbol.toUpperCase();
3961
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3962
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
3963
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3964
+ );
3965
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3966
+ }).length;
3967
+ return {
3968
+ source: "coinalyze",
3969
+ metric: "liquidation",
3970
+ symbol: item.symbol,
3971
+ interval: interval2,
3972
+ fromMs: confirmedLiquidationWindow.fromMs,
3973
+ toMs: confirmedLiquidationWindow.toMs,
3974
+ eventRowsCount
3975
+ };
3976
+ });
3977
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3978
+ for (const coverageRow of metricCoverageRows) {
3979
+ const symbol = coverageRow.symbol.toUpperCase();
3980
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3981
+ ranges.push({
3982
+ fromMs: coverageRow.fromMs,
3983
+ toMs: coverageRow.toMs
3984
+ });
3985
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3986
+ }
3987
+ }
3988
+ if (mode === "backtest") {
3989
+ const rowsCountBySymbol = /* @__PURE__ */ new Map();
3990
+ for (const row of rows) {
3991
+ const symbol = row.symbol.toUpperCase();
3992
+ rowsCountBySymbol.set(
3870
3993
  symbol,
3994
+ (rowsCountBySymbol.get(symbol) ?? 0) + 1
3995
+ );
3996
+ }
3997
+ const coverageRows = missingBatch.map((item) => {
3998
+ const normalizedSymbol = item.symbol.toUpperCase();
3999
+ const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
4000
+ return {
4001
+ source: "coinalyze",
4002
+ symbol: item.symbol,
3871
4003
  interval: interval2,
3872
4004
  fromMs: cursor,
3873
- toMs
3874
- })
3875
- );
3876
- const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
3877
- coverageRanges.push({ fromMs: cursor, toMs });
3878
- coverageRangesBySymbol.set(symbol, coverageRanges);
4005
+ toMs,
4006
+ rowsCount
4007
+ };
4008
+ });
4009
+ await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
4010
+ for (const coverageRow of coverageRows) {
4011
+ const symbol = coverageRow.symbol.toUpperCase();
4012
+ edgesBySymbol.set(
4013
+ symbol,
4014
+ extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
4015
+ );
4016
+ coverageKeys.add(
4017
+ coverageKey({
4018
+ symbol,
4019
+ interval: interval2,
4020
+ fromMs: cursor,
4021
+ toMs
4022
+ })
4023
+ );
4024
+ const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
4025
+ coverageRanges.push({ fromMs: cursor, toMs });
4026
+ coverageRangesBySymbol.set(symbol, coverageRanges);
4027
+ }
3879
4028
  }
3880
4029
  }
3881
4030
  }