diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index d6d53301..6c579c46 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -1115,31 +1115,40 @@ function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): P const INCREASE_ACTIONS = new Set(["TradePosSizeIncrease"]); const CLOSE_ACTIONS = new Set(["TradeClosedMarket", "TradeClosedTP", "TradeClosedSL", "TradeClosedLIQ"]); - const byId = new Map(); + const byId = new Map(); for (const t of trades) { if (!byId.has(t.id)) byId.set(t.id, {}); const e = byId.get(t.id)!; if (OPEN_ACTIONS.has(t.action)) e.open = t; - else if (INCREASE_ACTIONS.has(t.action) && e.open) { - e.open = { ...e.open, size: t.size, leverage: t.leverage }; + else if (INCREASE_ACTIONS.has(t.action)) { + if (e.open) e.open = { ...e.open, size: t.size, leverage: t.leverage }; + // Track increases for positions opened before the window (no open event in data) + else if (!e.lastIncrease || new Date(t.date).getTime() < new Date(e.lastIncrease.date).getTime()) { + e.lastIncrease = t; + } } else if (CLOSE_ACTIONS.has(t.action)) e.close = t; } const now = Date.now(); const slices: PositionSlice[] = []; - for (const { open, close } of byId.values()) { - if (!open) continue; - const openMs = new Date(open.date).getTime(); - if (openMs < cutoffMs) continue; - // Still-open positions use now as close time (same as reconstructHlPositions) + for (const { open, close, lastIncrease } of byId.values()) { + // Use open event if available; fall back to earliest increase in the window + // for positions opened before the fetch window (open event not in data). + const anchor = open ?? lastIncrease; + if (!anchor) continue; + const rawOpenMs = new Date(anchor.date).getTime(); const closeMs = close ? new Date(close.date).getTime() : now; + // Skip positions that closed before the analysis window + if (closeMs < cutoffMs) continue; + // Cap openMs to the window start so long-running positions aren't missed + const openMs = Math.max(rawOpenMs, cutoffMs); slices.push({ - coin: open.pair.split("/")[0], - notionalUsd: open.size * open.leverage, + coin: anchor.pair.split("/")[0], + notionalUsd: anchor.size * anchor.leverage, openMs, closeMs, - isLong: open.buy !== false, + isLong: anchor.buy !== false, }); } @@ -1480,8 +1489,11 @@ export async function GET(req: Request) { await Promise.all(fetches); - // Phase 2: fetch HL funding history for Gains positions (Gains→HL carry projection) + // Phase 2: fetch HL funding history + extended Gains history for position reconstruction let hlFundingHistoryByCoins: Map> = new Map(); + // Extended Gains history (1 year) used only for HL funding projection reconstruction — + // the fee accounting (taker/borrow/funding fees) still uses gainsTradesData (cutoffMs window). + let gainsPositionData: GainsApiTrade[] = gainsTradesData; if ( fetchEvmWallet && (venueA === "gains" || venueB === "gains") && @@ -1494,7 +1506,13 @@ export async function GET(req: Request) { .map((t) => t.pair.split("/")[0]) ); const coinsToFetch = [...gainsCoinSet].slice(0, 6); - hlFundingHistoryByCoins = await fetchHlFundingHistory(coinsToFetch, cutoffMs).catch(() => new Map()); + const extendedCutoffMs = cutoffMs - 365 * 24 * 60 * 60 * 1000; + const [fundingHistory, extendedTrades] = await Promise.all([ + fetchHlFundingHistory(coinsToFetch, cutoffMs).catch(() => new Map>()), + fetchGainsTrades(wallet, extendedCutoffMs).catch(() => gainsTradesData), + ]); + hlFundingHistoryByCoins = fundingHistory; + gainsPositionData = extendedTrades; } function buildVenueResult(slug: string, rate: number, note: string, rateIsLive: boolean): VenueResult { @@ -1663,9 +1681,9 @@ export async function GET(req: Request) { hlOpenPositions, cutoffMs ); - } else if (venueA === "gains" && gainsTradesData.length > 0) { + } else if (venueA === "gains" && gainsPositionData.length > 0) { positions = reconstructGainsPositions( - gainsTradesData.filter((t) => t.collateralIndex === 3), + gainsPositionData.filter((t) => t.collateralIndex === 3), cutoffMs ); } else if (venueA === "gmx-v2" && gmxWalletData) { @@ -1803,9 +1821,9 @@ export async function GET(req: Request) { hlOpenPositions, cutoffMs ); - } else if (venueB === "gains" && gainsTradesData.length > 0) { + } else if (venueB === "gains" && gainsPositionData.length > 0) { positions = reconstructGainsPositions( - gainsTradesData.filter((t) => t.collateralIndex === 3), + gainsPositionData.filter((t) => t.collateralIndex === 3), cutoffMs ); } else if (venueB === "gmx-v2" && gmxWalletData) {