// TRADERVIEW — WALKTHROUGH

Tutorial index Docs hub
Progress
03 / 18

03.Trades & executions

Executions are the atom. Trades are FIFO-derived. The roll-up is deterministic, idempotent, and safe to re-run at any time. This page covers the data model, the views, and the editing endpoints (split / merge / risk / bulk).

The data model in one sentence

Executions are the source of truth; trades are a materialized view. Every broker fill is one immutable row in executions; traderview-core::rollup walks those rows in (account_id, symbol) partitions and FIFO-matches buy/sell pairs into the trades table. Re-running the roll-up at any time produces the same trade IDs and P&L for the same execution input.

TableColumns (abridged)
executionsid, account_id, symbol, side, qty, price, multiplier, fees, executed_at, asset_class, broker_order_id, fx_rate, imported_at, import_id
tradesid, account_id, symbol, side (long/short), entry_avg, exit_avg, qty, gross_pnl, net_pnl, status (open/closed), opened_at, closed_at, asset_class, r_multiple, planned_risk, tags, …

Every trades row also links to its constituent execution IDs via trade_executions so you can drill down to the exact fills that built it.

FIFO roll-up — what the algorithm actually does

In crates/traderview-core/src/rollup.rs:

  1. Group all executions by (account_id, symbol).
  2. Sort each group by executed_at ASC, id ASC (the tie-break on id makes the order deterministic if two fills land in the same millisecond).
  3. Walk the sorted list with a position pointer and an open-lot queue:
    • Same-side fill → push a new lot onto the queue.
    • Opposite-side fill → pop FIFO from the queue, match qty, accumulate realized P&L. If the opposite-side qty exceeds the queue, flip to short (or long) and push the residual as a new lot.
  4. Emit one trades row per fully-closed position; one row per still-open position with status='open' and exit_avg = NULL.
  5. Compute statistics per-trade — gross_pnl, net_pnl (gross minus fees), r_multiple (net_pnl / planned_risk if planned_risk is set on the trade), hold duration, max favorable / adverse excursion.

The roll-up is single-pass per (account_id, symbol) group, so a 100 k-execution book finishes in seconds. Re-running on an unchanged input is a no-op for the trade IDs: stable IDs are derived from the first opening execution.

The Trades view

frontend/js/views/trades.js. Server-side pagination (GET /api/trades?account=&status=&limit=&offset=&sort=), filter chips for asset class / status / R-bucket / tags, and the following per-row context menu (right-click anywhere on a trade row):

  • Open journal — jumps to the per-trade journal entry (step 04).
  • Open chart with markers — Charts view with entry / exit markers stamped on the bar.
  • Set planned riskPOST /api/trades/{id}/risk. Re-derives r_multiple for the row without touching executions.
  • SplitPOST /api/trades/{id}/split. For trades that should be modeled as two separate ideas (e.g. a position you held overnight and added to the next day).
  • MergePOST /api/trades/merge. Combine adjacent trades on the same symbol with a custom join logic.
  • Tag — multi-select per-account tag picker. Tags are queryable in reports (step 05).
  • Symbol nav — six per-symbol jumps (Charts / Options / Research / Earnings / News / Copy). 222 distinct symbol-nav paths are wired across the app — every grid that emits a symbol surfaces the same menu.

The Executions view

One row per fill. Same paginated grid pattern as Trades but with a sharper filter set: account / symbol / side / asset class / date range / import batch / has-error. Useful for forensics — "did that 9:31 fill at $185.04 really come through?" The Executions view answers without re-deriving anything.

You can also hand-add executions from this view (POST /api/executions) — handy for cash dividends, broker corrections, or backfilling a missing fill the CSV didn't capture. Hand-added rows are flagged with import_id = NULL and survive re-imports.

Bulk operations

Selecting multiple trades unlocks bulk endpoints. The grid uses range-select (Shift+click) and toggle-select (Cmd+click); the bulk action bar shows once anything is selected.

  • Bulk tag — adds / removes tags across the selection.
  • Bulk set planned risk — same R for all (useful when you sized every trade the same week).
  • Bulk delete — only deletes the trade row; executions are preserved, the rollup re-fires, and a fresh trade row appears. Net effect: trade-level metadata (tags, notes, planned-risk) is wiped without touching fills.
  • Bulk export — CSV / JSON of the selected trades (column set matches the visible grid).
  • Bulk POSTPOST /api/trades/bulk takes a list of partial updates; useful when scripting against the API.

Re-running the roll-up safely

POST /api/trades/rollup with no body re-runs every (account_id, symbol) partition. Optional body narrows the scope:

{
  "account_id": 17,
  "symbols": ["AAPL", "TSLA"],
  "force": false
}

force: true drops the trade rows for the targeted partitions and re-derives from scratch (also wipes per-trade metadata — tags, notes, planned-risk). The default force: false preserves trade IDs and metadata wherever the rolled-up rows match the existing ones; only trades whose execution membership changed are rewritten.

Open positions

Open trades (status='open') have exit_avg = NULL and unrealized P&L computed against the latest quote in quote_snapshots. The Live Positions tile (step 06) shows them in real time; the Trades grid shows them with a different row tint and the equity-curve view marks them with a dashed continuation.

Warning Don't manually delete an open trade with the bulk-delete action — the next rollup will recreate it but the planned-risk + tags you'd set are gone. Use the per-row Close action instead, which prompts for an exit fill.