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>
This commit is contained in:
Brandon Li
2026-10-27 01:27:07 -05:00
co-authored by Claude Opus 5.5
parent 6f6660fdcc
commit 1e844178de
13 changed files with 1149 additions and 16 deletions
+5 -4
View File
@@ -6,7 +6,7 @@ import type { TradeView } from '@/types';
/**
* Recorded positions from /api/trades — every trade on the accounts, the
* bot's and anyone else's, with who opened it and how it closed. On the
* dashboard under a Vest firm (inline) and on the account page (as a card,
* dashboard under each firm (inline) and on the account page (as a card,
* hidden while there's nothing to show).
*/
@@ -16,7 +16,7 @@ const PILL = 'inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold white
const CLOSED_BY: Record<string, { label: string; cls: string; title: string }> = {
bot: { label: 'Bot', cls: 'bg-violet-100 text-violet-700', title: "The bot's own close order" },
outside: { label: 'Outside', cls: 'bg-amber-100 text-amber-700', title: 'Closed by an order the bot did not send — by hand on Vest, or another tool' },
outside: { label: 'Outside', cls: 'bg-amber-100 text-amber-700', title: 'Closed by an order the bot did not send — by hand on the platform, another tool, or the firm’s own liquidation' },
stop_loss: { label: 'Stop loss', cls: 'bg-red-100 text-red-700', title: 'Its stop-loss triggered' },
take_profit: { label: 'Take profit', cls: 'bg-green-100 text-green-700', title: 'Its take-profit triggered' },
liquidation: { label: 'Liquidation', cls: 'bg-red-200 text-red-800', title: 'Liquidated by Vest' },
@@ -24,6 +24,7 @@ const CLOSED_BY: Record<string, { label: string; cls: string; title: string }> =
temporary_leverage_limit: { label: 'Leverage limit', cls: 'bg-orange-100 text-orange-700', title: 'Reduced by Vest’s temporary (weekend) leverage limit' },
corporate_action: { label: 'Corporate action', cls: 'bg-slate-100 text-slate-600', title: 'Closed by a corporate action' },
user_requested: { label: 'Self-requested', cls: 'bg-slate-100 text-slate-600', title: 'An order on the account closed it — from before the bot marked its own closes, so whose is unknown' },
unknown: { label: 'Unknown', cls: 'bg-slate-100 text-slate-500', title: 'Closed while the platform’s history wasn’t being read — when and how aren’t on record' },
};
function money(v: number): string {
@@ -44,7 +45,7 @@ function when(ms: number): string {
function OpenedBy({ by }: { by: TradeView['openedBy'] }) {
if (by === 'bot') return <span className={`${PILL} bg-violet-100 text-violet-700`} title="Opened by the bot">Bot</span>;
if (by === 'outside') return <span className={`${PILL} bg-amber-100 text-amber-700`} title="Opened by an order the bot did not send — by hand on Vest, or another tool">Outside</span>;
if (by === 'outside') return <span className={`${PILL} bg-amber-100 text-amber-700`} title="Opened by an order the bot did not send — by hand on the platform, or another tool">Outside</span>;
return <span className="text-slate-400" title="Opened before the bot began marking its own trades">—</span>;
}
@@ -105,7 +106,7 @@ export function TradesTable({ query, title, showAccount = false, formatAccount =
const table = !trades
? <div className="text-sm text-slate-400 italic py-2">{failed ? 'Couldn’t load trades' : 'Loading trades…'}</div>
: trades.length === 0
? <div className="text-sm text-slate-400 italic py-2">No trades recorded yet — they appear after the next history read (within the hour, or a minute after a position closes)</div>
? <div className="text-sm text-slate-400 italic py-2">No trades recorded yet — they appear a few minutes after a fill, once the platform’s history is read</div>
: (
<div className="overflow-x-auto max-h-[28rem] overflow-y-auto">
<table className="w-full text-xs">