From 016444239e8d540066f0a674a0225520b85c4098 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Mon, 26 Oct 2026 20:02:11 -0500 Subject: [PATCH] Trades: an open position shows no close price or P&L MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vest's position history gives an open position a placeholder close price and a $0 P&L (290.25 on an NDX short at 29,538.96). Only a closed position keeps them now — in the parser, in the stored row (a placeholder already stored is cleared on the next read) and in the API. Co-Authored-By: Claude Opus 5.5 --- app/api/trades/route.ts | 4 ++-- lib/db.ts | 6 ++++-- lib/vest-helpers.ts | 11 +++++++---- scripts/check-vest.mjs | 7 +++++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/api/trades/route.ts b/app/api/trades/route.ts index 03ee9d4..5bb3cb9 100644 --- a/app/api/trades/route.ts +++ b/app/api/trades/route.ts @@ -41,8 +41,8 @@ export async function GET(req: Request) { side: t.side, quantity: t.quantity, openPrice: t.openPrice, - closePrice: t.closePrice, - pnl: t.pnl, + closePrice: open ? null : t.closePrice, + pnl: open ? null : t.pnl, fee: t.fee, funding: t.funding, net, diff --git a/lib/db.ts b/lib/db.ts index 95f605d..789860a 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -652,8 +652,10 @@ export function saveTrades(rows: Omit[]): voi quantity = CASE WHEN excluded.quantity > 0 THEN excluded.quantity ELSE trades.quantity END, leverage = COALESCE(excluded.leverage, trades.leverage), open_price = COALESCE(excluded.open_price, trades.open_price), - close_price = COALESCE(excluded.close_price, trades.close_price), - pnl = COALESCE(excluded.pnl, trades.pnl), + close_price = CASE WHEN COALESCE(excluded.closed_at, trades.closed_at) IS NULL THEN NULL + ELSE COALESCE(excluded.close_price, trades.close_price) END, + pnl = CASE WHEN COALESCE(excluded.closed_at, trades.closed_at) IS NULL THEN NULL + ELSE COALESCE(excluded.pnl, trades.pnl) END, fee = COALESCE(excluded.fee, trades.fee), funding = COALESCE(excluded.funding, trades.funding), opened_at = excluded.opened_at, diff --git a/lib/vest-helpers.ts b/lib/vest-helpers.ts index 106a43b..51d76d8 100644 --- a/lib/vest-helpers.ts +++ b/lib/vest-helpers.ts @@ -313,9 +313,12 @@ export function parseHistoryPosition(raw: unknown): VestTrade | null { if (!p || typeof id !== 'string' || typeof p.symbol !== 'string') return null; const openedAt = toMs(p.openDate ?? p.open_date ?? p.createdAt ?? p.created_at); if (!Number.isFinite(openedAt)) return null; - const closedAt = toMs(p.closeDate ?? p.close_date); + const closedMs = toMs(p.closeDate ?? p.close_date); + const closedAt = Number.isFinite(closedMs) && closedMs > 0 ? closedMs : null; const openPrice = num(p.openPrice ?? p.open_price); - const closePrice = num(p.closePrice ?? p.close_price); + // An open position comes with a placeholder close price and a $0 P&L + // (seen: 290.25 on an NDX short at 29,538.96): only a close has them. + const closePrice = closedAt === null ? undefined : num(p.closePrice ?? p.close_price); const reason = p.closeReason ?? p.close_reason; return { positionId: id, @@ -325,11 +328,11 @@ export function parseHistoryPosition(raw: unknown): VestTrade | null { leverage: num(p.leverage) ?? null, openPrice: openPrice !== undefined && openPrice > 0 ? openPrice : null, closePrice: closePrice !== undefined && closePrice > 0 ? closePrice : null, - pnl: num(p.pnl) ?? null, + pnl: closedAt === null ? null : num(p.pnl) ?? null, fee: num(p.fee) ?? null, funding: num(p.funding) ?? null, openedAt, - closedAt: Number.isFinite(closedAt) && closedAt > 0 ? closedAt : null, + closedAt, closeReason: typeof reason === 'string' && reason !== '' ? reason : null, }; } diff --git a/scripts/check-vest.mjs b/scripts/check-vest.mjs index dddd738..7bebb80 100644 --- a/scripts/check-vest.mjs +++ b/scripts/check-vest.mjs @@ -293,6 +293,13 @@ test('parseHistoryPosition: open, snake_case, seconds, and junk', () => { assert.equal(open.openedAt, 1793065590000); assert.equal(open.closedAt, null); assert.equal(open.closeReason, null); + const held = parseHistoryPosition({ + positionId: 'p3', symbol: 'NDX-USD-PERP', side: 'short', quantity: '85.7363', openPrice: '29538.96', closePrice: '290.25', + pnl: '0.0', fee: '63.314059', funding: '0.0', openDate: 1793065422000, closeDate: null, + }); + assert.equal(held.closePrice, null, "an open position's placeholder close price is dropped"); + assert.equal(held.pnl, null, 'and its $0 P&L'); + assert.equal(held.fee, 63.314059, 'its entry fee is real'); assert.equal(parseHistoryPosition({ symbol: 'GC-PERP', openDate: 1 }), null, 'no id'); assert.equal(parseHistoryPosition({ positionId: 'p2', symbol: 'GC-PERP' }), null, 'no open time'); assert.equal(parseHistoryPosition(null), null);