Files
autofirmer-expanded/app/api/trades/route.ts
T
Brandon LiandClaude Opus 5.5 016444239e Trades: an open position shows no close price or P&L
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 <noreply@anthropic.com>
2026-10-26 20:02:11 -05:00

57 lines
2.5 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getClients } from '@/lib/clients';
import { loadTrades, getSetting, TRADES_SINCE_SETTING } from '@/lib/db';
import type { TradeView } from '@/types';
/**
* GET /api/trades?accountId=… | ?firmId=… [&limit=…] — recorded positions,
* newest first: one account, a firm's current accounts, or all of them.
*
* Who opened and closed each one: the bot marks its own orders as it sends
* them, so after marking began (TRADES_SINCE_SETTING) anything unmarked was
* someone else's. A close Vest calls "user_requested" is an order on the
* account — the bot's if it marked it, else outside.
*/
export async function GET(req: Request) {
const url = new URL(req.url);
const accountId = Number(url.searchParams.get('accountId'));
const firmId = url.searchParams.get('firmId');
const limit = Math.min(Math.max(Math.floor(Number(url.searchParams.get('limit'))) || 200, 1), 1000);
let accountIds: number[] | undefined;
if (Number.isFinite(accountId) && accountId > 0) accountIds = [accountId];
else if (firmId) accountIds = getClients().get(firmId)?.accountList.map((a) => a.id) ?? [];
const since = Number(getSetting(TRADES_SINCE_SETTING) ?? NaN);
const tracked = (at: number) => Number.isFinite(since) && at >= since;
const trades: TradeView[] = loadTrades({ accountIds, limit }).map((t) => {
const open = t.closedAt === null && t.closeReason === null && t.closedBy === null;
const closedBy = open ? null
: t.closedBy === 'bot' ? 'bot'
: t.closeReason === 'user_requested' ? (t.closedBy === 'outside' || tracked(t.closedAt ?? t.openedAt) ? 'outside' : 'user_requested')
: t.closeReason ?? 'unknown';
const net = open || t.pnl === null ? null
: Math.round((t.pnl - Math.abs(t.fee ?? 0) + (t.funding ?? 0)) * 100) / 100;
return {
positionId: t.positionId,
accountId: t.accountId,
accountName: t.accountName,
symbol: t.symbol,
side: t.side,
quantity: t.quantity,
openPrice: t.openPrice,
closePrice: open ? null : t.closePrice,
pnl: open ? null : t.pnl,
fee: t.fee,
funding: t.funding,
net,
openedAt: t.openedAt,
closedAt: t.closedAt,
openedBy: t.openedBy === 'bot' ? 'bot' : t.openedBy === 'outside' || tracked(t.openedAt) ? 'outside' : null,
closedBy,
};
});
return NextResponse.json(trades);
}