@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.
package/dist/cli.js CHANGED
@@ -2755,7 +2755,8 @@ var init_selectStrategy = __esm({
2755
2755
  "MaStrategy",
2756
2756
  "AdaptiveMomentumRibbon",
2757
2757
  "TrendLine",
2758
- "VolumeDivergence"
2758
+ "VolumeDivergence",
2759
+ "Grid"
2759
2760
  ];
2760
2761
  };
2761
2762
  selectStrategy = async (promptLabel = "Select strategy", options) => {
@@ -3005,7 +3006,7 @@ var init_aiTrainDataset = __esm({
3005
3006
  });
3006
3007
 
3007
3008
  // 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;
3009
+ 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
3010
  var init_aiTrainMetrics = __esm({
3010
3011
  "src/lib/aiTrainMetrics.ts"() {
3011
3012
  "use strict";
@@ -3018,6 +3019,7 @@ var init_aiTrainMetrics = __esm({
3018
3019
  DAY_MS = 24 * 60 * 60 * 1e3;
3019
3020
  DAYS_PER_WEEK = 7;
3020
3021
  DAYS_PER_MONTH = 30.4375;
3022
+ DAYS_PER_YEAR = 365;
3021
3023
  getEvaluationPeriodDays = (evaluations) => {
3022
3024
  let minTimestamp = null;
3023
3025
  let maxTimestamp = null;
@@ -3039,6 +3041,40 @@ var init_aiTrainMetrics = __esm({
3039
3041
  return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
3040
3042
  };
3041
3043
  qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
3044
+ calculateTradePnlRiskRatios = ({
3045
+ profits,
3046
+ totalProfit,
3047
+ maxDrawdown,
3048
+ periodDays
3049
+ }) => {
3050
+ if (!profits.length || periodDays == null || periodDays <= 0) {
3051
+ return {
3052
+ sharpeRatio: null,
3053
+ sortinoRatio: null,
3054
+ calmarRatio: null
3055
+ };
3056
+ }
3057
+ const meanProfit = totalProfit / profits.length;
3058
+ const variance = profits.reduce((sum2, profit) => {
3059
+ const diff = profit - meanProfit;
3060
+ return sum2 + diff * diff;
3061
+ }, 0) / profits.length;
3062
+ const stdDev = Math.sqrt(variance);
3063
+ const downsideDeviation = Math.sqrt(
3064
+ profits.reduce(
3065
+ (sum2, profit) => profit < 0 ? sum2 + profit * profit : sum2,
3066
+ 0
3067
+ ) / profits.length
3068
+ );
3069
+ const annualizationScale = Math.sqrt(
3070
+ profits.length / periodDays * DAYS_PER_YEAR
3071
+ );
3072
+ return {
3073
+ sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
3074
+ sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
3075
+ calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
3076
+ };
3077
+ };
3042
3078
  emptyRiskSummary = () => ({
3043
3079
  trades: 0,
3044
3080
  totalProfit: 0,
@@ -3054,12 +3090,15 @@ var init_aiTrainMetrics = __esm({
3054
3090
  maxDrawdown: 0,
3055
3091
  maxDrawdownPctOfGrossProfit: null,
3056
3092
  maxDrawdownPctOfTotalProfit: null,
3093
+ sharpeRatio: null,
3094
+ sortinoRatio: null,
3095
+ calmarRatio: null,
3057
3096
  recoveryFactor: null,
3058
3097
  ulcerIndex: null,
3059
3098
  maxConsecutiveWins: 0,
3060
3099
  maxConsecutiveLosses: 0
3061
3100
  });
3062
- summarizeApprovedRisk = (evaluations) => {
3101
+ summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
3063
3102
  const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
3064
3103
  const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
3065
3104
  const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
@@ -3082,8 +3121,10 @@ var init_aiTrainMetrics = __esm({
3082
3121
  let currentLossStreak = 0;
3083
3122
  let maxConsecutiveWins = 0;
3084
3123
  let maxConsecutiveLosses = 0;
3124
+ const approvedProfits = [];
3085
3125
  for (const evaluation of approvedEvaluations) {
3086
3126
  const profit = evaluation.profit;
3127
+ approvedProfits.push(profit);
3087
3128
  if (profit > 0) {
3088
3129
  grossProfit += profit;
3089
3130
  wins += 1;
@@ -3111,6 +3152,12 @@ var init_aiTrainMetrics = __esm({
3111
3152
  const totalProfit = grossProfit - grossLoss;
3112
3153
  const avgWin = divideOrNull(grossProfit, wins);
3113
3154
  const avgLoss = divideOrNull(grossLoss, losses);
3155
+ const riskRatios = calculateTradePnlRiskRatios({
3156
+ profits: approvedProfits,
3157
+ totalProfit,
3158
+ maxDrawdown,
3159
+ periodDays
3160
+ });
3114
3161
  return {
3115
3162
  trades: approvedEvaluations.length,
3116
3163
  totalProfit,
@@ -3126,6 +3173,7 @@ var init_aiTrainMetrics = __esm({
3126
3173
  maxDrawdown,
3127
3174
  maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
3128
3175
  maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
3176
+ ...riskRatios,
3129
3177
  recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
3130
3178
  ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
3131
3179
  maxConsecutiveWins,
@@ -3224,7 +3272,7 @@ var init_aiTrainMetrics = __esm({
3224
3272
  avgApprovedTradesPerDay,
3225
3273
  avgApprovedTradesPerWeek,
3226
3274
  expectancyDelta,
3227
- approvedRisk: summarizeApprovedRisk(evaluations),
3275
+ approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
3228
3276
  qualityBuckets: [...bucketMap.values()].sort(
3229
3277
  (a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
3230
3278
  )
@@ -3388,7 +3436,7 @@ var init_aiTrainOptions = __esm({
3388
3436
  });
3389
3437
 
3390
3438
  // src/lib/aiPocketSearch.ts
3391
- var DAY_MS2, DAYS_PER_WEEK2, DAYS_PER_MONTH2, OUTCOME_SEGMENTS, isPlainRecord, isFiniteNumber, isFeaturePrimitive, normalizeFeaturePrimitive, isOutcomePath, isFeaturePathPrefix, isCompactFeaturePathSkipped, addFlattenedFeatures, findFeatureNumber, findFeatureString, addDirectionalDerivedFeatures, collectAiPocketFeatures, formatNumber, formatPredicateValue, roundThreshold, quantileAt, getPeriodDays, createSummaryAccumulator, addSummaryRow, emptyAiPocketSummary, finalizeAiPocketSummary, summarizeSelectedRows, summarizeMask, summarizeRowIndexes, summarizeAiPocketRows, matchesPredicate, buildMask, intersectMasks, buildPredicateListMask, toPublicPredicate, buildAiPocketPredicateResult, scorePositivePocket, scoreNegativePocket, comparePositivePockets, compareNegativePockets, createPocketResult, hashMask, isBetterRepresentativePocket, estimateCombinationCount, searchAiPockets, formatMdNumber, formatMdPercent, escapeMarkdownCell, markdownTable, summaryMetricRows, pocketRows, buildAiPocketMarkdownReport;
3439
+ var DAY_MS2, DAYS_PER_WEEK2, DAYS_PER_MONTH2, OUTCOME_SEGMENTS, isPlainRecord, isFiniteNumber, isFeaturePrimitive, normalizeFeaturePrimitive, isOutcomePath, isFeaturePathPrefix, isCompactFeaturePathSkipped, addFlattenedFeatures, findFeatureNumber, findFeatureString, addDirectionalDerivedFeatures, addSignalRiskDistanceFeatures, collectAiPocketFeatures, formatNumber, formatPredicateValue, roundThreshold, quantileAt, getPeriodDays, createSummaryAccumulator, addSummaryRow, emptyAiPocketSummary, finalizeAiPocketSummary, summarizeSelectedRows, summarizeMask, summarizeRowIndexes, summarizeAiPocketRows, matchesPredicate, buildMask, intersectMasks, buildPredicateListMask, toPublicPredicate, buildAiPocketPredicateResult, scorePositivePocket, scoreNegativePocket, comparePositivePockets, compareNegativePockets, createPocketResult, hashMask, isBetterRepresentativePocket, estimateCombinationCount, searchAiPockets, formatMdNumber, formatMdPercent, escapeMarkdownCell, markdownTable, summaryMetricRows, pocketRows, buildAiPocketMarkdownReport;
3392
3440
  var init_aiPocketSearch = __esm({
3393
3441
  "src/lib/aiPocketSearch.ts"() {
3394
3442
  "use strict";
@@ -3692,6 +3740,23 @@ var init_aiPocketSearch = __esm({
3692
3740
  }
3693
3741
  features["derived.directIndicatorSupportCount"] = supportCount;
3694
3742
  };
3743
+ addSignalRiskDistanceFeatures = ({
3744
+ features,
3745
+ signal
3746
+ }) => {
3747
+ const currentPrice = isFiniteNumber(signal.prices?.currentPrice) ? signal.prices.currentPrice : null;
3748
+ const stopLossPrice = isFiniteNumber(signal.prices?.stopLossPrice) ? signal.prices.stopLossPrice : null;
3749
+ const takeProfitPrice = isFiniteNumber(signal.prices?.takeProfitPrice) ? signal.prices.takeProfitPrice : null;
3750
+ if (currentPrice == null || currentPrice === 0) {
3751
+ return;
3752
+ }
3753
+ if (stopLossPrice != null) {
3754
+ features["derived.stopDistanceBps"] = Math.abs(currentPrice - stopLossPrice) / Math.abs(currentPrice) * 1e4;
3755
+ }
3756
+ if (takeProfitPrice != null) {
3757
+ features["derived.takeProfitDistanceBps"] = Math.abs(takeProfitPrice - currentPrice) / Math.abs(currentPrice) * 1e4;
3758
+ }
3759
+ };
3695
3760
  collectAiPocketFeatures = ({
3696
3761
  payload,
3697
3762
  gateContext,
@@ -3719,6 +3784,7 @@ var init_aiPocketSearch = __esm({
3719
3784
  maxDepth: 8,
3720
3785
  shouldSkipPath: featureProfile === "compact" ? isCompactFeaturePathSkipped : void 0
3721
3786
  });
3787
+ addSignalRiskDistanceFeatures({ features, signal });
3722
3788
  addDirectionalDerivedFeatures(features);
3723
3789
  return features;
3724
3790
  };
@@ -6135,7 +6201,7 @@ var init_aiTrainEvaluationDump = __esm({
6135
6201
  });
6136
6202
 
6137
6203
  // 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;
6204
+ 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
6205
  var init_aiTrainResearch = __esm({
6140
6206
  "src/lib/aiTrainResearch.ts"() {
6141
6207
  "use strict";
@@ -6164,6 +6230,12 @@ var init_aiTrainResearch = __esm({
6164
6230
  "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
6165
6231
  "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
6166
6232
  ];
6233
+ DERIVATIVES_CONTEXT_DATA_MODEL = {
6234
+ derivativesSourceIntervals: "15m",
6235
+ derivativesDerivedIntervals: "1h",
6236
+ derivativesHourlyFallback: "stored-1h",
6237
+ derivativesDataModelVersion: 2
6238
+ };
6167
6239
  normalizeForStableJson = (value) => {
6168
6240
  if (Array.isArray(value)) {
6169
6241
  return value.map(normalizeForStableJson);
@@ -6276,6 +6348,7 @@ var init_aiTrainResearch = __esm({
6276
6348
  const relativeCandidates = [
6277
6349
  `packages/strategies/src/${strategyName}/adapters/ai.ts`,
6278
6350
  `packages/strategies/src/${strategyName}/guardrails.ts`,
6351
+ `packages/strategies/src/${strategyName}/pockets.ts`,
6279
6352
  `packages/strategies/src/${strategyName}/config.ts`,
6280
6353
  "packages/node/src/ai.ts"
6281
6354
  ];
@@ -6322,7 +6395,8 @@ var init_aiTrainResearch = __esm({
6322
6395
  ...Object.fromEntries(
6323
6396
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
6324
6397
  ),
6325
- ...runContext
6398
+ ...runContext,
6399
+ ...DERIVATIVES_CONTEXT_DATA_MODEL
6326
6400
  };
6327
6401
  return {
6328
6402
  gitSha,
@@ -7402,6 +7476,9 @@ var init_aiTrain = __esm({
7402
7476
  import_chalk5.default.gray("CALENDAR/D"),
7403
7477
  import_chalk5.default.gray("WR"),
7404
7478
  import_chalk5.default.gray("PF"),
7479
+ import_chalk5.default.gray("SHARPE"),
7480
+ import_chalk5.default.gray("SORTINO"),
7481
+ import_chalk5.default.gray("CALMAR"),
7405
7482
  import_chalk5.default.gray("PNL"),
7406
7483
  import_chalk5.default.gray("MAX_DD"),
7407
7484
  import_chalk5.default.gray("TOP REJECT")
@@ -7416,6 +7493,9 @@ var init_aiTrain = __esm({
7416
7493
  colorizeMetricNumber(window2.approvedPerCalendarDay),
7417
7494
  colorizeRatio2(window2.outcome.approvedRisk.winRate),
7418
7495
  colorizeMetricNumber(window2.outcome.approvedRisk.profitFactor),
7496
+ colorizeMetricNumber(window2.outcome.approvedRisk.sharpeRatio),
7497
+ colorizeMetricNumber(window2.outcome.approvedRisk.sortinoRatio),
7498
+ colorizeMetricNumber(window2.outcome.approvedRisk.calmarRatio),
7419
7499
  colorizeProfit2(window2.outcome.approvedRisk.totalProfit),
7420
7500
  colorizeProfit2(-window2.outcome.approvedRisk.maxDrawdown),
7421
7501
  import_chalk5.default.gray(
@@ -7464,6 +7544,18 @@ var init_aiTrain = __esm({
7464
7544
  "profit_factor",
7465
7545
  colorizeMetricNumber(summary.approvedRisk.profitFactor)
7466
7546
  ],
7547
+ [
7548
+ "sharpe_ratio",
7549
+ colorizeMetricNumber(summary.approvedRisk.sharpeRatio)
7550
+ ],
7551
+ [
7552
+ "sortino_ratio",
7553
+ colorizeMetricNumber(summary.approvedRisk.sortinoRatio)
7554
+ ],
7555
+ [
7556
+ "calmar_ratio",
7557
+ colorizeMetricNumber(summary.approvedRisk.calmarRatio)
7558
+ ],
7467
7559
  [
7468
7560
  "payoff_ratio",
7469
7561
  colorizeMetricNumber(summary.approvedRisk.payoffRatio)
@@ -8590,7 +8682,7 @@ var init_binanceMarketContextBackfill = __esm({
8590
8682
  });
8591
8683
 
8592
8684
  // 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;
8685
+ 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
8686
  var init_derivativesContextBackfill = __esm({
8595
8687
  "src/lib/derivativesContextBackfill.ts"() {
8596
8688
  "use strict";
@@ -8722,6 +8814,38 @@ var init_derivativesContextBackfill = __esm({
8722
8814
  return null;
8723
8815
  };
8724
8816
  resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
8817
+ resolveDerivativesContextRequiredFetchFromMs = (params) => {
8818
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
8819
+ ranges: params.dataCoverageRanges,
8820
+ fromMs: params.fromMs,
8821
+ toMs: params.toMs,
8822
+ intervalMs: params.intervalMs
8823
+ });
8824
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
8825
+ edges: params.edges,
8826
+ fromMs: params.fromMs,
8827
+ toMs: params.toMs,
8828
+ intervalMs: params.intervalMs,
8829
+ refreshClosedTail: params.mode === "signals"
8830
+ });
8831
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
8832
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
8833
+ interval: params.interval,
8834
+ fromMs: params.fromMs,
8835
+ toMs: params.toMs,
8836
+ nowMs: params.nowMs
8837
+ });
8838
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
8839
+ ranges: params.liquidationCoverageRanges,
8840
+ fromMs: confirmedLiquidationWindow.fromMs,
8841
+ toMs: confirmedLiquidationWindow.toMs,
8842
+ intervalMs: params.intervalMs
8843
+ }) : null;
8844
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
8845
+ (value) => value != null
8846
+ );
8847
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
8848
+ };
8725
8849
  countBackfillWindows = (params) => {
8726
8850
  let count = 0;
8727
8851
  let cursor = params.fromMs;
@@ -9057,7 +9181,8 @@ var init_derivativesContextBackfill = __esm({
9057
9181
  mode,
9058
9182
  startMs,
9059
9183
  endMs,
9060
- preloadStartMs: params.preloadStartMs
9184
+ preloadStartMs: params.preloadStartMs,
9185
+ nowMs: Date.now()
9061
9186
  });
9062
9187
  if (safeEndMs <= fromMs) {
9063
9188
  return skippedBackfillResult2();
@@ -9091,15 +9216,33 @@ var init_derivativesContextBackfill = __esm({
9091
9216
  );
9092
9217
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
9093
9218
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
9219
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
9220
+ const coverageNowMs = Date.now();
9094
9221
  await Promise.all(
9095
9222
  intervalWindows.map(async (window2) => {
9096
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
9097
- source: "coinalyze",
9098
- symbols,
9223
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9099
9224
  interval: window2.interval,
9100
9225
  fromMs: window2.fromMs,
9101
- toMs: window2.toMs
9226
+ toMs: window2.toMs,
9227
+ nowMs: coverageNowMs
9102
9228
  });
9229
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
9230
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
9231
+ source: "coinalyze",
9232
+ symbols,
9233
+ interval: window2.interval,
9234
+ fromMs: window2.fromMs,
9235
+ toMs: window2.toMs
9236
+ }),
9237
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
9238
+ source: "coinalyze",
9239
+ metric: "liquidation",
9240
+ symbols,
9241
+ interval: window2.interval,
9242
+ fromMs: confirmedLiquidationWindow.fromMs,
9243
+ toMs: confirmedLiquidationWindow.toMs
9244
+ }) : Promise.resolve([])
9245
+ ]);
9103
9246
  coverageKeysByInterval.set(
9104
9247
  window2.interval,
9105
9248
  new Set(
@@ -9121,6 +9264,17 @@ var init_derivativesContextBackfill = __esm({
9121
9264
  rangesBySymbol.set(symbol, ranges);
9122
9265
  }
9123
9266
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
9267
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
9268
+ for (const row of liquidationCoverageRows) {
9269
+ const symbol = row.symbol.toUpperCase();
9270
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
9271
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
9272
+ liquidationRangesBySymbol.set(symbol, ranges);
9273
+ }
9274
+ liquidationCoverageRangesByInterval.set(
9275
+ window2.interval,
9276
+ liquidationRangesBySymbol
9277
+ );
9124
9278
  })
9125
9279
  );
9126
9280
  const cachedWindows = intervalWindows.reduce(
@@ -9145,7 +9299,7 @@ var init_derivativesContextBackfill = __esm({
9145
9299
  (symbol) => backfillWindows.every((backfillWindow) => {
9146
9300
  const normalizedSymbol = symbol.toUpperCase();
9147
9301
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
9148
- return hasDerivativesWindowCoverage({
9302
+ const dataCovered = hasDerivativesWindowCoverage({
9149
9303
  edges: edgesBySymbol?.get(normalizedSymbol),
9150
9304
  fromMs: backfillWindow.fromMs,
9151
9305
  toMs: backfillWindow.toMs
@@ -9162,6 +9316,19 @@ var init_derivativesContextBackfill = __esm({
9162
9316
  toMs: backfillWindow.toMs
9163
9317
  })
9164
9318
  );
9319
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9320
+ interval: window2.interval,
9321
+ fromMs: backfillWindow.fromMs,
9322
+ toMs: backfillWindow.toMs,
9323
+ nowMs: coverageNowMs
9324
+ });
9325
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
9326
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
9327
+ fromMs: confirmedLiquidationWindow.fromMs,
9328
+ toMs: confirmedLiquidationWindow.toMs,
9329
+ intervalMs: window2.intervalMs
9330
+ }) == null;
9331
+ return dataCovered && liquidationCovered;
9165
9332
  })
9166
9333
  );
9167
9334
  });
@@ -9246,6 +9413,11 @@ var init_derivativesContextBackfill = __esm({
9246
9413
  coverageKeysByInterval.set(interval5, coverageKeys);
9247
9414
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
9248
9415
  coverageRangesByInterval.set(interval5, coverageRangesBySymbol);
9416
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
9417
+ liquidationCoverageRangesByInterval.set(
9418
+ interval5,
9419
+ liquidationCoverageRangesBySymbol
9420
+ );
9249
9421
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
9250
9422
  const batch = symbolBatches[batchIdx];
9251
9423
  let cursor = window2.fromMs;
@@ -9258,33 +9430,25 @@ var init_derivativesContextBackfill = __esm({
9258
9430
  fromMs: cursor,
9259
9431
  toMs
9260
9432
  });
9261
- if (coverageKeys.has(key)) {
9262
- return null;
9263
- }
9264
9433
  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
- }
9434
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
9435
+ {
9436
+ mode,
9437
+ interval: interval5,
9438
+ intervalMs: intervalMs2,
9439
+ fromMs: cursor,
9440
+ toMs,
9441
+ nowMs: coverageNowMs,
9442
+ dataCoverageKeyExists: coverageKeys.has(key),
9443
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
9444
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
9445
+ edges: edgesBySymbol.get(normalizedSymbol)
9446
+ }
9447
+ );
9448
+ if (requiredFromMs == null) return null;
9285
9449
  return {
9286
9450
  item,
9287
- fromMs: Math.max(coverageFromMs, edgesFromMs)
9451
+ fromMs: requiredFromMs
9288
9452
  };
9289
9453
  }).filter(
9290
9454
  (item) => item != null
@@ -9309,6 +9473,7 @@ var init_derivativesContextBackfill = __esm({
9309
9473
  );
9310
9474
  let rows = [];
9311
9475
  let missingClosedSymbols = [];
9476
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
9312
9477
  for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
9313
9478
  const oiMap = await fetchMetricBatch({
9314
9479
  endpoint: oiPath,
@@ -9328,7 +9493,7 @@ var init_derivativesContextBackfill = __esm({
9328
9493
  fromMs: group.fromMs,
9329
9494
  toMs
9330
9495
  });
9331
- const liqMap = await fetchMetricBatch({
9496
+ liquidationRowsByMarket = await fetchMetricBatch({
9332
9497
  endpoint: liqPath,
9333
9498
  metric: "liq",
9334
9499
  marketSymbols,
@@ -9343,7 +9508,7 @@ var init_derivativesContextBackfill = __esm({
9343
9508
  symbol: item.symbol,
9344
9509
  oiRaw: oiMap.get(marketSymbol) ?? [],
9345
9510
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
9346
- liqRaw: liqMap.get(marketSymbol) ?? []
9511
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
9347
9512
  });
9348
9513
  return (0, import_indicators.coinalyzePointsToRows)(points, interval5, "coinalyze");
9349
9514
  });
@@ -9366,6 +9531,42 @@ var init_derivativesContextBackfill = __esm({
9366
9531
  await (0, import_timescale2.upsertDerivatives)(rows);
9367
9532
  totalRows += rows.length;
9368
9533
  }
9534
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
9535
+ interval: interval5,
9536
+ fromMs: group.fromMs,
9537
+ toMs,
9538
+ nowMs: coverageNowMs
9539
+ });
9540
+ if (confirmedLiquidationWindow) {
9541
+ const metricCoverageRows = missingBatch.map((item) => {
9542
+ const marketSymbol = item.marketSymbol.toUpperCase();
9543
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
9544
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
9545
+ point.t ?? point.ts ?? point.time ?? point.timestamp
9546
+ );
9547
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
9548
+ }).length;
9549
+ return {
9550
+ source: "coinalyze",
9551
+ metric: "liquidation",
9552
+ symbol: item.symbol,
9553
+ interval: interval5,
9554
+ fromMs: confirmedLiquidationWindow.fromMs,
9555
+ toMs: confirmedLiquidationWindow.toMs,
9556
+ eventRowsCount
9557
+ };
9558
+ });
9559
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
9560
+ for (const coverageRow of metricCoverageRows) {
9561
+ const symbol = coverageRow.symbol.toUpperCase();
9562
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
9563
+ ranges.push({
9564
+ fromMs: coverageRow.fromMs,
9565
+ toMs: coverageRow.toMs
9566
+ });
9567
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
9568
+ }
9569
+ }
9369
9570
  if (mode === "backtest") {
9370
9571
  const rowsCountBySymbol = /* @__PURE__ */ new Map();
9371
9572
  for (const row of rows) {
@@ -19393,6 +19594,36 @@ var init_derivativesIngestCoinalyzeAll = __esm({
19393
19594
  await (0, import_timescale7.upsertDerivatives)(rows);
19394
19595
  totalRows += rows.length;
19395
19596
  }
19597
+ const confirmedLiquidationWindow = (0, import_indicators3.resolveCoinalyzeConfirmedIntradayCoverage)({
19598
+ interval: interval5,
19599
+ fromMs: cursor,
19600
+ toMs,
19601
+ nowMs: now2
19602
+ });
19603
+ if (confirmedLiquidationWindow) {
19604
+ await (0, import_timescale7.applyDerivativesMetricCoverage)(
19605
+ batch.map((item) => {
19606
+ const marketSymbol = item.marketSymbol.toUpperCase();
19607
+ const eventRowsCount = (liqMap.get(marketSymbol) ?? []).filter(
19608
+ (point) => {
19609
+ const timestamp = (0, import_indicators3.toCoinalyzeTimestampMs)(
19610
+ point.t ?? point.ts ?? point.time ?? point.timestamp
19611
+ );
19612
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
19613
+ }
19614
+ ).length;
19615
+ return {
19616
+ source: "coinalyze",
19617
+ metric: "liquidation",
19618
+ symbol: item.symbol,
19619
+ interval: interval5,
19620
+ fromMs: confirmedLiquidationWindow.fromMs,
19621
+ toMs: confirmedLiquidationWindow.toMs,
19622
+ eventRowsCount
19623
+ };
19624
+ })
19625
+ );
19626
+ }
19396
19627
  } catch (error) {
19397
19628
  failedWindows += 1;
19398
19629
  console.error(
@@ -30079,7 +30310,7 @@ var init_resultsReporting = __esm({
30079
30310
  });
30080
30311
 
30081
30312
  // src/lib/runtimeTradeSync.ts
30082
- var import_constants18, import_time10, import_redis21, CLOSED_PNL_LIMIT, EXCHANGE_HISTORY_MAX_RANGE_MS, splitExchangeHistoryTimeRange, formatRuntimeTradeSyncError, loadClosedPnlRows, consumeClosedPnlMatch, syncRuntimeTrades;
30313
+ var import_constants18, import_time10, import_redis21, CLOSED_PNL_LIMIT, CLOSED_PNL_RECONCILIATION_LOOKBACK_MS, EXCHANGE_HISTORY_MAX_RANGE_MS, isRuntimeTradeSyncFallbackClose, splitExchangeHistoryTimeRange, formatRuntimeTradeSyncError, loadClosedPnlRows, consumeClosedPnlMatch, syncRuntimeTrades;
30083
30314
  var init_runtimeTradeSync = __esm({
30084
30315
  "src/lib/runtimeTradeSync.ts"() {
30085
30316
  "use strict";
@@ -30087,7 +30318,9 @@ var init_runtimeTradeSync = __esm({
30087
30318
  import_time10 = require("@tradejs/core/time");
30088
30319
  import_redis21 = require("@tradejs/infra/redis");
30089
30320
  CLOSED_PNL_LIMIT = 100;
30321
+ CLOSED_PNL_RECONCILIATION_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
30090
30322
  EXCHANGE_HISTORY_MAX_RANGE_MS = 7 * 24 * 60 * 60 * 1e3;
30323
+ isRuntimeTradeSyncFallbackClose = (trade) => trade.status === "closed" && trade.exitTimestamp === trade.lastSyncedAt && trade.exitPrice == null && trade.actualExitPrice == null && trade.closeFee == null && trade.fundingFee == null;
30091
30324
  splitExchangeHistoryTimeRange = ({
30092
30325
  startTime,
30093
30326
  endTime,
@@ -30216,7 +30449,7 @@ var init_runtimeTradeSync = __esm({
30216
30449
  );
30217
30450
  const closedPnlRows = await loadClosedPnlRows({
30218
30451
  connector,
30219
- startTime,
30452
+ startTime: Math.max(0, startTime - CLOSED_PNL_RECONCILIATION_LOOKBACK_MS),
30220
30453
  endTime,
30221
30454
  callbacks: closedPnlCallbacks
30222
30455
  });
@@ -30228,14 +30461,14 @@ var init_runtimeTradeSync = __esm({
30228
30461
  }
30229
30462
  const syncedTrades = [];
30230
30463
  for (const trade of trades) {
30231
- if (trade.status !== "active") {
30464
+ if (trade.status !== "active" && !isRuntimeTradeSyncFallbackClose(trade)) {
30232
30465
  syncedTrades.push(trade);
30233
30466
  continue;
30234
30467
  }
30235
30468
  const openPosition = openPositionsBySymbol.get(trade.symbol);
30236
30469
  const activeOrderId = activeOrderIdBySymbol.get(trade.symbol);
30237
30470
  const isCurrentActiveTrade = activeOrderId === trade.orderId;
30238
- if (!openPositionsReliable) {
30471
+ if (trade.status === "active" && !openPositionsReliable) {
30239
30472
  syncedTrades.push({
30240
30473
  ...trade,
30241
30474
  status: "active",
@@ -30243,7 +30476,7 @@ var init_runtimeTradeSync = __esm({
30243
30476
  });
30244
30477
  continue;
30245
30478
  }
30246
- if (isCurrentActiveTrade && openPosition && openPosition.direction === trade.direction) {
30479
+ if (trade.status === "active" && isCurrentActiveTrade && openPosition && openPosition.direction === trade.direction) {
30247
30480
  const nextTrade2 = {
30248
30481
  ...trade,
30249
30482
  status: "active",
@@ -30271,21 +30504,24 @@ var init_runtimeTradeSync = __esm({
30271
30504
  continue;
30272
30505
  }
30273
30506
  const matchedClosedPnl = consumeClosedPnlMatch(closedPnlBuckets, trade);
30507
+ if (!matchedClosedPnl) {
30508
+ continue;
30509
+ }
30274
30510
  const nextTrade = {
30275
30511
  ...trade,
30276
30512
  status: "closed",
30277
- currentPrice: matchedClosedPnl?.exitPrice ?? trade.currentPrice ?? null,
30278
- currentPnl: matchedClosedPnl?.closedPnl ?? trade.closedPnl ?? trade.currentPnl ?? null,
30279
- closedPnl: matchedClosedPnl?.closedPnl ?? trade.closedPnl ?? trade.currentPnl ?? null,
30280
- actualEntryPrice: matchedClosedPnl?.entryPrice ?? trade.actualEntryPrice ?? null,
30281
- exitPrice: matchedClosedPnl?.exitPrice ?? trade.exitPrice ?? null,
30282
- actualExitPrice: matchedClosedPnl?.exitPrice ?? trade.actualExitPrice ?? null,
30283
- exitTimestamp: matchedClosedPnl?.closedAt ?? trade.exitTimestamp ?? endTime,
30513
+ currentPrice: matchedClosedPnl.exitPrice ?? trade.currentPrice ?? null,
30514
+ currentPnl: matchedClosedPnl.closedPnl,
30515
+ closedPnl: matchedClosedPnl.closedPnl,
30516
+ actualEntryPrice: matchedClosedPnl.entryPrice ?? trade.actualEntryPrice ?? null,
30517
+ exitPrice: matchedClosedPnl.exitPrice ?? trade.exitPrice ?? null,
30518
+ actualExitPrice: matchedClosedPnl.exitPrice ?? trade.actualExitPrice ?? null,
30519
+ exitTimestamp: matchedClosedPnl.closedAt,
30284
30520
  exitType: trade.exitType ?? null,
30285
- openFee: matchedClosedPnl?.openFee ?? trade.openFee ?? null,
30286
- closeFee: matchedClosedPnl?.closeFee ?? trade.closeFee ?? null,
30287
- fundingFee: matchedClosedPnl?.fundingFee ?? trade.fundingFee ?? null,
30288
- totalFee: matchedClosedPnl?.totalFee ?? trade.totalFee ?? null,
30521
+ openFee: matchedClosedPnl.openFee ?? trade.openFee ?? null,
30522
+ closeFee: matchedClosedPnl.closeFee ?? trade.closeFee ?? null,
30523
+ fundingFee: matchedClosedPnl.fundingFee ?? trade.fundingFee ?? null,
30524
+ totalFee: matchedClosedPnl.totalFee ?? trade.totalFee ?? null,
30289
30525
  lastSyncedAt: endTime
30290
30526
  };
30291
30527
  await Promise.all([
@@ -32443,8 +32679,7 @@ var init_signalsSummary = __esm({
32443
32679
  if (trade.status !== "closed" || !isTimestampInWindow(trade.exitTimestamp, startTime, endTime)) {
32444
32680
  return false;
32445
32681
  }
32446
- const looksLikeSyncFallbackClose = trade.exitTimestamp === trade.lastSyncedAt && trade.exitPrice == null && trade.actualExitPrice == null && trade.closeFee == null && trade.fundingFee == null && trade.totalFee == null;
32447
- return !looksLikeSyncFallbackClose;
32682
+ return !isRuntimeTradeSyncFallbackClose(trade);
32448
32683
  };
32449
32684
  buildSummaryMessages = ({
32450
32685
  hours,