@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.
@@ -41,11 +41,11 @@ var import_timescale = require("@tradejs/infra/timescale");
41
41
  var import_userSettings = require("@tradejs/infra/userSettings");
42
42
  var import_connectors = require("@tradejs/connectors");
43
43
  import_args.default.example(
44
- "yarn ts-node ./src/scripts/derivativesIngest --provider coinalyze --symbols BTCUSDT,ETHUSDT --intervals 15m,1h --days 120",
44
+ "yarn ts-node ./src/scripts/derivativesIngest --provider coinalyze --symbols BTCUSDT,ETHUSDT --intervals 15m --days 120",
45
45
  "Ingest market features (derivatives/spread) into Timescale by provider"
46
46
  );
47
47
  import_args.default.option(["s", "symbols"], "Comma-separated symbols", "BTCUSDT,ETHUSDT");
48
- import_args.default.option(["t", "intervals"], "Comma-separated intervals: 15m,1h", "15m,1h");
48
+ import_args.default.option(["t", "intervals"], "Comma-separated intervals: 15m,1h", "15m");
49
49
  import_args.default.option(["d", "days"], "Lookback in days", 120);
50
50
  import_args.default.option(
51
51
  ["p", "provider"],
@@ -82,9 +82,16 @@ var main = async () => {
82
82
  let totalSpreadRows = 0;
83
83
  for (const symbol of symbols) {
84
84
  for (const interval of intervals) {
85
+ const lastClosedStartMs = (0, import_indicators.getLastClosedDerivativesBarStartMs)(
86
+ now,
87
+ interval
88
+ );
85
89
  let cursor = fromMs;
86
- while (cursor < now) {
87
- const toMs = Math.min(now, cursor + batchDays * 24 * 60 * 60 * 1e3);
90
+ while (cursor < lastClosedStartMs) {
91
+ const toMs = Math.min(
92
+ lastClosedStartMs,
93
+ cursor + batchDays * 24 * 60 * 60 * 1e3
94
+ );
88
95
  process.stdout.write(
89
96
  `\r${import_chalk.default.cyan(providerName)} ${import_chalk.default.yellow(symbol)} ${interval} ${new Date(cursor).toISOString()} .. ${new Date(toMs).toISOString()} `
90
97
  );
@@ -49,7 +49,7 @@ var coinalyzeIntervalMap = {
49
49
  "1h": "1hour"
50
50
  };
51
51
  import_args.default.example(
52
- "yarn ts-node ./src/scripts/derivativesIngestCoinalyzeAll --days 120 --intervals 15m,1h",
52
+ "yarn ts-node ./src/scripts/derivativesIngestCoinalyzeAll --days 120 --intervals 15m",
53
53
  "Fetch derivatives for all getTickers symbols matched to Coinalyze markets"
54
54
  );
55
55
  import_args.default.option(["U", "user"], "User settings profile name from Redis", "root");
@@ -57,7 +57,7 @@ import_args.default.option(["t", "tickers"], "Comma-separated include symbols");
57
57
  import_args.default.option(["e", "exclude"], "Comma-separated exclude symbols");
58
58
  import_args.default.option(["l", "tickersLimit"], "Tickers limit");
59
59
  import_args.default.option(["c", "chunk"], "Chunk selector, e.g. 1/4");
60
- import_args.default.option(["i", "intervals"], "Intervals: 15m,1h", "15m,1h");
60
+ import_args.default.option(["i", "intervals"], "Intervals: 15m,1h", "15m");
61
61
  import_args.default.option(["d", "days"], "Lookback in days", 120);
62
62
  import_args.default.option(["b", "batchDays"], "Request chunk size in days", 120);
63
63
  import_args.default.option(
@@ -326,12 +326,16 @@ var main = async () => {
326
326
  let totalRows = 0;
327
327
  let failedWindows = 0;
328
328
  for (const interval of intervals) {
329
+ const lastClosedStartMs = (0, import_indicators.getLastClosedDerivativesBarStartMs)(now, interval);
329
330
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
330
331
  const batch = symbolBatches[batchIdx];
331
332
  const marketSymbols = batch.map((item) => item.marketSymbol);
332
333
  let cursor = fromMs;
333
- while (cursor < now) {
334
- const toMs = Math.min(now, cursor + batchDays * 24 * 60 * 60 * 1e3);
334
+ while (cursor < lastClosedStartMs) {
335
+ const toMs = Math.min(
336
+ lastClosedStartMs,
337
+ cursor + batchDays * 24 * 60 * 60 * 1e3
338
+ );
335
339
  try {
336
340
  const oiMap = await fetchMetricBatch({
337
341
  endpoint: oiPath,
@@ -374,6 +378,36 @@ var main = async () => {
374
378
  await (0, import_timescale.upsertDerivatives)(rows);
375
379
  totalRows += rows.length;
376
380
  }
381
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
382
+ interval,
383
+ fromMs: cursor,
384
+ toMs,
385
+ nowMs: now
386
+ });
387
+ if (confirmedLiquidationWindow) {
388
+ await (0, import_timescale.applyDerivativesMetricCoverage)(
389
+ batch.map((item) => {
390
+ const marketSymbol = item.marketSymbol.toUpperCase();
391
+ const eventRowsCount = (liqMap.get(marketSymbol) ?? []).filter(
392
+ (point) => {
393
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
394
+ point.t ?? point.ts ?? point.time ?? point.timestamp
395
+ );
396
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
397
+ }
398
+ ).length;
399
+ return {
400
+ source: "coinalyze",
401
+ metric: "liquidation",
402
+ symbol: item.symbol,
403
+ interval,
404
+ fromMs: confirmedLiquidationWindow.fromMs,
405
+ toMs: confirmedLiquidationWindow.toMs,
406
+ eventRowsCount
407
+ };
408
+ })
409
+ );
410
+ }
377
411
  } catch (error) {
378
412
  failedWindows += 1;
379
413
  console.error(
@@ -3106,7 +3106,7 @@ var import_userSettings = require("@tradejs/infra/userSettings");
3106
3106
  var HOUR_MS = 60 * 60 * 1e3;
3107
3107
  var DAY_MS2 = 24 * HOUR_MS;
3108
3108
  var DEFAULT_LOOKBACK_HOURS = 48;
3109
- var DEFAULT_INTERVALS = ["15m", "1h"];
3109
+ var SOURCE_INTERVALS = ["15m"];
3110
3110
  var coinalyzeIntervalMap = {
3111
3111
  "15m": "15min",
3112
3112
  "1h": "1hour"
@@ -3167,10 +3167,7 @@ var isDerivativesContextBackfillEnabled = (env) => {
3167
3167
  var shouldBackfillDerivativesContextForBacktest = (params) => !params.cacheOnly && (params.aiEnabled || params.mlEnabled) && isBacktestDerivativesContextEnabled();
3168
3168
  var shouldBackfillDerivativesContextForSignals = (params) => !params.cacheOnly && isSignalsDerivativesContextEnabled();
3169
3169
  var resolveDerivativesContextIntervals = () => {
3170
- const intervals = (0, import_indicators.normalizeDerivativesIntervals)(
3171
- process.env.DERIVATIVES_CONTEXT_INTERVALS
3172
- );
3173
- return intervals.length ? intervals : DEFAULT_INTERVALS;
3170
+ return [...SOURCE_INTERVALS];
3174
3171
  };
3175
3172
  var resolveDerivativesContextLookbackMs = () => {
3176
3173
  const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
@@ -3205,9 +3202,10 @@ var resolveDerivativesContextBackfillWindow = (params) => {
3205
3202
  };
3206
3203
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
3207
3204
  const intervalMs = derivativesIntervalMs(params.interval);
3205
+ const alignedToMs = params.closedOnly ? (0, import_indicators.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
3208
3206
  return {
3209
3207
  fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
3210
- toMs: Math.floor(params.toMs / intervalMs) * intervalMs,
3208
+ toMs: alignedToMs,
3211
3209
  intervalMs
3212
3210
  };
3213
3211
  };
@@ -3225,6 +3223,39 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
3225
3223
  }
3226
3224
  return null;
3227
3225
  };
3226
+ var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
3227
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
3228
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
3229
+ ranges: params.dataCoverageRanges,
3230
+ fromMs: params.fromMs,
3231
+ toMs: params.toMs,
3232
+ intervalMs: params.intervalMs
3233
+ });
3234
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
3235
+ edges: params.edges,
3236
+ fromMs: params.fromMs,
3237
+ toMs: params.toMs,
3238
+ intervalMs: params.intervalMs,
3239
+ refreshClosedTail: params.mode === "signals"
3240
+ });
3241
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
3242
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3243
+ interval: params.interval,
3244
+ fromMs: params.fromMs,
3245
+ toMs: params.toMs,
3246
+ nowMs: params.nowMs
3247
+ });
3248
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
3249
+ ranges: params.liquidationCoverageRanges,
3250
+ fromMs: confirmedLiquidationWindow.fromMs,
3251
+ toMs: confirmedLiquidationWindow.toMs,
3252
+ intervalMs: params.intervalMs
3253
+ }) : null;
3254
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
3255
+ (value) => value != null
3256
+ );
3257
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
3258
+ };
3228
3259
  var countBackfillWindows = (params) => {
3229
3260
  let count = 0;
3230
3261
  let cursor = params.fromMs;
@@ -3288,6 +3319,14 @@ var groupDerivativesContextMissingFetchRanges = (ranges) => {
3288
3319
  }
3289
3320
  return [...groups.entries()].sort(([a], [b]) => a - b).map(([fromMs, items]) => ({ fromMs, items }));
3290
3321
  };
3322
+ var getMissingClosedDerivativesSymbols = (params) => {
3323
+ const availableSymbols = new Set(
3324
+ params.rows.filter((row) => row.ts.getTime() === params.expectedTimestamp).map((row) => row.symbol.trim().toUpperCase())
3325
+ );
3326
+ return params.symbols.filter(
3327
+ (symbol) => !availableSymbols.has(symbol.trim().toUpperCase())
3328
+ );
3329
+ };
3291
3330
  var coverageKey = (params) => [
3292
3331
  params.symbol.trim().toUpperCase(),
3293
3332
  params.interval,
@@ -3552,7 +3591,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3552
3591
  mode,
3553
3592
  startMs,
3554
3593
  endMs,
3555
- preloadStartMs: params.preloadStartMs
3594
+ preloadStartMs: params.preloadStartMs,
3595
+ nowMs: Date.now()
3556
3596
  });
3557
3597
  if (safeEndMs <= fromMs) {
3558
3598
  return skippedBackfillResult2();
@@ -3567,7 +3607,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3567
3607
  ...resolveDerivativesContextIntervalBackfillWindow({
3568
3608
  fromMs,
3569
3609
  toMs: safeEndMs,
3570
- interval
3610
+ interval,
3611
+ closedOnly: mode === "signals"
3571
3612
  })
3572
3613
  })).filter((item) => item.toMs > item.fromMs);
3573
3614
  if (!intervalWindows.length) {
@@ -3585,15 +3626,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3585
3626
  );
3586
3627
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
3587
3628
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
3629
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
3630
+ const coverageNowMs = Date.now();
3588
3631
  await Promise.all(
3589
3632
  intervalWindows.map(async (window2) => {
3590
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
3591
- source: "coinalyze",
3592
- symbols,
3633
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3593
3634
  interval: window2.interval,
3594
3635
  fromMs: window2.fromMs,
3595
- toMs: window2.toMs
3636
+ toMs: window2.toMs,
3637
+ nowMs: coverageNowMs
3596
3638
  });
3639
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
3640
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
3641
+ source: "coinalyze",
3642
+ symbols,
3643
+ interval: window2.interval,
3644
+ fromMs: window2.fromMs,
3645
+ toMs: window2.toMs
3646
+ }),
3647
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
3648
+ source: "coinalyze",
3649
+ metric: "liquidation",
3650
+ symbols,
3651
+ interval: window2.interval,
3652
+ fromMs: confirmedLiquidationWindow.fromMs,
3653
+ toMs: confirmedLiquidationWindow.toMs
3654
+ }) : Promise.resolve([])
3655
+ ]);
3597
3656
  coverageKeysByInterval.set(
3598
3657
  window2.interval,
3599
3658
  new Set(
@@ -3615,6 +3674,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3615
3674
  rangesBySymbol.set(symbol, ranges);
3616
3675
  }
3617
3676
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3677
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3678
+ for (const row of liquidationCoverageRows) {
3679
+ const symbol = row.symbol.toUpperCase();
3680
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3681
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3682
+ liquidationRangesBySymbol.set(symbol, ranges);
3683
+ }
3684
+ liquidationCoverageRangesByInterval.set(
3685
+ window2.interval,
3686
+ liquidationRangesBySymbol
3687
+ );
3618
3688
  })
3619
3689
  );
3620
3690
  const cachedWindows = intervalWindows.reduce(
@@ -3626,7 +3696,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3626
3696
  }),
3627
3697
  0
3628
3698
  );
3629
- const allBackfillWindowsCached = intervalWindows.every((window2) => {
3699
+ const allBackfillWindowsCached = mode !== "signals" && intervalWindows.every((window2) => {
3630
3700
  const edgesBySymbol = edgesByInterval.get(window2.interval);
3631
3701
  const coverageKeys = coverageKeysByInterval.get(window2.interval);
3632
3702
  const backfillWindows = buildBackfillWindows({
@@ -3639,7 +3709,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3639
3709
  (symbol) => backfillWindows.every((backfillWindow) => {
3640
3710
  const normalizedSymbol = symbol.toUpperCase();
3641
3711
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
3642
- return hasDerivativesWindowCoverage({
3712
+ const dataCovered = hasDerivativesWindowCoverage({
3643
3713
  edges: edgesBySymbol?.get(normalizedSymbol),
3644
3714
  fromMs: backfillWindow.fromMs,
3645
3715
  toMs: backfillWindow.toMs
@@ -3656,6 +3726,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3656
3726
  toMs: backfillWindow.toMs
3657
3727
  })
3658
3728
  );
3729
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3730
+ interval: window2.interval,
3731
+ fromMs: backfillWindow.fromMs,
3732
+ toMs: backfillWindow.toMs,
3733
+ nowMs: coverageNowMs
3734
+ });
3735
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3736
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3737
+ fromMs: confirmedLiquidationWindow.fromMs,
3738
+ toMs: confirmedLiquidationWindow.toMs,
3739
+ intervalMs: window2.intervalMs
3740
+ }) == null;
3741
+ return dataCovered && liquidationCovered;
3659
3742
  })
3660
3743
  );
3661
3744
  });
@@ -3740,6 +3823,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3740
3823
  coverageKeysByInterval.set(interval, coverageKeys);
3741
3824
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3742
3825
  coverageRangesByInterval.set(interval, coverageRangesBySymbol);
3826
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3827
+ liquidationCoverageRangesByInterval.set(
3828
+ interval,
3829
+ liquidationCoverageRangesBySymbol
3830
+ );
3743
3831
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3744
3832
  const batch = symbolBatches[batchIdx];
3745
3833
  let cursor = window2.fromMs;
@@ -3752,30 +3840,26 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3752
3840
  fromMs: cursor,
3753
3841
  toMs
3754
3842
  });
3755
- if (coverageKeys.has(key)) {
3756
- return null;
3757
- }
3758
3843
  const normalizedSymbol = item.symbol.toUpperCase();
3759
- const coverageFromMs = resolveDerivativesContextMissingCoverageFetchFromMs({
3760
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3761
- fromMs: cursor,
3762
- toMs,
3763
- intervalMs
3764
- });
3765
- if (coverageFromMs == null) {
3766
- return null;
3767
- }
3768
- const edges = edgesBySymbol.get(normalizedSymbol);
3769
- const edgesFromMs = resolveDerivativesContextMissingFetchFromMs({
3770
- edges,
3771
- fromMs: cursor,
3772
- toMs,
3773
- intervalMs
3774
- });
3775
- if (edgesFromMs == null) {
3776
- return null;
3777
- }
3778
- return { item, fromMs: Math.max(coverageFromMs, edgesFromMs) };
3844
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3845
+ {
3846
+ mode,
3847
+ interval,
3848
+ intervalMs,
3849
+ fromMs: cursor,
3850
+ toMs,
3851
+ nowMs: coverageNowMs,
3852
+ dataCoverageKeyExists: coverageKeys.has(key),
3853
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3854
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3855
+ edges: edgesBySymbol.get(normalizedSymbol)
3856
+ }
3857
+ );
3858
+ if (requiredFromMs == null) return null;
3859
+ return {
3860
+ item,
3861
+ fromMs: requiredFromMs
3862
+ };
3779
3863
  }).filter(
3780
3864
  (item) => item != null
3781
3865
  );
@@ -3789,85 +3873,150 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3789
3873
  const marketSymbols = missingBatch.map(
3790
3874
  (item) => item.marketSymbol
3791
3875
  );
3792
- const oiMap = await fetchMetricBatch({
3793
- endpoint: oiPath,
3794
- metric: "oi",
3795
- marketSymbols,
3796
- apiKey,
3797
- interval,
3798
- fromMs: group.fromMs,
3799
- toMs
3800
- });
3801
- const fundingMap = await fetchMetricBatch({
3802
- endpoint: fundingPath,
3803
- metric: "funding",
3804
- marketSymbols,
3805
- apiKey,
3806
- interval,
3807
- fromMs: group.fromMs,
3808
- toMs
3809
- });
3810
- const liqMap = await fetchMetricBatch({
3811
- endpoint: liqPath,
3812
- metric: "liq",
3813
- marketSymbols,
3814
- apiKey,
3815
- interval,
3816
- fromMs: group.fromMs,
3817
- toMs
3818
- });
3819
- const rows = missingBatch.flatMap((item) => {
3820
- const marketSymbol = item.marketSymbol.toUpperCase();
3821
- const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3822
- symbol: item.symbol,
3823
- oiRaw: oiMap.get(marketSymbol) ?? [],
3824
- fundingRaw: fundingMap.get(marketSymbol) ?? [],
3825
- liqRaw: liqMap.get(marketSymbol) ?? []
3876
+ const maxClosedBarAttempts = mode === "signals" ? asInt2(
3877
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_MAX_ATTEMPTS,
3878
+ 3
3879
+ ) : 1;
3880
+ const closedBarRetryDelayMs = asInt2(
3881
+ process.env.DERIVATIVES_CONTEXT_CLOSED_BAR_RETRY_DELAY_MS,
3882
+ 2e3
3883
+ );
3884
+ let rows = [];
3885
+ let missingClosedSymbols = [];
3886
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3887
+ for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3888
+ const oiMap = await fetchMetricBatch({
3889
+ endpoint: oiPath,
3890
+ metric: "oi",
3891
+ marketSymbols,
3892
+ apiKey,
3893
+ interval,
3894
+ fromMs: group.fromMs,
3895
+ toMs
3826
3896
  });
3827
- return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3828
- });
3897
+ const fundingMap = await fetchMetricBatch({
3898
+ endpoint: fundingPath,
3899
+ metric: "funding",
3900
+ marketSymbols,
3901
+ apiKey,
3902
+ interval,
3903
+ fromMs: group.fromMs,
3904
+ toMs
3905
+ });
3906
+ liquidationRowsByMarket = await fetchMetricBatch({
3907
+ endpoint: liqPath,
3908
+ metric: "liq",
3909
+ marketSymbols,
3910
+ apiKey,
3911
+ interval,
3912
+ fromMs: group.fromMs,
3913
+ toMs
3914
+ });
3915
+ rows = missingBatch.flatMap((item) => {
3916
+ const marketSymbol = item.marketSymbol.toUpperCase();
3917
+ const points = (0, import_indicators.mergeCoinalyzeMetrics)({
3918
+ symbol: item.symbol,
3919
+ oiRaw: oiMap.get(marketSymbol) ?? [],
3920
+ fundingRaw: fundingMap.get(marketSymbol) ?? [],
3921
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3922
+ });
3923
+ return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3924
+ });
3925
+ missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
3926
+ symbols: missingBatch.map((item) => item.symbol),
3927
+ rows,
3928
+ expectedTimestamp: toMs
3929
+ }) : [];
3930
+ if (!missingClosedSymbols.length) break;
3931
+ if (attempt < maxClosedBarAttempts) {
3932
+ await (0, import_async.delay)(closedBarRetryDelayMs);
3933
+ }
3934
+ }
3935
+ if (missingClosedSymbols.length) {
3936
+ throw new Error(
3937
+ `Coinalyze closed ${interval} bar ${new Date(toMs).toISOString()} unavailable for ${missingClosedSymbols.join(",")}`
3938
+ );
3939
+ }
3829
3940
  if (rows.length) {
3830
3941
  await (0, import_timescale2.upsertDerivatives)(rows);
3831
3942
  totalRows += rows.length;
3832
3943
  }
3833
- const rowsCountBySymbol = /* @__PURE__ */ new Map();
3834
- for (const row of rows) {
3835
- const symbol = row.symbol.toUpperCase();
3836
- rowsCountBySymbol.set(
3837
- symbol,
3838
- (rowsCountBySymbol.get(symbol) ?? 0) + 1
3839
- );
3840
- }
3841
- const coverageRows = missingBatch.map((item) => {
3842
- const normalizedSymbol = item.symbol.toUpperCase();
3843
- const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3844
- return {
3845
- source: "coinalyze",
3846
- symbol: item.symbol,
3847
- interval,
3848
- fromMs: cursor,
3849
- toMs,
3850
- rowsCount
3851
- };
3944
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3945
+ interval,
3946
+ fromMs: group.fromMs,
3947
+ toMs,
3948
+ nowMs: coverageNowMs
3852
3949
  });
3853
- await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
3854
- for (const coverageRow of coverageRows) {
3855
- const symbol = coverageRow.symbol.toUpperCase();
3856
- edgesBySymbol.set(
3857
- symbol,
3858
- extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
3859
- );
3860
- coverageKeys.add(
3861
- coverageKey({
3950
+ if (confirmedLiquidationWindow) {
3951
+ const metricCoverageRows = missingBatch.map((item) => {
3952
+ const marketSymbol = item.marketSymbol.toUpperCase();
3953
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3954
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
3955
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3956
+ );
3957
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3958
+ }).length;
3959
+ return {
3960
+ source: "coinalyze",
3961
+ metric: "liquidation",
3962
+ symbol: item.symbol,
3963
+ interval,
3964
+ fromMs: confirmedLiquidationWindow.fromMs,
3965
+ toMs: confirmedLiquidationWindow.toMs,
3966
+ eventRowsCount
3967
+ };
3968
+ });
3969
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3970
+ for (const coverageRow of metricCoverageRows) {
3971
+ const symbol = coverageRow.symbol.toUpperCase();
3972
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3973
+ ranges.push({
3974
+ fromMs: coverageRow.fromMs,
3975
+ toMs: coverageRow.toMs
3976
+ });
3977
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3978
+ }
3979
+ }
3980
+ if (mode === "backtest") {
3981
+ const rowsCountBySymbol = /* @__PURE__ */ new Map();
3982
+ for (const row of rows) {
3983
+ const symbol = row.symbol.toUpperCase();
3984
+ rowsCountBySymbol.set(
3862
3985
  symbol,
3986
+ (rowsCountBySymbol.get(symbol) ?? 0) + 1
3987
+ );
3988
+ }
3989
+ const coverageRows = missingBatch.map((item) => {
3990
+ const normalizedSymbol = item.symbol.toUpperCase();
3991
+ const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
3992
+ return {
3993
+ source: "coinalyze",
3994
+ symbol: item.symbol,
3863
3995
  interval,
3864
3996
  fromMs: cursor,
3865
- toMs
3866
- })
3867
- );
3868
- const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
3869
- coverageRanges.push({ fromMs: cursor, toMs });
3870
- coverageRangesBySymbol.set(symbol, coverageRanges);
3997
+ toMs,
3998
+ rowsCount
3999
+ };
4000
+ });
4001
+ await (0, import_timescale2.upsertDerivativesBackfillCoverage)(coverageRows);
4002
+ for (const coverageRow of coverageRows) {
4003
+ const symbol = coverageRow.symbol.toUpperCase();
4004
+ edgesBySymbol.set(
4005
+ symbol,
4006
+ extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
4007
+ );
4008
+ coverageKeys.add(
4009
+ coverageKey({
4010
+ symbol,
4011
+ interval,
4012
+ fromMs: cursor,
4013
+ toMs
4014
+ })
4015
+ );
4016
+ const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
4017
+ coverageRanges.push({ fromMs: cursor, toMs });
4018
+ coverageRangesBySymbol.set(symbol, coverageRanges);
4019
+ }
3871
4020
  }
3872
4021
  }
3873
4022
  }