@tradejs/cli 2.0.1 → 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.
package/dist/cli.js CHANGED
@@ -3005,7 +3005,7 @@ var init_aiTrainDataset = __esm({
3005
3005
  });
3006
3006
 
3007
3007
  // src/lib/aiTrainMetrics.ts
3008
- var divideOrNull, DAY_MS, DAYS_PER_WEEK, DAYS_PER_MONTH, getEvaluationPeriodDays, qualitySortKey, emptyRiskSummary, summarizeApprovedRisk, summarizeAiTrainEvaluations, getDirectionSortKey, getMonthKey, isDirectionMatchAtThreshold, summarizeAiTrainEvaluationsByDirection, summarizeAiTrainEvaluationsByMonth, summarizeAiTrainEvaluationsByQualityThreshold;
3008
+ var divideOrNull, DAY_MS, DAYS_PER_WEEK, DAYS_PER_MONTH, DAYS_PER_YEAR, getEvaluationPeriodDays, qualitySortKey, calculateTradePnlRiskRatios, emptyRiskSummary, summarizeApprovedRisk, summarizeAiTrainEvaluations, getDirectionSortKey, getMonthKey, isDirectionMatchAtThreshold, summarizeAiTrainEvaluationsByDirection, summarizeAiTrainEvaluationsByMonth, summarizeAiTrainEvaluationsByQualityThreshold;
3009
3009
  var init_aiTrainMetrics = __esm({
3010
3010
  "src/lib/aiTrainMetrics.ts"() {
3011
3011
  "use strict";
@@ -3018,6 +3018,7 @@ var init_aiTrainMetrics = __esm({
3018
3018
  DAY_MS = 24 * 60 * 60 * 1e3;
3019
3019
  DAYS_PER_WEEK = 7;
3020
3020
  DAYS_PER_MONTH = 30.4375;
3021
+ DAYS_PER_YEAR = 365;
3021
3022
  getEvaluationPeriodDays = (evaluations) => {
3022
3023
  let minTimestamp = null;
3023
3024
  let maxTimestamp = null;
@@ -3039,6 +3040,40 @@ var init_aiTrainMetrics = __esm({
3039
3040
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
3040
3041
  };
3041
3042
  qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
3043
+ calculateTradePnlRiskRatios = ({
3044
+ profits,
3045
+ totalProfit,
3046
+ maxDrawdown,
3047
+ periodDays
3048
+ }) => {
3049
+ if (!profits.length || periodDays == null || periodDays <= 0) {
3050
+ return {
3051
+ sharpeRatio: null,
3052
+ sortinoRatio: null,
3053
+ calmarRatio: null
3054
+ };
3055
+ }
3056
+ const meanProfit = totalProfit / profits.length;
3057
+ const variance = profits.reduce((sum2, profit) => {
3058
+ const diff = profit - meanProfit;
3059
+ return sum2 + diff * diff;
3060
+ }, 0) / profits.length;
3061
+ const stdDev = Math.sqrt(variance);
3062
+ const downsideDeviation = Math.sqrt(
3063
+ profits.reduce(
3064
+ (sum2, profit) => profit < 0 ? sum2 + profit * profit : sum2,
3065
+ 0
3066
+ ) / profits.length
3067
+ );
3068
+ const annualizationScale = Math.sqrt(
3069
+ profits.length / periodDays * DAYS_PER_YEAR
3070
+ );
3071
+ return {
3072
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
3073
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
3074
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
3075
+ };
3076
+ };
3042
3077
  emptyRiskSummary = () => ({
3043
3078
  trades: 0,
3044
3079
  totalProfit: 0,
@@ -3054,12 +3089,15 @@ var init_aiTrainMetrics = __esm({
3054
3089
  maxDrawdown: 0,
3055
3090
  maxDrawdownPctOfGrossProfit: null,
3056
3091
  maxDrawdownPctOfTotalProfit: null,
3092
+ sharpeRatio: null,
3093
+ sortinoRatio: null,
3094
+ calmarRatio: null,
3057
3095
  recoveryFactor: null,
3058
3096
  ulcerIndex: null,
3059
3097
  maxConsecutiveWins: 0,
3060
3098
  maxConsecutiveLosses: 0
3061
3099
  });
3062
- summarizeApprovedRisk = (evaluations) => {
3100
+ summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
3063
3101
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
3064
3102
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
3065
3103
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -3082,8 +3120,10 @@ var init_aiTrainMetrics = __esm({
3082
3120
  let currentLossStreak = 0;
3083
3121
  let maxConsecutiveWins = 0;
3084
3122
  let maxConsecutiveLosses = 0;
3123
+ const approvedProfits = [];
3085
3124
  for (const evaluation of approvedEvaluations) {
3086
3125
  const profit = evaluation.profit;
3126
+ approvedProfits.push(profit);
3087
3127
  if (profit > 0) {
3088
3128
  grossProfit += profit;
3089
3129
  wins += 1;
@@ -3111,6 +3151,12 @@ var init_aiTrainMetrics = __esm({
3111
3151
  const totalProfit = grossProfit - grossLoss;
3112
3152
  const avgWin = divideOrNull(grossProfit, wins);
3113
3153
  const avgLoss = divideOrNull(grossLoss, losses);
3154
+ const riskRatios = calculateTradePnlRiskRatios({
3155
+ profits: approvedProfits,
3156
+ totalProfit,
3157
+ maxDrawdown,
3158
+ periodDays
3159
+ });
3114
3160
  return {
3115
3161
  trades: approvedEvaluations.length,
3116
3162
  totalProfit,
@@ -3126,6 +3172,7 @@ var init_aiTrainMetrics = __esm({
3126
3172
  maxDrawdown,
3127
3173
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
3128
3174
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
3175
+ ...riskRatios,
3129
3176
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
3130
3177
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
3131
3178
  maxConsecutiveWins,
@@ -3224,7 +3271,7 @@ var init_aiTrainMetrics = __esm({
3224
3271
  avgApprovedTradesPerDay,
3225
3272
  avgApprovedTradesPerWeek,
3226
3273
  expectancyDelta,
3227
- approvedRisk: summarizeApprovedRisk(evaluations),
3274
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
3228
3275
  qualityBuckets: [...bucketMap.values()].sort(
3229
3276
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
3230
3277
  )
@@ -6135,7 +6182,7 @@ var init_aiTrainEvaluationDump = __esm({
6135
6182
  });
6136
6183
 
6137
6184
  // src/lib/aiTrainResearch.ts
6138
- var import_node_child_process2, import_node_crypto2, import_promises4, import_node_path3, DAY_MS3, RESEARCH_CONTEXT_ENV_KEYS, normalizeForStableJson, fingerprintResearchValue, getTimestampRange, summarizeAiTrainCoverage, summarizeAiTrainRejectReasons, summarizeAiTrainTerminalWindows, readOptionalFile, getGitLineage, resolveGateFingerprint, buildAiTrainLineage;
6185
+ var import_node_child_process2, import_node_crypto2, import_promises4, import_node_path3, DAY_MS3, RESEARCH_CONTEXT_ENV_KEYS, DERIVATIVES_CONTEXT_DATA_MODEL, normalizeForStableJson, fingerprintResearchValue, getTimestampRange, summarizeAiTrainCoverage, summarizeAiTrainRejectReasons, summarizeAiTrainTerminalWindows, readOptionalFile, getGitLineage, resolveGateFingerprint, buildAiTrainLineage;
6139
6186
  var init_aiTrainResearch = __esm({
6140
6187
  "src/lib/aiTrainResearch.ts"() {
6141
6188
  "use strict";
@@ -6164,6 +6211,12 @@ var init_aiTrainResearch = __esm({
6164
6211
  "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
6165
6212
  "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
6166
6213
  ];
6214
+ DERIVATIVES_CONTEXT_DATA_MODEL = {
6215
+ derivativesSourceIntervals: "15m",
6216
+ derivativesDerivedIntervals: "1h",
6217
+ derivativesHourlyFallback: "stored-1h",
6218
+ derivativesDataModelVersion: 2
6219
+ };
6167
6220
  normalizeForStableJson = (value) => {
6168
6221
  if (Array.isArray(value)) {
6169
6222
  return value.map(normalizeForStableJson);
@@ -6276,6 +6329,7 @@ var init_aiTrainResearch = __esm({
6276
6329
  const relativeCandidates = [
6277
6330
  `packages/strategies/src/${strategyName}/adapters/ai.ts`,
6278
6331
  `packages/strategies/src/${strategyName}/guardrails.ts`,
6332
+ `packages/strategies/src/${strategyName}/pockets.ts`,
6279
6333
  `packages/strategies/src/${strategyName}/config.ts`,
6280
6334
  "packages/node/src/ai.ts"
6281
6335
  ];
@@ -6322,7 +6376,8 @@ var init_aiTrainResearch = __esm({
6322
6376
  ...Object.fromEntries(
6323
6377
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
6324
6378
  ),
6325
- ...runContext
6379
+ ...runContext,
6380
+ ...DERIVATIVES_CONTEXT_DATA_MODEL
6326
6381
  };
6327
6382
  return {
6328
6383
  gitSha,
@@ -7402,6 +7457,9 @@ var init_aiTrain = __esm({
7402
7457
  import_chalk5.default.gray("CALENDAR/D"),
7403
7458
  import_chalk5.default.gray("WR"),
7404
7459
  import_chalk5.default.gray("PF"),
7460
+ import_chalk5.default.gray("SHARPE"),
7461
+ import_chalk5.default.gray("SORTINO"),
7462
+ import_chalk5.default.gray("CALMAR"),
7405
7463
  import_chalk5.default.gray("PNL"),
7406
7464
  import_chalk5.default.gray("MAX_DD"),
7407
7465
  import_chalk5.default.gray("TOP REJECT")
@@ -7416,6 +7474,9 @@ var init_aiTrain = __esm({
7416
7474
  colorizeMetricNumber(window2.approvedPerCalendarDay),
7417
7475
  colorizeRatio2(window2.outcome.approvedRisk.winRate),
7418
7476
  colorizeMetricNumber(window2.outcome.approvedRisk.profitFactor),
7477
+ colorizeMetricNumber(window2.outcome.approvedRisk.sharpeRatio),
7478
+ colorizeMetricNumber(window2.outcome.approvedRisk.sortinoRatio),
7479
+ colorizeMetricNumber(window2.outcome.approvedRisk.calmarRatio),
7419
7480
  colorizeProfit2(window2.outcome.approvedRisk.totalProfit),
7420
7481
  colorizeProfit2(-window2.outcome.approvedRisk.maxDrawdown),
7421
7482
  import_chalk5.default.gray(
@@ -7464,6 +7525,18 @@ var init_aiTrain = __esm({
7464
7525
  "profit_factor",
7465
7526
  colorizeMetricNumber(summary.approvedRisk.profitFactor)
7466
7527
  ],
7528
+ [
7529
+ "sharpe_ratio",
7530
+ colorizeMetricNumber(summary.approvedRisk.sharpeRatio)
7531
+ ],
7532
+ [
7533
+ "sortino_ratio",
7534
+ colorizeMetricNumber(summary.approvedRisk.sortinoRatio)
7535
+ ],
7536
+ [
7537
+ "calmar_ratio",
7538
+ colorizeMetricNumber(summary.approvedRisk.calmarRatio)
7539
+ ],
7467
7540
  [
7468
7541
  "payoff_ratio",
7469
7542
  colorizeMetricNumber(summary.approvedRisk.payoffRatio)
@@ -8590,7 +8663,7 @@ var init_binanceMarketContextBackfill = __esm({
8590
8663
  });
8591
8664
 
8592
8665
  // src/lib/derivativesContextBackfill.ts
8593
- var import_chalk7, import_progress4, import_async, import_constants5, import_indicators, import_timescale2, import_userSettings2, HOUR_MS, DAY_MS6, DEFAULT_LOOKBACK_HOURS, SOURCE_INTERVALS, coinalyzeIntervalMap, derivativesIntervalMs, lastRequestTs, asInt2, parseList, parseBooleanFlag, normalizeSymbols, isDerivativesTargetContextEnabled, resolveDerivativesContextBackfillSymbols, chunkArray, isBacktestDerivativesContextEnabled, isSignalsDerivativesContextEnabled, isDerivativesContextBackfillEnabled, shouldBackfillDerivativesContextForBacktest, shouldBackfillDerivativesContextForSignals, resolveDerivativesContextIntervals, resolveDerivativesContextLookbackMs, resolveDerivativesContextBackfillWindow, resolveDerivativesContextIntervalBackfillWindow, resolveDerivativesContextMissingFetchFromMs, resolveDerivativesContextFetchFromMs, countBackfillWindows, buildBackfillWindows, hasDerivativesWindowCoverage, resolveDerivativesContextMissingCoverageFetchFromMs, groupDerivativesContextMissingFetchRanges, getMissingClosedDerivativesSymbols, coverageKey, extendEdges, getCoinalyzeApiKey, getCoinalyzeBaseUrl, getRequestDelayMs, getRequestTimeoutMs, networkErrorCodes, getNestedErrorValue, getCoinalyzeErrorCause, isRetryableCoinalyzeFetchError, formatCoinalyzeRequestError, fetchJsonWithRateLimit, fetchCoinalyzeMarkets, selectBestMarket, buildMatches, normalizeMetricPoint, toSeriesMap, fetchMetricBatch, skippedBackfillResult2, backfillDerivativesContext, backfillDerivativesContextForBacktest, backfillDerivativesContextForSignals;
8666
+ var import_chalk7, import_progress4, import_async, import_constants5, import_indicators, import_timescale2, import_userSettings2, HOUR_MS, DAY_MS6, DEFAULT_LOOKBACK_HOURS, SOURCE_INTERVALS, coinalyzeIntervalMap, derivativesIntervalMs, lastRequestTs, asInt2, parseList, parseBooleanFlag, normalizeSymbols, isDerivativesTargetContextEnabled, resolveDerivativesContextBackfillSymbols, chunkArray, isBacktestDerivativesContextEnabled, isSignalsDerivativesContextEnabled, isDerivativesContextBackfillEnabled, shouldBackfillDerivativesContextForBacktest, shouldBackfillDerivativesContextForSignals, resolveDerivativesContextIntervals, resolveDerivativesContextLookbackMs, resolveDerivativesContextBackfillWindow, resolveDerivativesContextIntervalBackfillWindow, resolveDerivativesContextMissingFetchFromMs, resolveDerivativesContextFetchFromMs, resolveDerivativesContextRequiredFetchFromMs, countBackfillWindows, buildBackfillWindows, hasDerivativesWindowCoverage, resolveDerivativesContextMissingCoverageFetchFromMs, groupDerivativesContextMissingFetchRanges, getMissingClosedDerivativesSymbols, coverageKey, extendEdges, getCoinalyzeApiKey, getCoinalyzeBaseUrl, getRequestDelayMs, getRequestTimeoutMs, networkErrorCodes, getNestedErrorValue, getCoinalyzeErrorCause, isRetryableCoinalyzeFetchError, formatCoinalyzeRequestError, fetchJsonWithRateLimit, fetchCoinalyzeMarkets, selectBestMarket, buildMatches, normalizeMetricPoint, toSeriesMap, fetchMetricBatch, skippedBackfillResult2, backfillDerivativesContext, backfillDerivativesContextForBacktest, backfillDerivativesContextForSignals;
8594
8667
  var init_derivativesContextBackfill = __esm({
8595
8668
  "src/lib/derivativesContextBackfill.ts"() {
8596
8669
  "use strict";
@@ -8722,6 +8795,38 @@ var init_derivativesContextBackfill = __esm({
8722
8795
  return null;
8723
8796
  };
8724
8797
  resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
8798
+ resolveDerivativesContextRequiredFetchFromMs = (params) => {
8799
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
8800
+ ranges: params.dataCoverageRanges,
8801
+ fromMs: params.fromMs,
8802
+ toMs: params.toMs,
8803
+ intervalMs: params.intervalMs
8804
+ });
8805
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
8806
+ edges: params.edges,
8807
+ fromMs: params.fromMs,
8808
+ toMs: params.toMs,
8809
+ intervalMs: params.intervalMs,
8810
+ refreshClosedTail: params.mode === "signals"
8811
+ });
8812
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
8813
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
8814
+ interval: params.interval,
8815
+ fromMs: params.fromMs,
8816
+ toMs: params.toMs,
8817
+ nowMs: params.nowMs
8818
+ });
8819
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
8820
+ ranges: params.liquidationCoverageRanges,
8821
+ fromMs: confirmedLiquidationWindow.fromMs,
8822
+ toMs: confirmedLiquidationWindow.toMs,
8823
+ intervalMs: params.intervalMs
8824
+ }) : null;
8825
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
8826
+ (value) => value != null
8827
+ );
8828
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
8829
+ };
8725
8830
  countBackfillWindows = (params) => {
8726
8831
  let count = 0;
8727
8832
  let cursor = params.fromMs;
@@ -9057,7 +9162,8 @@ var init_derivativesContextBackfill = __esm({
9057
9162
  mode,
9058
9163
  startMs,
9059
9164
  endMs,
9060
- preloadStartMs: params.preloadStartMs
9165
+ preloadStartMs: params.preloadStartMs,
9166
+ nowMs: Date.now()
9061
9167
  });
9062
9168
  if (safeEndMs <= fromMs) {
9063
9169
  return skippedBackfillResult2();
@@ -9091,15 +9197,33 @@ var init_derivativesContextBackfill = __esm({
9091
9197
  );
9092
9198
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
9093
9199
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
9200
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
9201
+ const coverageNowMs = Date.now();
9094
9202
  await Promise.all(
9095
9203
  intervalWindows.map(async (window2) => {
9096
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
9097
- source: "coinalyze",
9098
- symbols,
9204
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9099
9205
  interval: window2.interval,
9100
9206
  fromMs: window2.fromMs,
9101
- toMs: window2.toMs
9207
+ toMs: window2.toMs,
9208
+ nowMs: coverageNowMs
9102
9209
  });
9210
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
9211
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
9212
+ source: "coinalyze",
9213
+ symbols,
9214
+ interval: window2.interval,
9215
+ fromMs: window2.fromMs,
9216
+ toMs: window2.toMs
9217
+ }),
9218
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
9219
+ source: "coinalyze",
9220
+ metric: "liquidation",
9221
+ symbols,
9222
+ interval: window2.interval,
9223
+ fromMs: confirmedLiquidationWindow.fromMs,
9224
+ toMs: confirmedLiquidationWindow.toMs
9225
+ }) : Promise.resolve([])
9226
+ ]);
9103
9227
  coverageKeysByInterval.set(
9104
9228
  window2.interval,
9105
9229
  new Set(
@@ -9121,6 +9245,17 @@ var init_derivativesContextBackfill = __esm({
9121
9245
  rangesBySymbol.set(symbol, ranges);
9122
9246
  }
9123
9247
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
9248
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
9249
+ for (const row of liquidationCoverageRows) {
9250
+ const symbol = row.symbol.toUpperCase();
9251
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
9252
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
9253
+ liquidationRangesBySymbol.set(symbol, ranges);
9254
+ }
9255
+ liquidationCoverageRangesByInterval.set(
9256
+ window2.interval,
9257
+ liquidationRangesBySymbol
9258
+ );
9124
9259
  })
9125
9260
  );
9126
9261
  const cachedWindows = intervalWindows.reduce(
@@ -9145,7 +9280,7 @@ var init_derivativesContextBackfill = __esm({
9145
9280
  (symbol) => backfillWindows.every((backfillWindow) => {
9146
9281
  const normalizedSymbol = symbol.toUpperCase();
9147
9282
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
9148
- return hasDerivativesWindowCoverage({
9283
+ const dataCovered = hasDerivativesWindowCoverage({
9149
9284
  edges: edgesBySymbol?.get(normalizedSymbol),
9150
9285
  fromMs: backfillWindow.fromMs,
9151
9286
  toMs: backfillWindow.toMs
@@ -9162,6 +9297,19 @@ var init_derivativesContextBackfill = __esm({
9162
9297
  toMs: backfillWindow.toMs
9163
9298
  })
9164
9299
  );
9300
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9301
+ interval: window2.interval,
9302
+ fromMs: backfillWindow.fromMs,
9303
+ toMs: backfillWindow.toMs,
9304
+ nowMs: coverageNowMs
9305
+ });
9306
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
9307
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
9308
+ fromMs: confirmedLiquidationWindow.fromMs,
9309
+ toMs: confirmedLiquidationWindow.toMs,
9310
+ intervalMs: window2.intervalMs
9311
+ }) == null;
9312
+ return dataCovered && liquidationCovered;
9165
9313
  })
9166
9314
  );
9167
9315
  });
@@ -9246,6 +9394,11 @@ var init_derivativesContextBackfill = __esm({
9246
9394
  coverageKeysByInterval.set(interval5, coverageKeys);
9247
9395
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
9248
9396
  coverageRangesByInterval.set(interval5, coverageRangesBySymbol);
9397
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
9398
+ liquidationCoverageRangesByInterval.set(
9399
+ interval5,
9400
+ liquidationCoverageRangesBySymbol
9401
+ );
9249
9402
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
9250
9403
  const batch = symbolBatches[batchIdx];
9251
9404
  let cursor = window2.fromMs;
@@ -9258,33 +9411,25 @@ var init_derivativesContextBackfill = __esm({
9258
9411
  fromMs: cursor,
9259
9412
  toMs
9260
9413
  });
9261
- if (coverageKeys.has(key)) {
9262
- return null;
9263
- }
9264
9414
  const normalizedSymbol = item.symbol.toUpperCase();
9265
- const coverageFromMs = mode === "signals" ? cursor : resolveDerivativesContextMissingCoverageFetchFromMs({
9266
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
9267
- fromMs: cursor,
9268
- toMs,
9269
- intervalMs: intervalMs2
9270
- });
9271
- if (coverageFromMs == null) {
9272
- return null;
9273
- }
9274
- const edges = edgesBySymbol.get(normalizedSymbol);
9275
- const edgesFromMs = resolveDerivativesContextFetchFromMs({
9276
- edges,
9277
- fromMs: cursor,
9278
- toMs,
9279
- intervalMs: intervalMs2,
9280
- refreshClosedTail: mode === "signals"
9281
- });
9282
- if (edgesFromMs == null) {
9283
- return null;
9284
- }
9415
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
9416
+ {
9417
+ mode,
9418
+ interval: interval5,
9419
+ intervalMs: intervalMs2,
9420
+ fromMs: cursor,
9421
+ toMs,
9422
+ nowMs: coverageNowMs,
9423
+ dataCoverageKeyExists: coverageKeys.has(key),
9424
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
9425
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
9426
+ edges: edgesBySymbol.get(normalizedSymbol)
9427
+ }
9428
+ );
9429
+ if (requiredFromMs == null) return null;
9285
9430
  return {
9286
9431
  item,
9287
- fromMs: Math.max(coverageFromMs, edgesFromMs)
9432
+ fromMs: requiredFromMs
9288
9433
  };
9289
9434
  }).filter(
9290
9435
  (item) => item != null
@@ -9309,6 +9454,7 @@ var init_derivativesContextBackfill = __esm({
9309
9454
  );
9310
9455
  let rows = [];
9311
9456
  let missingClosedSymbols = [];
9457
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
9312
9458
  for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
9313
9459
  const oiMap = await fetchMetricBatch({
9314
9460
  endpoint: oiPath,
@@ -9328,7 +9474,7 @@ var init_derivativesContextBackfill = __esm({
9328
9474
  fromMs: group.fromMs,
9329
9475
  toMs
9330
9476
  });
9331
- const liqMap = await fetchMetricBatch({
9477
+ liquidationRowsByMarket = await fetchMetricBatch({
9332
9478
  endpoint: liqPath,
9333
9479
  metric: "liq",
9334
9480
  marketSymbols,
@@ -9343,7 +9489,7 @@ var init_derivativesContextBackfill = __esm({
9343
9489
  symbol: item.symbol,
9344
9490
  oiRaw: oiMap.get(marketSymbol) ?? [],
9345
9491
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
9346
- liqRaw: liqMap.get(marketSymbol) ?? []
9492
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
9347
9493
  });
9348
9494
  return (0, import_indicators.coinalyzePointsToRows)(points, interval5, "coinalyze");
9349
9495
  });
@@ -9366,6 +9512,42 @@ var init_derivativesContextBackfill = __esm({
9366
9512
  await (0, import_timescale2.upsertDerivatives)(rows);
9367
9513
  totalRows += rows.length;
9368
9514
  }
9515
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9516
+ interval: interval5,
9517
+ fromMs: group.fromMs,
9518
+ toMs,
9519
+ nowMs: coverageNowMs
9520
+ });
9521
+ if (confirmedLiquidationWindow) {
9522
+ const metricCoverageRows = missingBatch.map((item) => {
9523
+ const marketSymbol = item.marketSymbol.toUpperCase();
9524
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
9525
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
9526
+ point.t ?? point.ts ?? point.time ?? point.timestamp
9527
+ );
9528
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
9529
+ }).length;
9530
+ return {
9531
+ source: "coinalyze",
9532
+ metric: "liquidation",
9533
+ symbol: item.symbol,
9534
+ interval: interval5,
9535
+ fromMs: confirmedLiquidationWindow.fromMs,
9536
+ toMs: confirmedLiquidationWindow.toMs,
9537
+ eventRowsCount
9538
+ };
9539
+ });
9540
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
9541
+ for (const coverageRow of metricCoverageRows) {
9542
+ const symbol = coverageRow.symbol.toUpperCase();
9543
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
9544
+ ranges.push({
9545
+ fromMs: coverageRow.fromMs,
9546
+ toMs: coverageRow.toMs
9547
+ });
9548
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
9549
+ }
9550
+ }
9369
9551
  if (mode === "backtest") {
9370
9552
  const rowsCountBySymbol = /* @__PURE__ */ new Map();
9371
9553
  for (const row of rows) {
@@ -19393,6 +19575,36 @@ var init_derivativesIngestCoinalyzeAll = __esm({
19393
19575
  await (0, import_timescale7.upsertDerivatives)(rows);
19394
19576
  totalRows += rows.length;
19395
19577
  }
19578
+ const confirmedLiquidationWindow = (0, import_indicators3.resolveCoinalyzeConfirmedIntradayCoverage)({
19579
+ interval: interval5,
19580
+ fromMs: cursor,
19581
+ toMs,
19582
+ nowMs: now2
19583
+ });
19584
+ if (confirmedLiquidationWindow) {
19585
+ await (0, import_timescale7.applyDerivativesMetricCoverage)(
19586
+ batch.map((item) => {
19587
+ const marketSymbol = item.marketSymbol.toUpperCase();
19588
+ const eventRowsCount = (liqMap.get(marketSymbol) ?? []).filter(
19589
+ (point) => {
19590
+ const timestamp = (0, import_indicators3.toCoinalyzeTimestampMs)(
19591
+ point.t ?? point.ts ?? point.time ?? point.timestamp
19592
+ );
19593
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
19594
+ }
19595
+ ).length;
19596
+ return {
19597
+ source: "coinalyze",
19598
+ metric: "liquidation",
19599
+ symbol: item.symbol,
19600
+ interval: interval5,
19601
+ fromMs: confirmedLiquidationWindow.fromMs,
19602
+ toMs: confirmedLiquidationWindow.toMs,
19603
+ eventRowsCount
19604
+ };
19605
+ })
19606
+ );
19607
+ }
19396
19608
  } catch (error) {
19397
19609
  failedWindows += 1;
19398
19610
  console.error(
@@ -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
  )