@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.
@@ -2610,7 +2610,7 @@ var import_userSettings = require("@tradejs/infra/userSettings");
2610
2610
  var HOUR_MS = 60 * 60 * 1e3;
2611
2611
  var DAY_MS2 = 24 * HOUR_MS;
2612
2612
  var DEFAULT_LOOKBACK_HOURS = 48;
2613
- var DEFAULT_INTERVALS = ["15m", "1h"];
2613
+ var SOURCE_INTERVALS = ["15m"];
2614
2614
  var coinalyzeIntervalMap = {
2615
2615
  "15m": "15min",
2616
2616
  "1h": "1hour"
@@ -2671,10 +2671,7 @@ var isDerivativesContextBackfillEnabled = (env) => {
2671
2671
  var shouldBackfillDerivativesContextForBacktest = (params) => !params.cacheOnly && (params.aiEnabled || params.mlEnabled) && isBacktestDerivativesContextEnabled();
2672
2672
  var shouldBackfillDerivativesContextForSignals = (params) => !params.cacheOnly && isSignalsDerivativesContextEnabled();
2673
2673
  var resolveDerivativesContextIntervals = () => {
2674
- const intervals = (0, import_indicators2.normalizeDerivativesIntervals)(
2675
- process.env.DERIVATIVES_CONTEXT_INTERVALS
2676
- );
2677
- return intervals.length ? intervals : DEFAULT_INTERVALS;
2674
+ return [...SOURCE_INTERVALS];
2678
2675
  };
2679
2676
  var resolveDerivativesContextLookbackMs = () => {
2680
2677
  const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
@@ -2709,9 +2706,10 @@ var resolveDerivativesContextBackfillWindow = (params) => {
2709
2706
  };
2710
2707
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
2711
2708
  const intervalMs2 = derivativesIntervalMs(params.interval);
2709
+ const alignedToMs = params.closedOnly ? (0, import_indicators2.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs2) * intervalMs2;
2712
2710
  return {
2713
2711
  fromMs: Math.floor(params.fromMs / intervalMs2) * intervalMs2,
2714
- toMs: Math.floor(params.toMs / intervalMs2) * intervalMs2,
2712
+ toMs: alignedToMs,
2715
2713
  intervalMs: intervalMs2
2716
2714
  };
2717
2715
  };
@@ -2729,6 +2727,39 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
2729
2727
  }
2730
2728
  return null;
2731
2729
  };
2730
+ var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
2731
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
2732
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
2733
+ ranges: params.dataCoverageRanges,
2734
+ fromMs: params.fromMs,
2735
+ toMs: params.toMs,
2736
+ intervalMs: params.intervalMs
2737
+ });
2738
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
2739
+ edges: params.edges,
2740
+ fromMs: params.fromMs,
2741
+ toMs: params.toMs,
2742
+ intervalMs: params.intervalMs,
2743
+ refreshClosedTail: params.mode === "signals"
2744
+ });
2745
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
2746
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2747
+ interval: params.interval,
2748
+ fromMs: params.fromMs,
2749
+ toMs: params.toMs,
2750
+ nowMs: params.nowMs
2751
+ });
2752
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
2753
+ ranges: params.liquidationCoverageRanges,
2754
+ fromMs: confirmedLiquidationWindow.fromMs,
2755
+ toMs: confirmedLiquidationWindow.toMs,
2756
+ intervalMs: params.intervalMs
2757
+ }) : null;
2758
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
2759
+ (value) => value != null
2760
+ );
2761
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
2762
+ };
2732
2763
  var countBackfillWindows = (params) => {
2733
2764
  let count = 0;
2734
2765
  let cursor = params.fromMs;
@@ -2792,6 +2823,14 @@ var groupDerivativesContextMissingFetchRanges = (ranges) => {
2792
2823
  }
2793
2824
  return [...groups.entries()].sort(([a], [b]) => a - b).map(([fromMs, items]) => ({ fromMs, items }));
2794
2825
  };
2826
+ var getMissingClosedDerivativesSymbols = (params) => {
2827
+ const availableSymbols = new Set(
2828
+ params.rows.filter((row) => row.ts.getTime() === params.expectedTimestamp).map((row) => row.symbol.trim().toUpperCase())
2829
+ );
2830
+ return params.symbols.filter(
2831
+ (symbol) => !availableSymbols.has(symbol.trim().toUpperCase())
2832
+ );
2833
+ };
2795
2834
  var coverageKey = (params) => [
2796
2835
  params.symbol.trim().toUpperCase(),
2797
2836
  params.interval,
@@ -3056,7 +3095,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3056
3095
  mode,
3057
3096
  startMs,
3058
3097
  endMs,
3059
- preloadStartMs: params.preloadStartMs
3098
+ preloadStartMs: params.preloadStartMs,
3099
+ nowMs: Date.now()
3060
3100
  });
3061
3101
  if (safeEndMs <= fromMs) {
3062
3102
  return skippedBackfillResult2();
@@ -3071,7 +3111,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3071
3111
  ...resolveDerivativesContextIntervalBackfillWindow({
3072
3112
  fromMs,
3073
3113
  toMs: safeEndMs,
3074
- interval: interval2
3114
+ interval: interval2,
3115
+ closedOnly: mode === "signals"
3075
3116
  })
3076
3117
  })).filter((item) => item.toMs > item.fromMs);
3077
3118
  if (!intervalWindows.length) {
@@ -3089,15 +3130,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3089
3130
  );
3090
3131
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
3091
3132
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
3133
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
3134
+ const coverageNowMs = Date.now();
3092
3135
  await Promise.all(
3093
3136
  intervalWindows.map(async (window2) => {
3094
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
3095
- source: "coinalyze",
3096
- symbols,
3137
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
3097
3138
  interval: window2.interval,
3098
3139
  fromMs: window2.fromMs,
3099
- toMs: window2.toMs
3140
+ toMs: window2.toMs,
3141
+ nowMs: coverageNowMs
3100
3142
  });
3143
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
3144
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
3145
+ source: "coinalyze",
3146
+ symbols,
3147
+ interval: window2.interval,
3148
+ fromMs: window2.fromMs,
3149
+ toMs: window2.toMs
3150
+ }),
3151
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
3152
+ source: "coinalyze",
3153
+ metric: "liquidation",
3154
+ symbols,
3155
+ interval: window2.interval,
3156
+ fromMs: confirmedLiquidationWindow.fromMs,
3157
+ toMs: confirmedLiquidationWindow.toMs
3158
+ }) : Promise.resolve([])
3159
+ ]);
3101
3160
  coverageKeysByInterval.set(
3102
3161
  window2.interval,
3103
3162
  new Set(
@@ -3119,6 +3178,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3119
3178
  rangesBySymbol.set(symbol, ranges);
3120
3179
  }
3121
3180
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3181
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3182
+ for (const row of liquidationCoverageRows) {
3183
+ const symbol = row.symbol.toUpperCase();
3184
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3185
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3186
+ liquidationRangesBySymbol.set(symbol, ranges);
3187
+ }
3188
+ liquidationCoverageRangesByInterval.set(
3189
+ window2.interval,
3190
+ liquidationRangesBySymbol
3191
+ );
3122
3192
  })
3123
3193
  );
3124
3194
  const cachedWindows = intervalWindows.reduce(
@@ -3130,7 +3200,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3130
3200
  }),
3131
3201
  0
3132
3202
  );
3133
- const allBackfillWindowsCached = intervalWindows.every((window2) => {
3203
+ const allBackfillWindowsCached = mode !== "signals" && intervalWindows.every((window2) => {
3134
3204
  const edgesBySymbol = edgesByInterval.get(window2.interval);
3135
3205
  const coverageKeys = coverageKeysByInterval.get(window2.interval);
3136
3206
  const backfillWindows = buildBackfillWindows({
@@ -3143,7 +3213,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3143
3213
  (symbol) => backfillWindows.every((backfillWindow) => {
3144
3214
  const normalizedSymbol = symbol.toUpperCase();
3145
3215
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
3146
- return hasDerivativesWindowCoverage({
3216
+ const dataCovered = hasDerivativesWindowCoverage({
3147
3217
  edges: edgesBySymbol?.get(normalizedSymbol),
3148
3218
  fromMs: backfillWindow.fromMs,
3149
3219
  toMs: backfillWindow.toMs
@@ -3160,6 +3230,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3160
3230
  toMs: backfillWindow.toMs
3161
3231
  })
3162
3232
  );
3233
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
3234
+ interval: window2.interval,
3235
+ fromMs: backfillWindow.fromMs,
3236
+ toMs: backfillWindow.toMs,
3237
+ nowMs: coverageNowMs
3238
+ });
3239
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3240
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3241
+ fromMs: confirmedLiquidationWindow.fromMs,
3242
+ toMs: confirmedLiquidationWindow.toMs,
3243
+ intervalMs: window2.intervalMs
3244
+ }) == null;
3245
+ return dataCovered && liquidationCovered;
3163
3246
  })
3164
3247
  );
3165
3248
  });
@@ -3244,6 +3327,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3244
3327
  coverageKeysByInterval.set(interval2, coverageKeys);
3245
3328
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval2) ?? /* @__PURE__ */ new Map();
3246
3329
  coverageRangesByInterval.set(interval2, coverageRangesBySymbol);
3330
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval2) ?? /* @__PURE__ */ new Map();
3331
+ liquidationCoverageRangesByInterval.set(
3332
+ interval2,
3333
+ liquidationCoverageRangesBySymbol
3334
+ );
3247
3335
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3248
3336
  const batch = symbolBatches[batchIdx];
3249
3337
  let cursor = window2.fromMs;
@@ -3256,30 +3344,26 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3256
3344
  fromMs: cursor,
3257
3345
  toMs
3258
3346
  });
3259
- if (coverageKeys.has(key)) {
3260
- return null;
3261
- }
3262
3347
  const normalizedSymbol = item.symbol.toUpperCase();
3263
- const coverageFromMs = resolveDerivativesContextMissingCoverageFetchFromMs({
3264
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3265
- fromMs: cursor,
3266
- toMs,
3267
- intervalMs: intervalMs2
3268
- });
3269
- if (coverageFromMs == null) {
3270
- return null;
3271
- }
3272
- const edges = edgesBySymbol.get(normalizedSymbol);
3273
- const edgesFromMs = resolveDerivativesContextMissingFetchFromMs({
3274
- edges,
3275
- fromMs: cursor,
3276
- toMs,
3277
- intervalMs: intervalMs2
3278
- });
3279
- if (edgesFromMs == null) {
3280
- return null;
3281
- }
3282
- return { item, fromMs: Math.max(coverageFromMs, edgesFromMs) };
3348
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3349
+ {
3350
+ mode,
3351
+ interval: interval2,
3352
+ intervalMs: intervalMs2,
3353
+ fromMs: cursor,
3354
+ toMs,
3355
+ nowMs: coverageNowMs,
3356
+ dataCoverageKeyExists: coverageKeys.has(key),
3357
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3358
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3359
+ edges: edgesBySymbol.get(normalizedSymbol)
3360
+ }
3361
+ );
3362
+ if (requiredFromMs == null) return null;
3363
+ return {
3364
+ item,
3365
+ fromMs: requiredFromMs
3366
+ };
3283
3367
  }).filter(
3284
3368
  (item) => item != null
3285
3369
  );
@@ -3293,85 +3377,150 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3293
3377
  const marketSymbols = missingBatch.map(
3294
3378
  (item) => item.marketSymbol
3295
3379
  );
3296
- const oiMap = await fetchMetricBatch({
3297
- endpoint: oiPath,
3298
- metric: "oi",
3299
- marketSymbols,
3300
- apiKey,
3301
- interval: interval2,
3302
- fromMs: group.fromMs,
3303
- toMs
3304
- });
3305
- const fundingMap = await fetchMetricBatch({
3306
- endpoint: fundingPath,
3307
- metric: "funding",
3308
- marketSymbols,
3309
- apiKey,
3310
- interval: interval2,
3311
- fromMs: group.fromMs,
3312
- toMs
3313
- });
3314
- const liqMap = await fetchMetricBatch({
3315
- endpoint: liqPath,
3316
- metric: "liq",
3317
- marketSymbols,
3318
- apiKey,
3319
- interval: interval2,
3320
- fromMs: group.fromMs,
3321
- toMs
3322
- });
3323
- const rows = missingBatch.flatMap((item) => {
3324
- const marketSymbol = item.marketSymbol.toUpperCase();
3325
- const points = (0, import_indicators2.mergeCoinalyzeMetrics)({
3326
- symbol: item.symbol,
3327
- oiRaw: oiMap.get(marketSymbol) ?? [],
3328
- fundingRaw: fundingMap.get(marketSymbol) ?? [],
3329
- liqRaw: liqMap.get(marketSymbol) ?? []
3380
+ const maxClosedBarAttempts = mode === "signals" ? asInt2(
3381
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_MAX_ATTEMPTS,
3382
+ 3
3383
+ ) : 1;
3384
+ const closedBarRetryDelayMs = asInt2(
3385
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_RETRY_DELAY_MS,
3386
+ 2e3
3387
+ );
3388
+ let rows = [];
3389
+ let missingClosedSymbols = [];
3390
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3391
+ for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3392
+ const oiMap = await fetchMetricBatch({
3393
+ endpoint: oiPath,
3394
+ metric: "oi",
3395
+ marketSymbols,
3396
+ apiKey,
3397
+ interval: interval2,
3398
+ fromMs: group.fromMs,
3399
+ toMs
3330
3400
  });
3331
- return (0, import_indicators2.coinalyzePointsToRows)(points, interval2, "coinalyze");
3332
- });
3401
+ const fundingMap = await fetchMetricBatch({
3402
+ endpoint: fundingPath,
3403
+ metric: "funding",
3404
+ marketSymbols,
3405
+ apiKey,
3406
+ interval: interval2,
3407
+ fromMs: group.fromMs,
3408
+ toMs
3409
+ });
3410
+ liquidationRowsByMarket = await fetchMetricBatch({
3411
+ endpoint: liqPath,
3412
+ metric: "liq",
3413
+ marketSymbols,
3414
+ apiKey,
3415
+ interval: interval2,
3416
+ fromMs: group.fromMs,
3417
+ toMs
3418
+ });
3419
+ rows = missingBatch.flatMap((item) => {
3420
+ const marketSymbol = item.marketSymbol.toUpperCase();
3421
+ const points = (0, import_indicators2.mergeCoinalyzeMetrics)({
3422
+ symbol: item.symbol,
3423
+ oiRaw: oiMap.get(marketSymbol) ?? [],
3424
+ fundingRaw: fundingMap.get(marketSymbol) ?? [],
3425
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3426
+ });
3427
+ return (0, import_indicators2.coinalyzePointsToRows)(points, interval2, "coinalyze");
3428
+ });
3429
+ missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
3430
+ symbols: missingBatch.map((item) => item.symbol),
3431
+ rows,
3432
+ expectedTimestamp: toMs
3433
+ }) : [];
3434
+ if (!missingClosedSymbols.length) break;
3435
+ if (attempt < maxClosedBarAttempts) {
3436
+ await (0, import_async.delay)(closedBarRetryDelayMs);
3437
+ }
3438
+ }
3439
+ if (missingClosedSymbols.length) {
3440
+ throw new Error(
3441
+ `Coinalyze closed ${interval2} bar ${new Date(toMs).toISOString()} unavailable for ${missingClosedSymbols.join(",")}`
3442
+ );
3443
+ }
3333
3444
  if (rows.length) {
3334
3445
  await (0, import_timescale2.upsertDerivatives)(rows);
3335
3446
  totalRows += rows.length;
3336
3447
  }
3337
- const rowsCountBySymbol = /* @__PURE__ */ new Map();
3338
- for (const row of rows) {
3339
- const symbol = row.symbol.toUpperCase();
3340
- rowsCountBySymbol.set(
3341
- symbol,
3342
- (rowsCountBySymbol.get(symbol) ?? 0) + 1
3343
- );
3344
- }
3345
- const coverageRows = missingBatch.map((item) => {
3346
- const normalizedSymbol = item.symbol.toUpperCase();
3347
- const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3348
- return {
3349
- source: "coinalyze",
3350
- symbol: item.symbol,
3351
- interval: interval2,
3352
- fromMs: cursor,
3353
- toMs,
3354
- rowsCount
3355
- };
3448
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
3449
+ interval: interval2,
3450
+ fromMs: group.fromMs,
3451
+ toMs,
3452
+ nowMs: coverageNowMs
3356
3453
  });
3357
- await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
3358
- for (const coverageRow of coverageRows) {
3359
- const symbol = coverageRow.symbol.toUpperCase();
3360
- edgesBySymbol.set(
3361
- symbol,
3362
- extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
3363
- );
3364
- coverageKeys.add(
3365
- coverageKey({
3454
+ if (confirmedLiquidationWindow) {
3455
+ const metricCoverageRows = missingBatch.map((item) => {
3456
+ const marketSymbol = item.marketSymbol.toUpperCase();
3457
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3458
+ const timestamp = (0, import_indicators2.toCoinalyzeTimestampMs)(
3459
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3460
+ );
3461
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3462
+ }).length;
3463
+ return {
3464
+ source: "coinalyze",
3465
+ metric: "liquidation",
3466
+ symbol: item.symbol,
3467
+ interval: interval2,
3468
+ fromMs: confirmedLiquidationWindow.fromMs,
3469
+ toMs: confirmedLiquidationWindow.toMs,
3470
+ eventRowsCount
3471
+ };
3472
+ });
3473
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3474
+ for (const coverageRow of metricCoverageRows) {
3475
+ const symbol = coverageRow.symbol.toUpperCase();
3476
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3477
+ ranges.push({
3478
+ fromMs: coverageRow.fromMs,
3479
+ toMs: coverageRow.toMs
3480
+ });
3481
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3482
+ }
3483
+ }
3484
+ if (mode === "backtest") {
3485
+ const rowsCountBySymbol = /* @__PURE__ */ new Map();
3486
+ for (const row of rows) {
3487
+ const symbol = row.symbol.toUpperCase();
3488
+ rowsCountBySymbol.set(
3366
3489
  symbol,
3490
+ (rowsCountBySymbol.get(symbol) ?? 0) + 1
3491
+ );
3492
+ }
3493
+ const coverageRows = missingBatch.map((item) => {
3494
+ const normalizedSymbol = item.symbol.toUpperCase();
3495
+ const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3496
+ return {
3497
+ source: "coinalyze",
3498
+ symbol: item.symbol,
3367
3499
  interval: interval2,
3368
3500
  fromMs: cursor,
3369
- toMs
3370
- })
3371
- );
3372
- const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
3373
- coverageRanges.push({ fromMs: cursor, toMs });
3374
- coverageRangesBySymbol.set(symbol, coverageRanges);
3501
+ toMs,
3502
+ rowsCount
3503
+ };
3504
+ });
3505
+ await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
3506
+ for (const coverageRow of coverageRows) {
3507
+ const symbol = coverageRow.symbol.toUpperCase();
3508
+ edgesBySymbol.set(
3509
+ symbol,
3510
+ extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
3511
+ );
3512
+ coverageKeys.add(
3513
+ coverageKey({
3514
+ symbol,
3515
+ interval: interval2,
3516
+ fromMs: cursor,
3517
+ toMs
3518
+ })
3519
+ );
3520
+ const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
3521
+ coverageRanges.push({ fromMs: cursor, toMs });
3522
+ coverageRangesBySymbol.set(symbol, coverageRanges);
3523
+ }
3375
3524
  }
3376
3525
  }
3377
3526
  }