Files
autofirmer-expanded/app/api/trades/route.ts
T
Brandon LiandClaude Opus 5.5 1e844178de Trades: record every firm's trades, not just Vest's, and list them on each
TopstepX and Tradovate report fills, so a trade is built from them, flat
to flat (lib/fill-trades.ts), off the window each sync already reads:
TopstepX's two days of Trade/search, Tradovate's sync snapshot. Neither
says who placed an order, so every order the bot places is noted in
own_orders, and a trade is the bot's when its opening fill's order is
one; its stop or take-profit closing it reads as such. A contract is
written only when the position before its window comes out the same
two passes running, so a fill landing between the position and history
reads can't build the wrong trades.

DXtrade reports positions: its record groups the last two days' filled
orders by position code, with each close's SETTLEMENT and COMMISSION.
The bot's own orders carry af- codes, so nothing is kept locally. Only
accounts with something going on are read, at most every 5 minutes.

The Trades button shows on every firm with a login, and the account
page lists any account's trades.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:27:07 -05:00

60 lines
2.8 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. Trades built from
* futures fills (lib/trade-recorder.ts, DXtrade's record) carry their marks
* already: 'bot', 'outside', or 'unknown' from before the bot noted its own
* orders.
*/
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 ?? (t.closedBy === 'outside' ? 'outside' : t.closedBy === 'unknown' ? 'user_requested' : '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 === 'unknown' ? null : t.openedBy === 'outside' || tracked(t.openedAt) ? 'outside' : null,
closedBy,
};
});
return NextResponse.json(trades);
}