@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.
@@ -2552,6 +2552,38 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
2552
2552
  return null;
2553
2553
  };
2554
2554
  var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
2555
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
2556
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
2557
+ ranges: params.dataCoverageRanges,
2558
+ fromMs: params.fromMs,
2559
+ toMs: params.toMs,
2560
+ intervalMs: params.intervalMs
2561
+ });
2562
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
2563
+ edges: params.edges,
2564
+ fromMs: params.fromMs,
2565
+ toMs: params.toMs,
2566
+ intervalMs: params.intervalMs,
2567
+ refreshClosedTail: params.mode === "signals"
2568
+ });
2569
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
2570
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2571
+ interval: params.interval,
2572
+ fromMs: params.fromMs,
2573
+ toMs: params.toMs,
2574
+ nowMs: params.nowMs
2575
+ });
2576
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
2577
+ ranges: params.liquidationCoverageRanges,
2578
+ fromMs: confirmedLiquidationWindow.fromMs,
2579
+ toMs: confirmedLiquidationWindow.toMs,
2580
+ intervalMs: params.intervalMs
2581
+ }) : null;
2582
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
2583
+ (value) => value != null
2584
+ );
2585
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
2586
+ };
2555
2587
  var countBackfillWindows = (params) => {
2556
2588
  let count = 0;
2557
2589
  let cursor = params.fromMs;
@@ -2887,7 +2919,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2887
2919
  mode,
2888
2920
  startMs,
2889
2921
  endMs,
2890
- preloadStartMs: params.preloadStartMs
2922
+ preloadStartMs: params.preloadStartMs,
2923
+ nowMs: Date.now()
2891
2924
  });
2892
2925
  if (safeEndMs <= fromMs) {
2893
2926
  return skippedBackfillResult2();
@@ -2921,15 +2954,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2921
2954
  );
2922
2955
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
2923
2956
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
2957
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
2958
+ const coverageNowMs = Date.now();
2924
2959
  await Promise.all(
2925
2960
  intervalWindows.map(async (window2) => {
2926
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
2927
- source: "coinalyze",
2928
- symbols,
2961
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2929
2962
  interval: window2.interval,
2930
2963
  fromMs: window2.fromMs,
2931
- toMs: window2.toMs
2964
+ toMs: window2.toMs,
2965
+ nowMs: coverageNowMs
2932
2966
  });
2967
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
2968
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
2969
+ source: "coinalyze",
2970
+ symbols,
2971
+ interval: window2.interval,
2972
+ fromMs: window2.fromMs,
2973
+ toMs: window2.toMs
2974
+ }),
2975
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
2976
+ source: "coinalyze",
2977
+ metric: "liquidation",
2978
+ symbols,
2979
+ interval: window2.interval,
2980
+ fromMs: confirmedLiquidationWindow.fromMs,
2981
+ toMs: confirmedLiquidationWindow.toMs
2982
+ }) : Promise.resolve([])
2983
+ ]);
2933
2984
  coverageKeysByInterval.set(
2934
2985
  window2.interval,
2935
2986
  new Set(
@@ -2951,6 +3002,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2951
3002
  rangesBySymbol.set(symbol, ranges);
2952
3003
  }
2953
3004
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3005
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3006
+ for (const row of liquidationCoverageRows) {
3007
+ const symbol = row.symbol.toUpperCase();
3008
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3009
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3010
+ liquidationRangesBySymbol.set(symbol, ranges);
3011
+ }
3012
+ liquidationCoverageRangesByInterval.set(
3013
+ window2.interval,
3014
+ liquidationRangesBySymbol
3015
+ );
2954
3016
  })
2955
3017
  );
2956
3018
  const cachedWindows = intervalWindows.reduce(
@@ -2975,7 +3037,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2975
3037
  (symbol) => backfillWindows.every((backfillWindow) => {
2976
3038
  const normalizedSymbol = symbol.toUpperCase();
2977
3039
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
2978
- return hasDerivativesWindowCoverage({
3040
+ const dataCovered = hasDerivativesWindowCoverage({
2979
3041
  edges: edgesBySymbol?.get(normalizedSymbol),
2980
3042
  fromMs: backfillWindow.fromMs,
2981
3043
  toMs: backfillWindow.toMs
@@ -2992,6 +3054,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2992
3054
  toMs: backfillWindow.toMs
2993
3055
  })
2994
3056
  );
3057
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3058
+ interval: window2.interval,
3059
+ fromMs: backfillWindow.fromMs,
3060
+ toMs: backfillWindow.toMs,
3061
+ nowMs: coverageNowMs
3062
+ });
3063
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3064
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3065
+ fromMs: confirmedLiquidationWindow.fromMs,
3066
+ toMs: confirmedLiquidationWindow.toMs,
3067
+ intervalMs: window2.intervalMs
3068
+ }) == null;
3069
+ return dataCovered && liquidationCovered;
2995
3070
  })
2996
3071
  );
2997
3072
  });
@@ -3076,6 +3151,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3076
3151
  coverageKeysByInterval.set(interval, coverageKeys);
3077
3152
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3078
3153
  coverageRangesByInterval.set(interval, coverageRangesBySymbol);
3154
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3155
+ liquidationCoverageRangesByInterval.set(
3156
+ interval,
3157
+ liquidationCoverageRangesBySymbol
3158
+ );
3079
3159
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3080
3160
  const batch = symbolBatches[batchIdx];
3081
3161
  let cursor = window2.fromMs;
@@ -3088,33 +3168,25 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3088
3168
  fromMs: cursor,
3089
3169
  toMs
3090
3170
  });
3091
- if (coverageKeys.has(key)) {
3092
- return null;
3093
- }
3094
3171
  const normalizedSymbol = item.symbol.toUpperCase();
3095
- const coverageFromMs = mode === "signals" ? cursor : resolveDerivativesContextMissingCoverageFetchFromMs({
3096
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3097
- fromMs: cursor,
3098
- toMs,
3099
- intervalMs
3100
- });
3101
- if (coverageFromMs == null) {
3102
- return null;
3103
- }
3104
- const edges = edgesBySymbol.get(normalizedSymbol);
3105
- const edgesFromMs = resolveDerivativesContextFetchFromMs({
3106
- edges,
3107
- fromMs: cursor,
3108
- toMs,
3109
- intervalMs,
3110
- refreshClosedTail: mode === "signals"
3111
- });
3112
- if (edgesFromMs == null) {
3113
- return null;
3114
- }
3172
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3173
+ {
3174
+ mode,
3175
+ interval,
3176
+ intervalMs,
3177
+ fromMs: cursor,
3178
+ toMs,
3179
+ nowMs: coverageNowMs,
3180
+ dataCoverageKeyExists: coverageKeys.has(key),
3181
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3182
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3183
+ edges: edgesBySymbol.get(normalizedSymbol)
3184
+ }
3185
+ );
3186
+ if (requiredFromMs == null) return null;
3115
3187
  return {
3116
3188
  item,
3117
- fromMs: Math.max(coverageFromMs, edgesFromMs)
3189
+ fromMs: requiredFromMs
3118
3190
  };
3119
3191
  }).filter(
3120
3192
  (item) => item != null
@@ -3139,6 +3211,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3139
3211
  );
3140
3212
  let rows = [];
3141
3213
  let missingClosedSymbols = [];
3214
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3142
3215
  for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3143
3216
  const oiMap = await fetchMetricBatch({
3144
3217
  endpoint: oiPath,
@@ -3158,7 +3231,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3158
3231
  fromMs: group.fromMs,
3159
3232
  toMs
3160
3233
  });
3161
- const liqMap = await fetchMetricBatch({
3234
+ liquidationRowsByMarket = await fetchMetricBatch({
3162
3235
  endpoint: liqPath,
3163
3236
  metric: "liq",
3164
3237
  marketSymbols,
@@ -3173,7 +3246,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3173
3246
  symbol: item.symbol,
3174
3247
  oiRaw: oiMap.get(marketSymbol) ?? [],
3175
3248
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
3176
- liqRaw: liqMap.get(marketSymbol) ?? []
3249
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3177
3250
  });
3178
3251
  return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3179
3252
  });
@@ -3196,6 +3269,42 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3196
3269
  await (0, import_timescale2.upsertDerivatives)(rows);
3197
3270
  totalRows += rows.length;
3198
3271
  }
3272
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3273
+ interval,
3274
+ fromMs: group.fromMs,
3275
+ toMs,
3276
+ nowMs: coverageNowMs
3277
+ });
3278
+ if (confirmedLiquidationWindow) {
3279
+ const metricCoverageRows = missingBatch.map((item) => {
3280
+ const marketSymbol = item.marketSymbol.toUpperCase();
3281
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3282
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
3283
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3284
+ );
3285
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3286
+ }).length;
3287
+ return {
3288
+ source: "coinalyze",
3289
+ metric: "liquidation",
3290
+ symbol: item.symbol,
3291
+ interval,
3292
+ fromMs: confirmedLiquidationWindow.fromMs,
3293
+ toMs: confirmedLiquidationWindow.toMs,
3294
+ eventRowsCount
3295
+ };
3296
+ });
3297
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3298
+ for (const coverageRow of metricCoverageRows) {
3299
+ const symbol = coverageRow.symbol.toUpperCase();
3300
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3301
+ ranges.push({
3302
+ fromMs: coverageRow.fromMs,
3303
+ toMs: coverageRow.toMs
3304
+ });
3305
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3306
+ }
3307
+ }
3199
3308
  if (mode === "backtest") {
3200
3309
  const rowsCountBySymbol = /* @__PURE__ */ new Map();
3201
3310
  for (const row of rows) {
@@ -1663,6 +1663,7 @@ var divideOrNull = (num, denom) => {
1663
1663
  var DAY_MS = 24 * 60 * 60 * 1e3;
1664
1664
  var DAYS_PER_WEEK = 7;
1665
1665
  var DAYS_PER_MONTH = 30.4375;
1666
+ var DAYS_PER_YEAR = 365;
1666
1667
  var getEvaluationPeriodDays = (evaluations) => {
1667
1668
  let minTimestamp = null;
1668
1669
  let maxTimestamp = null;
@@ -1684,6 +1685,40 @@ var getEvaluationPeriodDays = (evaluations) => {
1684
1685
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
1685
1686
  };
1686
1687
  var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
1688
+ var calculateTradePnlRiskRatios = ({
1689
+ profits,
1690
+ totalProfit,
1691
+ maxDrawdown,
1692
+ periodDays
1693
+ }) => {
1694
+ if (!profits.length || periodDays == null || periodDays <= 0) {
1695
+ return {
1696
+ sharpeRatio: null,
1697
+ sortinoRatio: null,
1698
+ calmarRatio: null
1699
+ };
1700
+ }
1701
+ const meanProfit = totalProfit / profits.length;
1702
+ const variance = profits.reduce((sum, profit) => {
1703
+ const diff = profit - meanProfit;
1704
+ return sum + diff * diff;
1705
+ }, 0) / profits.length;
1706
+ const stdDev = Math.sqrt(variance);
1707
+ const downsideDeviation = Math.sqrt(
1708
+ profits.reduce(
1709
+ (sum, profit) => profit < 0 ? sum + profit * profit : sum,
1710
+ 0
1711
+ ) / profits.length
1712
+ );
1713
+ const annualizationScale = Math.sqrt(
1714
+ profits.length / periodDays * DAYS_PER_YEAR
1715
+ );
1716
+ return {
1717
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
1718
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
1719
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
1720
+ };
1721
+ };
1687
1722
  var emptyRiskSummary = () => ({
1688
1723
  trades: 0,
1689
1724
  totalProfit: 0,
@@ -1699,12 +1734,15 @@ var emptyRiskSummary = () => ({
1699
1734
  maxDrawdown: 0,
1700
1735
  maxDrawdownPctOfGrossProfit: null,
1701
1736
  maxDrawdownPctOfTotalProfit: null,
1737
+ sharpeRatio: null,
1738
+ sortinoRatio: null,
1739
+ calmarRatio: null,
1702
1740
  recoveryFactor: null,
1703
1741
  ulcerIndex: null,
1704
1742
  maxConsecutiveWins: 0,
1705
1743
  maxConsecutiveLosses: 0
1706
1744
  });
1707
- var summarizeApprovedRisk = (evaluations) => {
1745
+ var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
1708
1746
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
1709
1747
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
1710
1748
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -1727,8 +1765,10 @@ var summarizeApprovedRisk = (evaluations) => {
1727
1765
  let currentLossStreak = 0;
1728
1766
  let maxConsecutiveWins = 0;
1729
1767
  let maxConsecutiveLosses = 0;
1768
+ const approvedProfits = [];
1730
1769
  for (const evaluation of approvedEvaluations) {
1731
1770
  const profit = evaluation.profit;
1771
+ approvedProfits.push(profit);
1732
1772
  if (profit > 0) {
1733
1773
  grossProfit += profit;
1734
1774
  wins += 1;
@@ -1756,6 +1796,12 @@ var summarizeApprovedRisk = (evaluations) => {
1756
1796
  const totalProfit = grossProfit - grossLoss;
1757
1797
  const avgWin = divideOrNull(grossProfit, wins);
1758
1798
  const avgLoss = divideOrNull(grossLoss, losses);
1799
+ const riskRatios = calculateTradePnlRiskRatios({
1800
+ profits: approvedProfits,
1801
+ totalProfit,
1802
+ maxDrawdown,
1803
+ periodDays
1804
+ });
1759
1805
  return {
1760
1806
  trades: approvedEvaluations.length,
1761
1807
  totalProfit,
@@ -1771,6 +1817,7 @@ var summarizeApprovedRisk = (evaluations) => {
1771
1817
  maxDrawdown,
1772
1818
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
1773
1819
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
1820
+ ...riskRatios,
1774
1821
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
1775
1822
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
1776
1823
  maxConsecutiveWins,
@@ -1869,7 +1916,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
1869
1916
  avgApprovedTradesPerDay,
1870
1917
  avgApprovedTradesPerWeek,
1871
1918
  expectancyDelta,
1872
- approvedRisk: summarizeApprovedRisk(evaluations),
1919
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
1873
1920
  qualityBuckets: [...bucketMap.values()].sort(
1874
1921
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
1875
1922
  )
@@ -1653,6 +1653,7 @@ var divideOrNull = (num, denom) => {
1653
1653
  var DAY_MS = 24 * 60 * 60 * 1e3;
1654
1654
  var DAYS_PER_WEEK = 7;
1655
1655
  var DAYS_PER_MONTH = 30.4375;
1656
+ var DAYS_PER_YEAR = 365;
1656
1657
  var getEvaluationPeriodDays = (evaluations) => {
1657
1658
  let minTimestamp = null;
1658
1659
  let maxTimestamp = null;
@@ -1674,6 +1675,40 @@ var getEvaluationPeriodDays = (evaluations) => {
1674
1675
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
1675
1676
  };
1676
1677
  var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
1678
+ var calculateTradePnlRiskRatios = ({
1679
+ profits,
1680
+ totalProfit,
1681
+ maxDrawdown,
1682
+ periodDays
1683
+ }) => {
1684
+ if (!profits.length || periodDays == null || periodDays <= 0) {
1685
+ return {
1686
+ sharpeRatio: null,
1687
+ sortinoRatio: null,
1688
+ calmarRatio: null
1689
+ };
1690
+ }
1691
+ const meanProfit = totalProfit / profits.length;
1692
+ const variance = profits.reduce((sum, profit) => {
1693
+ const diff = profit - meanProfit;
1694
+ return sum + diff * diff;
1695
+ }, 0) / profits.length;
1696
+ const stdDev = Math.sqrt(variance);
1697
+ const downsideDeviation = Math.sqrt(
1698
+ profits.reduce(
1699
+ (sum, profit) => profit < 0 ? sum + profit * profit : sum,
1700
+ 0
1701
+ ) / profits.length
1702
+ );
1703
+ const annualizationScale = Math.sqrt(
1704
+ profits.length / periodDays * DAYS_PER_YEAR
1705
+ );
1706
+ return {
1707
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
1708
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
1709
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
1710
+ };
1711
+ };
1677
1712
  var emptyRiskSummary = () => ({
1678
1713
  trades: 0,
1679
1714
  totalProfit: 0,
@@ -1689,12 +1724,15 @@ var emptyRiskSummary = () => ({
1689
1724
  maxDrawdown: 0,
1690
1725
  maxDrawdownPctOfGrossProfit: null,
1691
1726
  maxDrawdownPctOfTotalProfit: null,
1727
+ sharpeRatio: null,
1728
+ sortinoRatio: null,
1729
+ calmarRatio: null,
1692
1730
  recoveryFactor: null,
1693
1731
  ulcerIndex: null,
1694
1732
  maxConsecutiveWins: 0,
1695
1733
  maxConsecutiveLosses: 0
1696
1734
  });
1697
- var summarizeApprovedRisk = (evaluations) => {
1735
+ var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
1698
1736
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
1699
1737
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
1700
1738
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -1717,8 +1755,10 @@ var summarizeApprovedRisk = (evaluations) => {
1717
1755
  let currentLossStreak = 0;
1718
1756
  let maxConsecutiveWins = 0;
1719
1757
  let maxConsecutiveLosses = 0;
1758
+ const approvedProfits = [];
1720
1759
  for (const evaluation of approvedEvaluations) {
1721
1760
  const profit = evaluation.profit;
1761
+ approvedProfits.push(profit);
1722
1762
  if (profit > 0) {
1723
1763
  grossProfit += profit;
1724
1764
  wins += 1;
@@ -1746,6 +1786,12 @@ var summarizeApprovedRisk = (evaluations) => {
1746
1786
  const totalProfit = grossProfit - grossLoss;
1747
1787
  const avgWin = divideOrNull(grossProfit, wins);
1748
1788
  const avgLoss = divideOrNull(grossLoss, losses);
1789
+ const riskRatios = calculateTradePnlRiskRatios({
1790
+ profits: approvedProfits,
1791
+ totalProfit,
1792
+ maxDrawdown,
1793
+ periodDays
1794
+ });
1749
1795
  return {
1750
1796
  trades: approvedEvaluations.length,
1751
1797
  totalProfit,
@@ -1761,6 +1807,7 @@ var summarizeApprovedRisk = (evaluations) => {
1761
1807
  maxDrawdown,
1762
1808
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
1763
1809
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
1810
+ ...riskRatios,
1764
1811
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
1765
1812
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
1766
1813
  maxConsecutiveWins,
@@ -1859,7 +1906,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
1859
1906
  avgApprovedTradesPerDay,
1860
1907
  avgApprovedTradesPerWeek,
1861
1908
  expectancyDelta,
1862
- approvedRisk: summarizeApprovedRisk(evaluations),
1909
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
1863
1910
  qualityBuckets: [...bucketMap.values()].sort(
1864
1911
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
1865
1912
  )
@@ -2523,6 +2570,12 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
2523
2570
  "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
2524
2571
  "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
2525
2572
  ];
2573
+ var DERIVATIVES_CONTEXT_DATA_MODEL = {
2574
+ derivativesSourceIntervals: "15m",
2575
+ derivativesDerivedIntervals: "1h",
2576
+ derivativesHourlyFallback: "stored-1h",
2577
+ derivativesDataModelVersion: 2
2578
+ };
2526
2579
  var normalizeForStableJson = (value) => {
2527
2580
  if (Array.isArray(value)) {
2528
2581
  return value.map(normalizeForStableJson);
@@ -2635,6 +2688,7 @@ var resolveGateFingerprint = async (projectRoot, strategyName, gitSha) => {
2635
2688
  const relativeCandidates = [
2636
2689
  `packages/strategies/src/${strategyName}/adapters/ai.ts`,
2637
2690
  `packages/strategies/src/${strategyName}/guardrails.ts`,
2691
+ `packages/strategies/src/${strategyName}/pockets.ts`,
2638
2692
  `packages/strategies/src/${strategyName}/config.ts`,
2639
2693
  "packages/node/src/ai.ts"
2640
2694
  ];
@@ -2681,7 +2735,8 @@ var buildAiTrainLineage = async ({
2681
2735
  ...Object.fromEntries(
2682
2736
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
2683
2737
  ),
2684
- ...runContext
2738
+ ...runContext,
2739
+ ...DERIVATIVES_CONTEXT_DATA_MODEL
2685
2740
  };
2686
2741
  return {
2687
2742
  gitSha,
@@ -3741,6 +3796,9 @@ var main = async () => {
3741
3796
  import_chalk.default.gray("CALENDAR/D"),
3742
3797
  import_chalk.default.gray("WR"),
3743
3798
  import_chalk.default.gray("PF"),
3799
+ import_chalk.default.gray("SHARPE"),
3800
+ import_chalk.default.gray("SORTINO"),
3801
+ import_chalk.default.gray("CALMAR"),
3744
3802
  import_chalk.default.gray("PNL"),
3745
3803
  import_chalk.default.gray("MAX_DD"),
3746
3804
  import_chalk.default.gray("TOP REJECT")
@@ -3755,6 +3813,9 @@ var main = async () => {
3755
3813
  colorizeMetricNumber(window2.approvedPerCalendarDay),
3756
3814
  colorizeRatio(window2.outcome.approvedRisk.winRate),
3757
3815
  colorizeMetricNumber(window2.outcome.approvedRisk.profitFactor),
3816
+ colorizeMetricNumber(window2.outcome.approvedRisk.sharpeRatio),
3817
+ colorizeMetricNumber(window2.outcome.approvedRisk.sortinoRatio),
3818
+ colorizeMetricNumber(window2.outcome.approvedRisk.calmarRatio),
3758
3819
  colorizeProfit(window2.outcome.approvedRisk.totalProfit),
3759
3820
  colorizeProfit(-window2.outcome.approvedRisk.maxDrawdown),
3760
3821
  import_chalk.default.gray(
@@ -3803,6 +3864,18 @@ var main = async () => {
3803
3864
  "profit_factor",
3804
3865
  colorizeMetricNumber(summary.approvedRisk.profitFactor)
3805
3866
  ],
3867
+ [
3868
+ "sharpe_ratio",
3869
+ colorizeMetricNumber(summary.approvedRisk.sharpeRatio)
3870
+ ],
3871
+ [
3872
+ "sortino_ratio",
3873
+ colorizeMetricNumber(summary.approvedRisk.sortinoRatio)
3874
+ ],
3875
+ [
3876
+ "calmar_ratio",
3877
+ colorizeMetricNumber(summary.approvedRisk.calmarRatio)
3878
+ ],
3806
3879
  [
3807
3880
  "payoff_ratio",
3808
3881
  colorizeMetricNumber(summary.approvedRisk.payoffRatio)