Page - Trades

abstract

Full trade history table for the active account — filter by date range, symbol, open/closed status, and paper trades; view scorecard KPIs; expand rows to see LLM analysis details.

Route

PropertyValue
Path/trades
Filefrontend/src/app/trades/page.tsx
Auth RequiredNo
LayoutRoot layout with sidebar
Dynamic SegmentNone

Component Tree

TradesPage (page.tsx)
├── AppHeader (title="Trades")
├── Filters Row
│   ├── DateRangePicker (from_date / to_date via react-day-picker)
│   ├── Symbol filter input
│   ├── Switch: show open trades only
│   ├── Switch: include paper trades
│   └── Button: Refresh
├── Scorecard (calculated from loaded trades)
│   └── Total Trades | Closed | Win Rate | Total P&L
├── TanStack Table (all trades)
│   └── Columns: Symbol, Direction, Entry, SL, TP, Close, Volume, P&L, Status, Source, Opened At
│   └── Expandable row → shows trade_analysis JSON (LLM post-trade review)
│   └── BrainCircuit button → trigger POST /trades/{id}/analyze
└── Pagination

Data Layer

Server State — Direct fetch

CallAPIEndpointTriggered When
Fetch tradestradesApi.list(params)GET /api/v1/tradesOn mount + filter changes
Analyze tradetradesApi.analyze(id)POST /api/v1/trades/{id}/analyzeBrainCircuit button click

Global State — Zustand

StoreFields ReadActions Called
useTradingStoreactiveAccountId

Local State — useState

VariableTypeInitialPurpose
tradesTrade[][]Loaded trades
dateRangeDateRange | undefinedundefinedDate filter from react-day-picker
showOpenOnlybooleanfalseFilter open trades
includePaperbooleanfalseInclude paper trades
Filter state (URL sync)useSearchParamsPersists filter in URL

Page Lifecycle

Scorecard Calculation (client-side)

function Scorecard({ trades }: { trades: Trade[] }) {
  const closed = trades.filter((t) => t.closed_at !== null);
  const wins = closed.filter((t) => (t.profit ?? 0) > 0);
  const totalPnl = closed.reduce((s, t) => s + (t.profit ?? 0), 0);
  const winRate = closed.length > 0 ? (wins.length / closed.length) * 100 : 0;
  // Renders: Total Trades, Closed, Win Rate %, Total P&L
}
warning

The Scorecard is computed from the loaded trades, not all trades in the database. If the user applies date filters or the limit=200 cap is hit, scorecard numbers reflect the filtered/capped dataset only — not lifetime stats.

Validation & Conditions

P&L Color Logic

const pnlColor = (p: number | null) => {
  if (p == null) return "";
  if (p > 0) return "text-green-600 dark:text-green-400";
  if (p < 0) return "text-red-600 dark:text-red-400";
  return "";
};

Render Conditions

ConditionWhat Shows
trades.length === 0Empty state (Inbox icon)
trade.closed_at === nullOpen trade — close_price and profit show as
trade.trade_analysis !== nullExpandable row with LLM analysis
trade.is_paper_trade"Paper" badge on row
RoleLink
Backend APIAPI-Trades
DB SchemaDB - trades
StoreStore - TradingStore