@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.
@@ -3109,7 +3109,7 @@ var import_userSettings = require("@tradejs/infra/userSettings");
3109
3109
  var HOUR_MS = 60 * 60 * 1e3;
3110
3110
  var DAY_MS2 = 24 * HOUR_MS;
3111
3111
  var DEFAULT_LOOKBACK_HOURS = 48;
3112
- var DEFAULT_INTERVALS = ["15m", "1h"];
3112
+ var SOURCE_INTERVALS = ["15m"];
3113
3113
  var coinalyzeIntervalMap = {
3114
3114
  "15m": "15min",
3115
3115
  "1h": "1hour"
@@ -3170,10 +3170,7 @@ var isDerivativesContextBackfillEnabled = (env) => {
3170
3170
  var shouldBackfillDerivativesContextForBacktest = (params) => !params.cacheOnly && (params.aiEnabled || params.mlEnabled) && isBacktestDerivativesContextEnabled();
3171
3171
  var shouldBackfillDerivativesContextForSignals = (params) => !params.cacheOnly && isSignalsDerivativesContextEnabled();
3172
3172
  var resolveDerivativesContextIntervals = () => {
3173
- const intervals = (0, import_indicators.normalizeDerivativesIntervals)(
3174
- process.env.DERIVATIVES_CONTEXT_INTERVALS
3175
- );
3176
- return intervals.length ? intervals : DEFAULT_INTERVALS;
3173
+ return [...SOURCE_INTERVALS];
3177
3174
  };
3178
3175
  var resolveDerivativesContextLookbackMs = () => {
3179
3176
  const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
@@ -3208,9 +3205,10 @@ var resolveDerivativesContextBackfillWindow = (params) => {
3208
3205
  };
3209
3206
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
3210
3207
  const intervalMs = derivativesIntervalMs(params.interval);
3208
+ const alignedToMs = params.closedOnly ? (0, import_indicators.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
3211
3209
  return {
3212
3210
  fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
3213
- toMs: Math.floor(params.toMs / intervalMs) * intervalMs,
3211
+ toMs: alignedToMs,
3214
3212
  intervalMs
3215
3213
  };
3216
3214
  };
@@ -3228,6 +3226,39 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
3228
3226
  }
3229
3227
  return null;
3230
3228
  };
3229
+ var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
3230
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
3231
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
3232
+ ranges: params.dataCoverageRanges,
3233
+ fromMs: params.fromMs,
3234
+ toMs: params.toMs,
3235
+ intervalMs: params.intervalMs
3236
+ });
3237
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
3238
+ edges: params.edges,
3239
+ fromMs: params.fromMs,
3240
+ toMs: params.toMs,
3241
+ intervalMs: params.intervalMs,
3242
+ refreshClosedTail: params.mode === "signals"
3243
+ });
3244
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
3245
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3246
+ interval: params.interval,
3247
+ fromMs: params.fromMs,
3248
+ toMs: params.toMs,
3249
+ nowMs: params.nowMs
3250
+ });
3251
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
3252
+ ranges: params.liquidationCoverageRanges,
3253
+ fromMs: confirmedLiquidationWindow.fromMs,
3254
+ toMs: confirmedLiquidationWindow.toMs,
3255
+ intervalMs: params.intervalMs
3256
+ }) : null;
3257
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
3258
+ (value) => value != null
3259
+ );
3260
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
3261
+ };
3231
3262
  var countBackfillWindows = (params) => {
3232
3263
  let count = 0;
3233
3264
  let cursor = params.fromMs;
@@ -3291,6 +3322,14 @@ var groupDerivativesContextMissingFetchRanges = (ranges) => {
3291
3322
  }
3292
3323
  return [...groups.entries()].sort(([a], [b]) => a - b).map(([fromMs, items]) => ({ fromMs, items }));
3293
3324
  };
3325
+ var getMissingClosedDerivativesSymbols = (params) => {
3326
+ const availableSymbols = new Set(
3327
+ params.rows.filter((row) => row.ts.getTime() === params.expectedTimestamp).map((row) => row.symbol.trim().toUpperCase())
3328
+ );
3329
+ return params.symbols.filter(
3330
+ (symbol) => !availableSymbols.has(symbol.trim().toUpperCase())
3331
+ );
3332
+ };
3294
3333
  var coverageKey = (params) => [
3295
3334
  params.symbol.trim().toUpperCase(),
3296
3335
  params.interval,
@@ -3555,7 +3594,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3555
3594
  mode,
3556
3595
  startMs,
3557
3596
  endMs,
3558
- preloadStartMs: params.preloadStartMs
3597
+ preloadStartMs: params.preloadStartMs,
3598
+ nowMs: Date.now()
3559
3599
  });
3560
3600
  if (safeEndMs <= fromMs) {
3561
3601
  return skippedBackfillResult2();
@@ -3570,7 +3610,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3570
3610
  ...resolveDerivativesContextIntervalBackfillWindow({
3571
3611
  fromMs,
3572
3612
  toMs: safeEndMs,
3573
- interval
3613
+ interval,
3614
+ closedOnly: mode === "signals"
3574
3615
  })
3575
3616
  })).filter((item) => item.toMs > item.fromMs);
3576
3617
  if (!intervalWindows.length) {
@@ -3588,15 +3629,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3588
3629
  );
3589
3630
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
3590
3631
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
3632
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
3633
+ const coverageNowMs = Date.now();
3591
3634
  await Promise.all(
3592
3635
  intervalWindows.map(async (window2) => {
3593
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
3594
- source: "coinalyze",
3595
- symbols,
3636
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3596
3637
  interval: window2.interval,
3597
3638
  fromMs: window2.fromMs,
3598
- toMs: window2.toMs
3639
+ toMs: window2.toMs,
3640
+ nowMs: coverageNowMs
3599
3641
  });
3642
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
3643
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
3644
+ source: "coinalyze",
3645
+ symbols,
3646
+ interval: window2.interval,
3647
+ fromMs: window2.fromMs,
3648
+ toMs: window2.toMs
3649
+ }),
3650
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
3651
+ source: "coinalyze",
3652
+ metric: "liquidation",
3653
+ symbols,
3654
+ interval: window2.interval,
3655
+ fromMs: confirmedLiquidationWindow.fromMs,
3656
+ toMs: confirmedLiquidationWindow.toMs
3657
+ }) : Promise.resolve([])
3658
+ ]);
3600
3659
  coverageKeysByInterval.set(
3601
3660
  window2.interval,
3602
3661
  new Set(
@@ -3618,6 +3677,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3618
3677
  rangesBySymbol.set(symbol, ranges);
3619
3678
  }
3620
3679
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3680
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3681
+ for (const row of liquidationCoverageRows) {
3682
+ const symbol = row.symbol.toUpperCase();
3683
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3684
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3685
+ liquidationRangesBySymbol.set(symbol, ranges);
3686
+ }
3687
+ liquidationCoverageRangesByInterval.set(
3688
+ window2.interval,
3689
+ liquidationRangesBySymbol
3690
+ );
3621
3691
  })
3622
3692
  );
3623
3693
  const cachedWindows = intervalWindows.reduce(
@@ -3629,7 +3699,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3629
3699
  }),
3630
3700
  0
3631
3701
  );
3632
- const allBackfillWindowsCached = intervalWindows.every((window2) => {
3702
+ const allBackfillWindowsCached = mode !== "signals" && intervalWindows.every((window2) => {
3633
3703
  const edgesBySymbol = edgesByInterval.get(window2.interval);
3634
3704
  const coverageKeys = coverageKeysByInterval.get(window2.interval);
3635
3705
  const backfillWindows = buildBackfillWindows({
@@ -3642,7 +3712,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3642
3712
  (symbol) => backfillWindows.every((backfillWindow) => {
3643
3713
  const normalizedSymbol = symbol.toUpperCase();
3644
3714
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
3645
- return hasDerivativesWindowCoverage({
3715
+ const dataCovered = hasDerivativesWindowCoverage({
3646
3716
  edges: edgesBySymbol?.get(normalizedSymbol),
3647
3717
  fromMs: backfillWindow.fromMs,
3648
3718
  toMs: backfillWindow.toMs
@@ -3659,6 +3729,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3659
3729
  toMs: backfillWindow.toMs
3660
3730
  })
3661
3731
  );
3732
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3733
+ interval: window2.interval,
3734
+ fromMs: backfillWindow.fromMs,
3735
+ toMs: backfillWindow.toMs,
3736
+ nowMs: coverageNowMs
3737
+ });
3738
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3739
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3740
+ fromMs: confirmedLiquidationWindow.fromMs,
3741
+ toMs: confirmedLiquidationWindow.toMs,
3742
+ intervalMs: window2.intervalMs
3743
+ }) == null;
3744
+ return dataCovered && liquidationCovered;
3662
3745
  })
3663
3746
  );
3664
3747
  });
@@ -3743,6 +3826,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3743
3826
  coverageKeysByInterval.set(interval, coverageKeys);
3744
3827
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3745
3828
  coverageRangesByInterval.set(interval, coverageRangesBySymbol);
3829
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3830
+ liquidationCoverageRangesByInterval.set(
3831
+ interval,
3832
+ liquidationCoverageRangesBySymbol
3833
+ );
3746
3834
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3747
3835
  const batch = symbolBatches[batchIdx];
3748
3836
  let cursor = window2.fromMs;
@@ -3755,30 +3843,26 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3755
3843
  fromMs: cursor,
3756
3844
  toMs
3757
3845
  });
3758
- if (coverageKeys.has(key)) {
3759
- return null;
3760
- }
3761
3846
  const normalizedSymbol = item.symbol.toUpperCase();
3762
- const coverageFromMs = resolveDerivativesContextMissingCoverageFetchFromMs({
3763
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3764
- fromMs: cursor,
3765
- toMs,
3766
- intervalMs
3767
- });
3768
- if (coverageFromMs == null) {
3769
- return null;
3770
- }
3771
- const edges = edgesBySymbol.get(normalizedSymbol);
3772
- const edgesFromMs = resolveDerivativesContextMissingFetchFromMs({
3773
- edges,
3774
- fromMs: cursor,
3775
- toMs,
3776
- intervalMs
3777
- });
3778
- if (edgesFromMs == null) {
3779
- return null;
3780
- }
3781
- return { item, fromMs: Math.max(coverageFromMs, edgesFromMs) };
3847
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3848
+ {
3849
+ mode,
3850
+ interval,
3851
+ intervalMs,
3852
+ fromMs: cursor,
3853
+ toMs,
3854
+ nowMs: coverageNowMs,
3855
+ dataCoverageKeyExists: coverageKeys.has(key),
3856
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3857
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3858
+ edges: edgesBySymbol.get(normalizedSymbol)
3859
+ }
3860
+ );
3861
+ if (requiredFromMs == null) return null;
3862
+ return {
3863
+ item,
3864
+ fromMs: requiredFromMs
3865
+ };
3782
3866
  }).filter(
3783
3867
  (item) => item != null
3784
3868
  );
@@ -3792,85 +3876,150 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3792
3876
  const marketSymbols = missingBatch.map(
3793
3877
  (item) => item.marketSymbol
3794
3878
  );
3795
- const oiMap = await fetchMetricBatch({
3796
- endpoint: oiPath,
3797
- metric: "oi",
3798
- marketSymbols,
3799
- apiKey,
3800
- interval,
3801
- fromMs: group.fromMs,
3802
- toMs
3803
- });
3804
- const fundingMap = await fetchMetricBatch({
3805
- endpoint: fundingPath,
3806
- metric: "funding",
3807
- marketSymbols,
3808
- apiKey,
3809
- interval,
3810
- fromMs: group.fromMs,
3811
- toMs
3812
- });
3813
- const liqMap = await fetchMetricBatch({
3814
- endpoint: liqPath,
3815
- metric: "liq",
3816
- marketSymbols,
3817
- apiKey,
3818
- interval,
3819
- fromMs: group.fromMs,
3820
- toMs
3821
- });
3822
- const rows = missingBatch.flatMap((item) => {
3823
- const marketSymbol = item.marketSymbol.toUpperCase();
3824
- const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3825
- symbol: item.symbol,
3826
- oiRaw: oiMap.get(marketSymbol) ?? [],
3827
- fundingRaw: fundingMap.get(marketSymbol) ?? [],
3828
- liqRaw: liqMap.get(marketSymbol) ?? []
3879
+ const maxClosedBarAttempts = mode === "signals" ? asInt2(
3880
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_MAX_ATTEMPTS,
3881
+ 3
3882
+ ) : 1;
3883
+ const closedBarRetryDelayMs = asInt2(
3884
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_RETRY_DELAY_MS,
3885
+ 2e3
3886
+ );
3887
+ let rows = [];
3888
+ let missingClosedSymbols = [];
3889
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3890
+ for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3891
+ const oiMap = await fetchMetricBatch({
3892
+ endpoint: oiPath,
3893
+ metric: "oi",
3894
+ marketSymbols,
3895
+ apiKey,
3896
+ interval,
3897
+ fromMs: group.fromMs,
3898
+ toMs
3829
3899
  });
3830
- return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3831
- });
3900
+ const fundingMap = await fetchMetricBatch({
3901
+ endpoint: fundingPath,
3902
+ metric: "funding",
3903
+ marketSymbols,
3904
+ apiKey,
3905
+ interval,
3906
+ fromMs: group.fromMs,
3907
+ toMs
3908
+ });
3909
+ liquidationRowsByMarket = await fetchMetricBatch({
3910
+ endpoint: liqPath,
3911
+ metric: "liq",
3912
+ marketSymbols,
3913
+ apiKey,
3914
+ interval,
3915
+ fromMs: group.fromMs,
3916
+ toMs
3917
+ });
3918
+ rows = missingBatch.flatMap((item) => {
3919
+ const marketSymbol = item.marketSymbol.toUpperCase();
3920
+ const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3921
+ symbol: item.symbol,
3922
+ oiRaw: oiMap.get(marketSymbol) ?? [],
3923
+ fundingRaw: fundingMap.get(marketSymbol) ?? [],
3924
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3925
+ });
3926
+ return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3927
+ });
3928
+ missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
3929
+ symbols: missingBatch.map((item) => item.symbol),
3930
+ rows,
3931
+ expectedTimestamp: toMs
3932
+ }) : [];
3933
+ if (!missingClosedSymbols.length) break;
3934
+ if (attempt < maxClosedBarAttempts) {
3935
+ await (0, import_async.delay)(closedBarRetryDelayMs);
3936
+ }
3937
+ }
3938
+ if (missingClosedSymbols.length) {
3939
+ throw new Error(
3940
+ `Coinalyze closed ${interval} bar ${new Date(toMs).toISOString()} unavailable for ${missingClosedSymbols.join(",")}`
3941
+ );
3942
+ }
3832
3943
  if (rows.length) {
3833
3944
  await (0, import_timescale2.upsertDerivatives)(rows);
3834
3945
  totalRows += rows.length;
3835
3946
  }
3836
- const rowsCountBySymbol = /* @__PURE__ */ new Map();
3837
- for (const row of rows) {
3838
- const symbol = row.symbol.toUpperCase();
3839
- rowsCountBySymbol.set(
3840
- symbol,
3841
- (rowsCountBySymbol.get(symbol) ?? 0) + 1
3842
- );
3843
- }
3844
- const coverageRows = missingBatch.map((item) => {
3845
- const normalizedSymbol = item.symbol.toUpperCase();
3846
- const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3847
- return {
3848
- source: "coinalyze",
3849
- symbol: item.symbol,
3850
- interval,
3851
- fromMs: cursor,
3852
- toMs,
3853
- rowsCount
3854
- };
3947
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3948
+ interval,
3949
+ fromMs: group.fromMs,
3950
+ toMs,
3951
+ nowMs: coverageNowMs
3855
3952
  });
3856
- await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
3857
- for (const coverageRow of coverageRows) {
3858
- const symbol = coverageRow.symbol.toUpperCase();
3859
- edgesBySymbol.set(
3860
- symbol,
3861
- extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
3862
- );
3863
- coverageKeys.add(
3864
- coverageKey({
3953
+ if (confirmedLiquidationWindow) {
3954
+ const metricCoverageRows = missingBatch.map((item) => {
3955
+ const marketSymbol = item.marketSymbol.toUpperCase();
3956
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3957
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
3958
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3959
+ );
3960
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3961
+ }).length;
3962
+ return {
3963
+ source: "coinalyze",
3964
+ metric: "liquidation",
3965
+ symbol: item.symbol,
3966
+ interval,
3967
+ fromMs: confirmedLiquidationWindow.fromMs,
3968
+ toMs: confirmedLiquidationWindow.toMs,
3969
+ eventRowsCount
3970
+ };
3971
+ });
3972
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3973
+ for (const coverageRow of metricCoverageRows) {
3974
+ const symbol = coverageRow.symbol.toUpperCase();
3975
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3976
+ ranges.push({
3977
+ fromMs: coverageRow.fromMs,
3978
+ toMs: coverageRow.toMs
3979
+ });
3980
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3981
+ }
3982
+ }
3983
+ if (mode === "backtest") {
3984
+ const rowsCountBySymbol = /* @__PURE__ */ new Map();
3985
+ for (const row of rows) {
3986
+ const symbol = row.symbol.toUpperCase();
3987
+ rowsCountBySymbol.set(
3865
3988
  symbol,
3989
+ (rowsCountBySymbol.get(symbol) ?? 0) + 1
3990
+ );
3991
+ }
3992
+ const coverageRows = missingBatch.map((item) => {
3993
+ const normalizedSymbol = item.symbol.toUpperCase();
3994
+ const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3995
+ return {
3996
+ source: "coinalyze",
3997
+ symbol: item.symbol,
3866
3998
  interval,
3867
3999
  fromMs: cursor,
3868
- toMs
3869
- })
3870
- );
3871
- const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
3872
- coverageRanges.push({ fromMs: cursor, toMs });
3873
- coverageRangesBySymbol.set(symbol, coverageRanges);
4000
+ toMs,
4001
+ rowsCount
4002
+ };
4003
+ });
4004
+ await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
4005
+ for (const coverageRow of coverageRows) {
4006
+ const symbol = coverageRow.symbol.toUpperCase();
4007
+ edgesBySymbol.set(
4008
+ symbol,
4009
+ extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
4010
+ );
4011
+ coverageKeys.add(
4012
+ coverageKey({
4013
+ symbol,
4014
+ interval,
4015
+ fromMs: cursor,
4016
+ toMs
4017
+ })
4018
+ );
4019
+ const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
4020
+ coverageRanges.push({ fromMs: cursor, toMs });
4021
+ coverageRangesBySymbol.set(symbol, coverageRanges);
4022
+ }
3874
4023
  }
3875
4024
  }
3876
4025
  }