---
name: sync-trades
description: >
  Get trades into the journal. Two modes: user provides a broker CSV,
  or auto-fetch from IBKR API. Use when the user says "import my trades",
  "sync my trades", "get my executions", "pull trades from broker",
  provides a CSV file, or wants to update their trade journal.
---

# Sync Trades

Get trade data from broker into the journal at
`accounts/{name}/journal/`. Two scripts handle the work:

- `scripts/fetch_ibkr.py` fetches CSV from IBKR's Flex Web Service API
- `scripts/parse_ibkr.py` parses CSV and writes journal + daily capital

## Manual mode

User provides a CSV file:

```bash
./run .claude/skills/sync-trades/scripts/parse_ibkr.py /path/to/LAFMM.csv accounts/{name}/
```

The script parses trades/cash/NAV and writes:
- `journal/` — trade entries for days with activity
- `capital/` — daily account value (every trading day from NAV)

Existing dates are skipped. Safe to re-run.

## Auto mode

Fetch from IBKR API, then parse:

```bash
# Read token and query_id from accounts/{name}/account.toml [broker.api] section
./run .claude/skills/sync-trades/scripts/fetch_ibkr.py \
  --token TOKEN --query-id QUERY_ID --out /tmp/trades.csv

./run .claude/skills/sync-trades/scripts/parse_ibkr.py /tmp/trades.csv accounts/{name}/
```

Extract token and query_id from `accounts/{name}/account.toml`:

```toml
[broker]
name = "Interactive Brokers"

[broker.api]
type = "ibkr-flex"
token = "..."
query_id = "..."
```

The Flex Query uses a 90-day rolling window. Every fetch returns
recent data. Dedup skips existing entries. Run anytime.

**Rate limits**: IBKR enforces 1 request/sec, 10 requests/min per
token (error 1018). The fetch script handles this with backoff.
Activity data updates once daily after close. Fetch once per day.

## What the scripts produce

Each trading day gets a journal file: `journal/YYYY/MM-DD.md`

```markdown
# 2026/04-10

Capital: $13,036.74
Dividend: +USD 0.42 (GOOG)
Tax: -USD 0.06 (GOOG)
Interest: +USD 4.23

## Trades

| time | symbol | side | qty | price | fees | order | pnl | open_close | signal |
|------|--------|------|-----|-------|------|-------|-----|------------|--------|
| 09:45 | NVDA | buy | 50 | 148.30 | 0.35 | limit | — | O | BUY 10(d) |
| 14:20 | AAPL | sell | 100 | 212.50 | 0.35 | stop | +320.00 | C | SELL 10(b) |

## Observations
```

- **Capital**: total account value (cash + positions) from NAV in Base
- **Cash flows**: deposits, withdrawals, dividends, tax, interest, fees in original currency
- **price/fees/pnl**: always base currency (USD). Non-USD trades are converted using `FXRateToBase` from the Flex Query at import time.
- **signal**: filled automatically from `cache/` during import.
  For dates after `tracked_since`, the script finds the most recent
  signal that matches the trade direction (BUY for buys, SELL for sells).
  WATCH/DANGER signals are skipped (informational, not actionable).
  A contradicting signal (e.g., SELL active but trader buys) stops the
  search and the trade is marked discretionary.
  `—` if no cache exists, date predates tracking, or no matching signal.

## After import

Report what changed. The parse script outputs JSON:

```json
{"trades": 3, "new_files": 1, "skipped": 46, "cash_flows": 0, ...}
```

Summarize for the user: "Synced 3 trades on 2026-04-10. 46 days
already up to date."

If `cache/` does not exist yet, all signals will be `—`. Tell the
user: "Run daily-update first to enable signal tracking."

## Place untracked tickers

The engine and stats need price history for every traded symbol.
After every import, check if any traded ticker is not in any tracked
group (`data/{group}/{TICKER}/`). If multiple untracked tickers are
found, look them all up in parallel using subagents, then decide
placement, then fetch prices in parallel. For each untracked ticker:

1. Look it up:
   ```bash
   ./run .claude/skills/sync-trades/scripts/ticker_meta.py NVDA
   ```
   Output: `NVDA   Technology / Semiconductors (EQUITY)`

   Use `--json` for structured output. Results are cached to
   `data/_meta/{TICKER}.json` automatically. If the script fails
   (no yfinance data), web search "what is {TICKER} stock" instead.
   Do not guess from training data.

2. Decide placement:
   - **Leveraged/inverse ETF** (`quoteType: ETF` and `category`
     indicates leveraged or inverse, e.g. "Trading--Leveraged Equity",
     "Trading--Inverse Equity"). These products don't trend like
     equities and will corrupt engine signals if placed in groups.
     Place in `data/_adhoc/{TICKER}/`.
   - **Equity that fits an existing group.** Read each group's
     `group.toml` name and the tickers already in it. If the stock's
     industry matches a group's theme, add it there. Example: AMD
     (industry: Semiconductors) fits `semis/` which has NVDA and AVGO.
   - **Equity with no matching group.** Place in `data/_adhoc/` for
     now. If the user repeatedly trades tickers in the same untracked
     sector, suggest creating a new group via build-watchlist.

3. For any ticker placed in a group or `_adhoc`:
   ```bash
   ./run .claude/skills/daily-update/scripts/fetch-prices.py {TICKER} --days 730
   ```
   If `data/_adhoc/{TICKER}/` already has data and you are moving
   it to a group, move the directory instead of re-fetching.

4. If any ticker was added to a tracked group, run `lafmm sync` to
   rebuild the cache so the new ticker gets engine state and signals.

Do not ask the user for confirmation. Use your judgment. Report what
you placed and where in your import summary.

## Attach tape observations

After import, check `~/.lafmm/tape.md` for pending tapes. The trader
records thoughts at trade time via `lafmm tape`. Each entry is a
`## YYYY-MM-DD` header followed by free text.

Paste matching tape entries into the journal's `## Observations`
section by date. Preserve the trader's voice verbatim. Remove
matched entries from `tape.md`. Flag any trades with no tape entry
and any tape entries with no matching trades.

## Signal timing

The engine processes closing prices. A signal fires after market
close on Day N. The trader sees it and can act on Day N+1 or later.

The matching algorithm walks backwards through all signals for the
ticker, strictly before the trade date:
1. Skip WATCH and DANGER (informational, not entry signals)
2. If the signal direction matches the trade (BUY + buy, SELL + sell),
   attach it
3. If the signal direction contradicts (BUY + sell, SELL + buy), stop
   searching. The trade goes against the system's most recent opinion.

No time limit. A BUY signal stays active until a SELL fires. This
is correct for trend-following: the signal IS the trend state.

Only dates after `tracked_since` from `account.toml` get signals.
Earlier entries predate LAFMM and stay as `—`.

## Setup

For IBKR Flex Query configuration, see `references/ibkr-setup.md`.

Future brokers: add a new parse script (e.g., `parse_schwab.py`).
Same journal output format, different input parsing.
