@tradejs/cli 2.0.1 → 2.0.3

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.
@@ -327,6 +327,23 @@ var addDirectionalDerivedFeatures = (features) => {
327
327
  }
328
328
  features["derived.directIndicatorSupportCount"] = supportCount;
329
329
  };
330
+ var addSignalRiskDistanceFeatures = ({
331
+ features,
332
+ signal
333
+ }) => {
334
+ const currentPrice = isFiniteNumber(signal.prices?.currentPrice) ? signal.prices.currentPrice : null;
335
+ const stopLossPrice = isFiniteNumber(signal.prices?.stopLossPrice) ? signal.prices.stopLossPrice : null;
336
+ const takeProfitPrice = isFiniteNumber(signal.prices?.takeProfitPrice) ? signal.prices.takeProfitPrice : null;
337
+ if (currentPrice == null || currentPrice === 0) {
338
+ return;
339
+ }
340
+ if (stopLossPrice != null) {
341
+ features["derived.stopDistanceBps"] = Math.abs(currentPrice - stopLossPrice) / Math.abs(currentPrice) * 1e4;
342
+ }
343
+ if (takeProfitPrice != null) {
344
+ features["derived.takeProfitDistanceBps"] = Math.abs(takeProfitPrice - currentPrice) / Math.abs(currentPrice) * 1e4;
345
+ }
346
+ };
330
347
  var collectAiPocketFeatures = ({
331
348
  payload,
332
349
  gateContext,
@@ -354,6 +371,7 @@ var collectAiPocketFeatures = ({
354
371
  maxDepth: 8,
355
372
  shouldSkipPath: featureProfile === "compact" ? isCompactFeaturePathSkipped : void 0
356
373
  });
374
+ addSignalRiskDistanceFeatures({ features, signal });
357
375
  addDirectionalDerivedFeatures(features);
358
376
  return features;
359
377
  };
@@ -34,6 +34,7 @@ var divideOrNull = (num, denom) => {
34
34
  var DAY_MS = 24 * 60 * 60 * 1e3;
35
35
  var DAYS_PER_WEEK = 7;
36
36
  var DAYS_PER_MONTH = 30.4375;
37
+ var DAYS_PER_YEAR = 365;
37
38
  var getEvaluationPeriodDays = (evaluations) => {
38
39
  let minTimestamp = null;
39
40
  let maxTimestamp = null;
@@ -55,6 +56,40 @@ var getEvaluationPeriodDays = (evaluations) => {
55
56
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
56
57
  };
57
58
  var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
59
+ var calculateTradePnlRiskRatios = ({
60
+ profits,
61
+ totalProfit,
62
+ maxDrawdown,
63
+ periodDays
64
+ }) => {
65
+ if (!profits.length || periodDays == null || periodDays <= 0) {
66
+ return {
67
+ sharpeRatio: null,
68
+ sortinoRatio: null,
69
+ calmarRatio: null
70
+ };
71
+ }
72
+ const meanProfit = totalProfit / profits.length;
73
+ const variance = profits.reduce((sum, profit) => {
74
+ const diff = profit - meanProfit;
75
+ return sum + diff * diff;
76
+ }, 0) / profits.length;
77
+ const stdDev = Math.sqrt(variance);
78
+ const downsideDeviation = Math.sqrt(
79
+ profits.reduce(
80
+ (sum, profit) => profit < 0 ? sum + profit * profit : sum,
81
+ 0
82
+ ) / profits.length
83
+ );
84
+ const annualizationScale = Math.sqrt(
85
+ profits.length / periodDays * DAYS_PER_YEAR
86
+ );
87
+ return {
88
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
89
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
90
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
91
+ };
92
+ };
58
93
  var emptyRiskSummary = () => ({
59
94
  trades: 0,
60
95
  totalProfit: 0,
@@ -70,12 +105,15 @@ var emptyRiskSummary = () => ({
70
105
  maxDrawdown: 0,
71
106
  maxDrawdownPctOfGrossProfit: null,
72
107
  maxDrawdownPctOfTotalProfit: null,
108
+ sharpeRatio: null,
109
+ sortinoRatio: null,
110
+ calmarRatio: null,
73
111
  recoveryFactor: null,
74
112
  ulcerIndex: null,
75
113
  maxConsecutiveWins: 0,
76
114
  maxConsecutiveLosses: 0
77
115
  });
78
- var summarizeApprovedRisk = (evaluations) => {
116
+ var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
79
117
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
80
118
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
81
119
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -98,8 +136,10 @@ var summarizeApprovedRisk = (evaluations) => {
98
136
  let currentLossStreak = 0;
99
137
  let maxConsecutiveWins = 0;
100
138
  let maxConsecutiveLosses = 0;
139
+ const approvedProfits = [];
101
140
  for (const evaluation of approvedEvaluations) {
102
141
  const profit = evaluation.profit;
142
+ approvedProfits.push(profit);
103
143
  if (profit > 0) {
104
144
  grossProfit += profit;
105
145
  wins += 1;
@@ -127,6 +167,12 @@ var summarizeApprovedRisk = (evaluations) => {
127
167
  const totalProfit = grossProfit - grossLoss;
128
168
  const avgWin = divideOrNull(grossProfit, wins);
129
169
  const avgLoss = divideOrNull(grossLoss, losses);
170
+ const riskRatios = calculateTradePnlRiskRatios({
171
+ profits: approvedProfits,
172
+ totalProfit,
173
+ maxDrawdown,
174
+ periodDays
175
+ });
130
176
  return {
131
177
  trades: approvedEvaluations.length,
132
178
  totalProfit,
@@ -142,6 +188,7 @@ var summarizeApprovedRisk = (evaluations) => {
142
188
  maxDrawdown,
143
189
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
144
190
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
191
+ ...riskRatios,
145
192
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
146
193
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
147
194
  maxConsecutiveWins,
@@ -240,7 +287,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
240
287
  avgApprovedTradesPerDay,
241
288
  avgApprovedTradesPerWeek,
242
289
  expectancyDelta,
243
- approvedRisk: summarizeApprovedRisk(evaluations),
290
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
244
291
  qualityBuckets: [...bucketMap.values()].sort(
245
292
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
246
293
  )
@@ -35,6 +35,7 @@ var divideOrNull = (num, denom) => {
35
35
  var DAY_MS = 24 * 60 * 60 * 1e3;
36
36
  var DAYS_PER_WEEK = 7;
37
37
  var DAYS_PER_MONTH = 30.4375;
38
+ var DAYS_PER_YEAR = 365;
38
39
  var getEvaluationPeriodDays = (evaluations) => {
39
40
  let minTimestamp = null;
40
41
  let maxTimestamp = null;
@@ -56,6 +57,40 @@ var getEvaluationPeriodDays = (evaluations) => {
56
57
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
57
58
  };
58
59
  var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
60
+ var calculateTradePnlRiskRatios = ({
61
+ profits,
62
+ totalProfit,
63
+ maxDrawdown,
64
+ periodDays
65
+ }) => {
66
+ if (!profits.length || periodDays == null || periodDays <= 0) {
67
+ return {
68
+ sharpeRatio: null,
69
+ sortinoRatio: null,
70
+ calmarRatio: null
71
+ };
72
+ }
73
+ const meanProfit = totalProfit / profits.length;
74
+ const variance = profits.reduce((sum, profit) => {
75
+ const diff = profit - meanProfit;
76
+ return sum + diff * diff;
77
+ }, 0) / profits.length;
78
+ const stdDev = Math.sqrt(variance);
79
+ const downsideDeviation = Math.sqrt(
80
+ profits.reduce(
81
+ (sum, profit) => profit < 0 ? sum + profit * profit : sum,
82
+ 0
83
+ ) / profits.length
84
+ );
85
+ const annualizationScale = Math.sqrt(
86
+ profits.length / periodDays * DAYS_PER_YEAR
87
+ );
88
+ return {
89
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
90
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
91
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
92
+ };
93
+ };
59
94
  var emptyRiskSummary = () => ({
60
95
  trades: 0,
61
96
  totalProfit: 0,
@@ -71,12 +106,15 @@ var emptyRiskSummary = () => ({
71
106
  maxDrawdown: 0,
72
107
  maxDrawdownPctOfGrossProfit: null,
73
108
  maxDrawdownPctOfTotalProfit: null,
109
+ sharpeRatio: null,
110
+ sortinoRatio: null,
111
+ calmarRatio: null,
74
112
  recoveryFactor: null,
75
113
  ulcerIndex: null,
76
114
  maxConsecutiveWins: 0,
77
115
  maxConsecutiveLosses: 0
78
116
  });
79
- var summarizeApprovedRisk = (evaluations) => {
117
+ var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
80
118
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
81
119
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
82
120
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -99,8 +137,10 @@ var summarizeApprovedRisk = (evaluations) => {
99
137
  let currentLossStreak = 0;
100
138
  let maxConsecutiveWins = 0;
101
139
  let maxConsecutiveLosses = 0;
140
+ const approvedProfits = [];
102
141
  for (const evaluation of approvedEvaluations) {
103
142
  const profit = evaluation.profit;
143
+ approvedProfits.push(profit);
104
144
  if (profit > 0) {
105
145
  grossProfit += profit;
106
146
  wins += 1;
@@ -128,6 +168,12 @@ var summarizeApprovedRisk = (evaluations) => {
128
168
  const totalProfit = grossProfit - grossLoss;
129
169
  const avgWin = divideOrNull(grossProfit, wins);
130
170
  const avgLoss = divideOrNull(grossLoss, losses);
171
+ const riskRatios = calculateTradePnlRiskRatios({
172
+ profits: approvedProfits,
173
+ totalProfit,
174
+ maxDrawdown,
175
+ periodDays
176
+ });
131
177
  return {
132
178
  trades: approvedEvaluations.length,
133
179
  totalProfit,
@@ -143,6 +189,7 @@ var summarizeApprovedRisk = (evaluations) => {
143
189
  maxDrawdown,
144
190
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
145
191
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
192
+ ...riskRatios,
146
193
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
147
194
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
148
195
  maxConsecutiveWins,
@@ -241,7 +288,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
241
288
  avgApprovedTradesPerDay,
242
289
  avgApprovedTradesPerWeek,
243
290
  expectancyDelta,
244
- approvedRisk: summarizeApprovedRisk(evaluations),
291
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
245
292
  qualityBuckets: [...bucketMap.values()].sort(
246
293
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
247
294
  )
@@ -52,6 +52,7 @@ var divideOrNull = (num, denom) => {
52
52
  var DAY_MS = 24 * 60 * 60 * 1e3;
53
53
  var DAYS_PER_WEEK = 7;
54
54
  var DAYS_PER_MONTH = 30.4375;
55
+ var DAYS_PER_YEAR = 365;
55
56
  var getEvaluationPeriodDays = (evaluations) => {
56
57
  let minTimestamp = null;
57
58
  let maxTimestamp = null;
@@ -73,6 +74,40 @@ var getEvaluationPeriodDays = (evaluations) => {
73
74
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
74
75
  };
75
76
  var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
77
+ var calculateTradePnlRiskRatios = ({
78
+ profits,
79
+ totalProfit,
80
+ maxDrawdown,
81
+ periodDays
82
+ }) => {
83
+ if (!profits.length || periodDays == null || periodDays <= 0) {
84
+ return {
85
+ sharpeRatio: null,
86
+ sortinoRatio: null,
87
+ calmarRatio: null
88
+ };
89
+ }
90
+ const meanProfit = totalProfit / profits.length;
91
+ const variance = profits.reduce((sum, profit) => {
92
+ const diff = profit - meanProfit;
93
+ return sum + diff * diff;
94
+ }, 0) / profits.length;
95
+ const stdDev = Math.sqrt(variance);
96
+ const downsideDeviation = Math.sqrt(
97
+ profits.reduce(
98
+ (sum, profit) => profit < 0 ? sum + profit * profit : sum,
99
+ 0
100
+ ) / profits.length
101
+ );
102
+ const annualizationScale = Math.sqrt(
103
+ profits.length / periodDays * DAYS_PER_YEAR
104
+ );
105
+ return {
106
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
107
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
108
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
109
+ };
110
+ };
76
111
  var emptyRiskSummary = () => ({
77
112
  trades: 0,
78
113
  totalProfit: 0,
@@ -88,12 +123,15 @@ var emptyRiskSummary = () => ({
88
123
  maxDrawdown: 0,
89
124
  maxDrawdownPctOfGrossProfit: null,
90
125
  maxDrawdownPctOfTotalProfit: null,
126
+ sharpeRatio: null,
127
+ sortinoRatio: null,
128
+ calmarRatio: null,
91
129
  recoveryFactor: null,
92
130
  ulcerIndex: null,
93
131
  maxConsecutiveWins: 0,
94
132
  maxConsecutiveLosses: 0
95
133
  });
96
- var summarizeApprovedRisk = (evaluations) => {
134
+ var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
97
135
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
98
136
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
99
137
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -116,8 +154,10 @@ var summarizeApprovedRisk = (evaluations) => {
116
154
  let currentLossStreak = 0;
117
155
  let maxConsecutiveWins = 0;
118
156
  let maxConsecutiveLosses = 0;
157
+ const approvedProfits = [];
119
158
  for (const evaluation of approvedEvaluations) {
120
159
  const profit = evaluation.profit;
160
+ approvedProfits.push(profit);
121
161
  if (profit > 0) {
122
162
  grossProfit += profit;
123
163
  wins += 1;
@@ -145,6 +185,12 @@ var summarizeApprovedRisk = (evaluations) => {
145
185
  const totalProfit = grossProfit - grossLoss;
146
186
  const avgWin = divideOrNull(grossProfit, wins);
147
187
  const avgLoss = divideOrNull(grossLoss, losses);
188
+ const riskRatios = calculateTradePnlRiskRatios({
189
+ profits: approvedProfits,
190
+ totalProfit,
191
+ maxDrawdown,
192
+ periodDays
193
+ });
148
194
  return {
149
195
  trades: approvedEvaluations.length,
150
196
  totalProfit,
@@ -160,6 +206,7 @@ var summarizeApprovedRisk = (evaluations) => {
160
206
  maxDrawdown,
161
207
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
162
208
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
209
+ ...riskRatios,
163
210
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
164
211
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
165
212
  maxConsecutiveWins,
@@ -258,7 +305,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
258
305
  avgApprovedTradesPerDay,
259
306
  avgApprovedTradesPerWeek,
260
307
  expectancyDelta,
261
- approvedRisk: summarizeApprovedRisk(evaluations),
308
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
262
309
  qualityBuckets: [...bucketMap.values()].sort(
263
310
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
264
311
  )
@@ -286,6 +333,12 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
286
333
  "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
287
334
  "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
288
335
  ];
336
+ var DERIVATIVES_CONTEXT_DATA_MODEL = {
337
+ derivativesSourceIntervals: "15m",
338
+ derivativesDerivedIntervals: "1h",
339
+ derivativesHourlyFallback: "stored-1h",
340
+ derivativesDataModelVersion: 2
341
+ };
289
342
  var normalizeForStableJson = (value) => {
290
343
  if (Array.isArray(value)) {
291
344
  return value.map(normalizeForStableJson);
@@ -398,6 +451,7 @@ var resolveGateFingerprint = async (projectRoot, strategyName, gitSha) => {
398
451
  const relativeCandidates = [
399
452
  `packages/strategies/src/${strategyName}/adapters/ai.ts`,
400
453
  `packages/strategies/src/${strategyName}/guardrails.ts`,
454
+ `packages/strategies/src/${strategyName}/pockets.ts`,
401
455
  `packages/strategies/src/${strategyName}/config.ts`,
402
456
  "packages/node/src/ai.ts"
403
457
  ];
@@ -444,7 +498,8 @@ var buildAiTrainLineage = async ({
444
498
  ...Object.fromEntries(
445
499
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
446
500
  ),
447
- ...runContext
501
+ ...runContext,
502
+ ...DERIVATIVES_CONTEXT_DATA_MODEL
448
503
  };
449
504
  return {
450
505
  gitSha,
@@ -47,6 +47,7 @@ __export(derivativesContextBackfill_exports, {
47
47
  resolveDerivativesContextLookbackMs: () => resolveDerivativesContextLookbackMs,
48
48
  resolveDerivativesContextMissingCoverageFetchFromMs: () => resolveDerivativesContextMissingCoverageFetchFromMs,
49
49
  resolveDerivativesContextMissingFetchFromMs: () => resolveDerivativesContextMissingFetchFromMs,
50
+ resolveDerivativesContextRequiredFetchFromMs: () => resolveDerivativesContextRequiredFetchFromMs,
50
51
  shouldBackfillDerivativesContextForBacktest: () => shouldBackfillDerivativesContextForBacktest,
51
52
  shouldBackfillDerivativesContextForSignals: () => shouldBackfillDerivativesContextForSignals
52
53
  });
@@ -179,6 +180,38 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
179
180
  return null;
180
181
  };
181
182
  var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
183
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
184
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
185
+ ranges: params.dataCoverageRanges,
186
+ fromMs: params.fromMs,
187
+ toMs: params.toMs,
188
+ intervalMs: params.intervalMs
189
+ });
190
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
191
+ edges: params.edges,
192
+ fromMs: params.fromMs,
193
+ toMs: params.toMs,
194
+ intervalMs: params.intervalMs,
195
+ refreshClosedTail: params.mode === "signals"
196
+ });
197
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
198
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
199
+ interval: params.interval,
200
+ fromMs: params.fromMs,
201
+ toMs: params.toMs,
202
+ nowMs: params.nowMs
203
+ });
204
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
205
+ ranges: params.liquidationCoverageRanges,
206
+ fromMs: confirmedLiquidationWindow.fromMs,
207
+ toMs: confirmedLiquidationWindow.toMs,
208
+ intervalMs: params.intervalMs
209
+ }) : null;
210
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
211
+ (value) => value != null
212
+ );
213
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
214
+ };
182
215
  var countBackfillWindows = (params) => {
183
216
  let count = 0;
184
217
  let cursor = params.fromMs;
@@ -517,7 +550,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
517
550
  mode,
518
551
  startMs,
519
552
  endMs,
520
- preloadStartMs: params.preloadStartMs
553
+ preloadStartMs: params.preloadStartMs,
554
+ nowMs: Date.now()
521
555
  });
522
556
  if (safeEndMs <= fromMs) {
523
557
  return skippedBackfillResult();
@@ -551,15 +585,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
551
585
  );
552
586
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
553
587
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
588
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
589
+ const coverageNowMs = Date.now();
554
590
  await Promise.all(
555
591
  intervalWindows.map(async (window) => {
556
- const coverageRows = await (0, import_timescale.getDerivativesBackfillCoverage)({
557
- source: "coinalyze",
558
- symbols,
592
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
559
593
  interval: window.interval,
560
594
  fromMs: window.fromMs,
561
- toMs: window.toMs
595
+ toMs: window.toMs,
596
+ nowMs: coverageNowMs
562
597
  });
598
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
599
+ (0, import_timescale.getDerivativesBackfillCoverage)({
600
+ source: "coinalyze",
601
+ symbols,
602
+ interval: window.interval,
603
+ fromMs: window.fromMs,
604
+ toMs: window.toMs
605
+ }),
606
+ confirmedLiquidationWindow ? (0, import_timescale.getDerivativesMetricCoverage)({
607
+ source: "coinalyze",
608
+ metric: "liquidation",
609
+ symbols,
610
+ interval: window.interval,
611
+ fromMs: confirmedLiquidationWindow.fromMs,
612
+ toMs: confirmedLiquidationWindow.toMs
613
+ }) : Promise.resolve([])
614
+ ]);
563
615
  coverageKeysByInterval.set(
564
616
  window.interval,
565
617
  new Set(
@@ -581,6 +633,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
581
633
  rangesBySymbol.set(symbol, ranges);
582
634
  }
583
635
  coverageRangesByInterval.set(window.interval, rangesBySymbol);
636
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
637
+ for (const row of liquidationCoverageRows) {
638
+ const symbol = row.symbol.toUpperCase();
639
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
640
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
641
+ liquidationRangesBySymbol.set(symbol, ranges);
642
+ }
643
+ liquidationCoverageRangesByInterval.set(
644
+ window.interval,
645
+ liquidationRangesBySymbol
646
+ );
584
647
  })
585
648
  );
586
649
  const cachedWindows = intervalWindows.reduce(
@@ -605,7 +668,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
605
668
  (symbol) => backfillWindows.every((backfillWindow) => {
606
669
  const normalizedSymbol = symbol.toUpperCase();
607
670
  const coverageRanges = coverageRangesByInterval.get(window.interval)?.get(normalizedSymbol) ?? [];
608
- return hasDerivativesWindowCoverage({
671
+ const dataCovered = hasDerivativesWindowCoverage({
609
672
  edges: edgesBySymbol?.get(normalizedSymbol),
610
673
  fromMs: backfillWindow.fromMs,
611
674
  toMs: backfillWindow.toMs
@@ -622,6 +685,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
622
685
  toMs: backfillWindow.toMs
623
686
  })
624
687
  );
688
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
689
+ interval: window.interval,
690
+ fromMs: backfillWindow.fromMs,
691
+ toMs: backfillWindow.toMs,
692
+ nowMs: coverageNowMs
693
+ });
694
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
695
+ ranges: liquidationCoverageRangesByInterval.get(window.interval)?.get(normalizedSymbol) ?? [],
696
+ fromMs: confirmedLiquidationWindow.fromMs,
697
+ toMs: confirmedLiquidationWindow.toMs,
698
+ intervalMs: window.intervalMs
699
+ }) == null;
700
+ return dataCovered && liquidationCovered;
625
701
  })
626
702
  );
627
703
  });
@@ -706,6 +782,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
706
782
  coverageKeysByInterval.set(interval, coverageKeys);
707
783
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
708
784
  coverageRangesByInterval.set(interval, coverageRangesBySymbol);
785
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
786
+ liquidationCoverageRangesByInterval.set(
787
+ interval,
788
+ liquidationCoverageRangesBySymbol
789
+ );
709
790
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
710
791
  const batch = symbolBatches[batchIdx];
711
792
  let cursor = window.fromMs;
@@ -718,33 +799,25 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
718
799
  fromMs: cursor,
719
800
  toMs
720
801
  });
721
- if (coverageKeys.has(key)) {
722
- return null;
723
- }
724
802
  const normalizedSymbol = item.symbol.toUpperCase();
725
- const coverageFromMs = mode === "signals" ? cursor : resolveDerivativesContextMissingCoverageFetchFromMs({
726
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
727
- fromMs: cursor,
728
- toMs,
729
- intervalMs
730
- });
731
- if (coverageFromMs == null) {
732
- return null;
733
- }
734
- const edges = edgesBySymbol.get(normalizedSymbol);
735
- const edgesFromMs = resolveDerivativesContextFetchFromMs({
736
- edges,
737
- fromMs: cursor,
738
- toMs,
739
- intervalMs,
740
- refreshClosedTail: mode === "signals"
741
- });
742
- if (edgesFromMs == null) {
743
- return null;
744
- }
803
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
804
+ {
805
+ mode,
806
+ interval,
807
+ intervalMs,
808
+ fromMs: cursor,
809
+ toMs,
810
+ nowMs: coverageNowMs,
811
+ dataCoverageKeyExists: coverageKeys.has(key),
812
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
813
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
814
+ edges: edgesBySymbol.get(normalizedSymbol)
815
+ }
816
+ );
817
+ if (requiredFromMs == null) return null;
745
818
  return {
746
819
  item,
747
- fromMs: Math.max(coverageFromMs, edgesFromMs)
820
+ fromMs: requiredFromMs
748
821
  };
749
822
  }).filter(
750
823
  (item) => item != null
@@ -769,6 +842,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
769
842
  );
770
843
  let rows = [];
771
844
  let missingClosedSymbols = [];
845
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
772
846
  for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
773
847
  const oiMap = await fetchMetricBatch({
774
848
  endpoint: oiPath,
@@ -788,7 +862,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
788
862
  fromMs: group.fromMs,
789
863
  toMs
790
864
  });
791
- const liqMap = await fetchMetricBatch({
865
+ liquidationRowsByMarket = await fetchMetricBatch({
792
866
  endpoint: liqPath,
793
867
  metric: "liq",
794
868
  marketSymbols,
@@ -803,7 +877,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
803
877
  symbol: item.symbol,
804
878
  oiRaw: oiMap.get(marketSymbol) ?? [],
805
879
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
806
- liqRaw: liqMap.get(marketSymbol) ?? []
880
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
807
881
  });
808
882
  return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
809
883
  });
@@ -826,6 +900,42 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
826
900
  await (0, import_timescale.upsertDerivatives)(rows);
827
901
  totalRows += rows.length;
828
902
  }
903
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
904
+ interval,
905
+ fromMs: group.fromMs,
906
+ toMs,
907
+ nowMs: coverageNowMs
908
+ });
909
+ if (confirmedLiquidationWindow) {
910
+ const metricCoverageRows = missingBatch.map((item) => {
911
+ const marketSymbol = item.marketSymbol.toUpperCase();
912
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
913
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
914
+ point.t ?? point.ts ?? point.time ?? point.timestamp
915
+ );
916
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
917
+ }).length;
918
+ return {
919
+ source: "coinalyze",
920
+ metric: "liquidation",
921
+ symbol: item.symbol,
922
+ interval,
923
+ fromMs: confirmedLiquidationWindow.fromMs,
924
+ toMs: confirmedLiquidationWindow.toMs,
925
+ eventRowsCount
926
+ };
927
+ });
928
+ await (0, import_timescale.applyDerivativesMetricCoverage)(metricCoverageRows);
929
+ for (const coverageRow of metricCoverageRows) {
930
+ const symbol = coverageRow.symbol.toUpperCase();
931
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
932
+ ranges.push({
933
+ fromMs: coverageRow.fromMs,
934
+ toMs: coverageRow.toMs
935
+ });
936
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
937
+ }
938
+ }
829
939
  if (mode === "backtest") {
830
940
  const rowsCountBySymbol = /* @__PURE__ */ new Map();
831
941
  for (const row of rows) {
@@ -939,6 +1049,7 @@ var backfillDerivativesContextForSignals = async (params) => backfillDerivatives
939
1049
  resolveDerivativesContextLookbackMs,
940
1050
  resolveDerivativesContextMissingCoverageFetchFromMs,
941
1051
  resolveDerivativesContextMissingFetchFromMs,
1052
+ resolveDerivativesContextRequiredFetchFromMs,
942
1053
  shouldBackfillDerivativesContextForBacktest,
943
1054
  shouldBackfillDerivativesContextForSignals
944
1055
  });