--- name: trader-memory-core description: Track investment theses across their lifecycle — from screening idea to closed position with postmortem. Register theses from screener outputs, manage state transitions, attach position sizing, review due dates, and generate postmortem reports with P&L and MAE/MFE analysis. Trigger when user says "register thesis", "track this idea", "thesis status", "review due", "close position", "postmortem", or "trading journal". --- # Trader Memory Core ## Overview Persistent state layer that bundles screening → analysis → position sizing → portfolio management outputs into a single "thesis object" per investment idea. Tracks what you thought, what happened, and what you learned — across conversations. Phase 1 supports single-ticker theses: dividend_income, growth_momentum, mean_reversion, earnings_drift, pivot_breakout. ## When to Use - After a screener (kanchi, earnings-trade-analyzer, vcp, pead, canslim, edge-candidate-agent) produces candidates - When transitioning a thesis from IDEA → ENTRY_READY → ACTIVE → CLOSED - When attaching position-sizer output to a thesis - When checking which theses are due for review - When closing a position and generating a postmortem with lessons learned ## Prerequisites - Python 3.10+ - `pyyaml` (already in project dependencies) - `jsonschema` (already in `pyproject.toml`; required by `thesis_store.py` and every command that imports it, including `thesis_ingest.py` and `thesis_review.py`) - FMP API key (optional, only for MAE/MFE calculation in postmortem) ### How to invoke the CLI Use the stdlib-only launcher `trader_memory_cli.py` for all CLI work. It transparently routes through `uv run --project ` when `uv` is available, so the repo's pinned `jsonschema` is reachable even from a foreign cwd or from `python3` with no global `jsonschema` (e.g. cron / Hermes profile runs): ```bash # From inside the repo python3 skills/trader-memory-core/scripts/trader_memory_cli.py store --state-dir state/theses list # From any other cwd (cron, profile, distribution runner) — point the launcher at the repo export CLAUDE_TRADING_SKILLS_REPO=/path/to/claude-trading-skills python3 "$CLAUDE_TRADING_SKILLS_REPO/skills/trader-memory-core/scripts/trader_memory_cli.py" \ store --state-dir /path/to/state/theses list ``` Subcommands: `store` → `thesis_store.py`, `ingest` → `thesis_ingest.py`, `review` → `thesis_review.py`. Everything after the subcommand is forwarded verbatim, so existing argument flags (`--state-dir`, `transition`, `open-position`, etc.) work unchanged. If the launcher reports that `jsonschema` is not importable AND `uv` is not on `PATH`, the actionable fixes (in priority order) are: 1. Install `uv` (https://docs.astral.sh/uv/) and re-run the launcher. 2. Install the project's dependencies into the current interpreter: ```bash uv pip install -e /path/to/claude-trading-skills # or, as a last resort: python3 -m pip install jsonschema ``` Do **not** treat the thesis store as unavailable and do **not** mutate `state/theses/*.yaml` by hand to work around a missing dependency — schema validation is part of thesis state integrity. ## Workflow ### 1. Register — Ingest screener output as thesis Read the screener's JSON output and convert to thesis using the appropriate adapter. ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py ingest \ --source kanchi-dividend-sop \ --input reports/kanchi_entry_signals_2026-03-14.json \ --state-dir state/theses/ ``` Supported sources: `kanchi-dividend-sop`, `earnings-trade-analyzer`, `vcp-screener`, `pead-screener`, `canslim-screener`, `edge-candidate-agent`, `manual`. Each thesis starts in `IDEA` status. For `kanchi-dividend-sop`, registration is fail-closed: each row must carry one of `CLEAN-PASS`, `PASS-CAUTION`, or `CONDITIONAL-PASS` in `verdict`. Missing verdicts and `HOLD-REVIEW` / `STEP1-RECHECK` / `FAIL` rows are skipped and never written to thesis state. #### Manual brokerage entry (fractional shares) For trades that did **not** come from a screener — e.g. fractional-share brokers (IBKR, Robinhood, IBI Smart, Alpaca, eToro) or hand journaling — use the `manual` source with a free-form JSON file (a single object or an array): ```json { "ticker": "AMD", "thesis_statement": "AMD AI accelerator momentum, fractional IBI Smart position", "thesis_type": "growth_momentum", "entry_price": 142.10, "entry_date": "2026-05-02", "shares": 7.86, "stop_price": 128.00 } ``` ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py ingest \ --source manual --input amd.json --state-dir state/theses/ ``` Required: `ticker`, `thesis_statement`, `thesis_type` (one of `dividend_income`, `growth_momentum`, `mean_reversion`, `earnings_drift`, `pivot_breakout`). `stop_price`/`stop_loss` and `target_price`/`take_profit` map to `exit.stop_loss`/`exit.take_profit`; `entry_price`/`entry_date`/`shares` are kept in `origin.raw_provenance` — the authoritative entry price/date and share count are set when you open the position (below). `shares` may be **fractional** (the schema accepts any positive number). Like every adapter, manual ingest creates an `IDEA` thesis only — it never mutates status directly. To record an **already-open broker position**, run the explicit lifecycle sequence (the `--event-date` flags backdate the history so it stays chronological): ```bash # 1. ingest → IDEA (stamped at entry_date) python3 .../trader_memory_cli.py ingest --source manual --input amd.json --state-dir state/theses/ # 2. IDEA → ENTRY_READY (backdated) python3 .../trader_memory_cli.py store --state-dir state/theses/ transition ENTRY_READY \ --reason "existing IBI Smart position" --event-date 2026-05-02 # 3. ENTRY_READY → ACTIVE (fractional shares, backdated) python3 .../trader_memory_cli.py store --state-dir state/theses/ open-position \ --actual-price 142.10 --actual-date 2026-05-02 --shares 7.86 --event-date 2026-05-02 ``` ### 2. Query — Search and list theses ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py store \ --state-dir state/theses/ list --ticker AAPL --status ACTIVE ``` Filter by `--ticker`, `--status`, or `--type`. ### 3. Update — Transition, attach position, link reports Each lifecycle operation is available **both** as a Python function and as a `thesis_store.py` CLI subcommand. `--event-date` / `--actual-date` accept a plain `YYYY-MM-DD` (widened to midnight UTC) or a full ISO timestamp. **State transition** (IDEA → ENTRY_READY only): ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py store --state-dir state/theses/ \ transition ENTRY_READY --reason "validated" [--event-date YYYY-MM-DD] ``` `--event-date` backdates `status_history.at` (use it when backfilling an existing position so the later backdated `open-position` stays chronological). Python: `thesis_store.transition(state_dir, thesis_id, "ENTRY_READY", reason, event_date=...)`. **Open position** (ENTRY_READY → ACTIVE — the only path to ACTIVE): ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ open-position \ --actual-price 142.10 --actual-date 2026-05-02 [--shares 7.86] [--event-date 2026-05-02] ``` `--shares` accepts **fractional** quantities. Python: `thesis_store.open_position(state_dir, thesis_id, actual_price, actual_date, shares=..., event_date=...)`. `shares` (and `shares_remaining`, when present) must be a **finite, positive number no greater than 1012** (a sanity bound, not an economic constraint — fractional shares below the cap remain unrestricted). NaN, ±Infinity, and absurdly large values (e.g. a malformed position-sizer report) are rejected with a clean error at save time, on `open-position`, `attach-position`, and `trim` alike. For a **futures** thesis, use `--contracts` instead of `--shares` (see "Futures positions" below) — if `attach-futures-position` already populated the position, omit `--contracts` and only pass `--actual-price`/`--actual-date`. **Trim — partial close** (ACTIVE/PARTIALLY_CLOSED → PARTIALLY_CLOSED, or → CLOSED when the whole remainder is sold): ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ trim \ --shares-sold 4 --price 120.00 --date 2026-05-10 ``` `position.shares` is the **original** opened quantity (immutable); `position.shares_remaining` tracks what is still open. Each trim appends a `status_history` ledger entry (`shares_sold` / `price` / `proceeds` / `realized_pnl`). `outcome.pnl_dollars` is the **cumulative** realized P&L (Σ all trims + final close); `outcome.pnl_pct = pnl_dollars / (entry_price × original_shares) × 100`. A trim that sells the entire remainder closes the thesis (default `exit_reason: manual`, overridable with `--exit-reason`). `--date` is the ledger timestamp (override with `--event-date`). Python: `thesis_store.trim(state_dir, thesis_id, shares_sold, price, date, ...)`. Status invariants: `ACTIVE` ⇒ `shares_remaining == shares`; `PARTIALLY_CLOSED` ⇒ `0 < shares_remaining < shares`; `CLOSED` ⇒ `shares_remaining == 0`. Legacy theses (no `shares_remaining`) are treated as fully open at runtime. For a **futures** thesis, use `--contracts-sold` instead of `--shares-sold` — `close`/`terminate` need no flag changes; they read `position.asset_type` and dispatch automatically (see "Futures positions" below). **Close or invalidate** (→ CLOSED or INVALIDATED): ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ close \ --exit-reason target_hit --actual-price 165.00 --actual-date 2026-06-01 python3 .../trader_memory_cli.py store --state-dir state/theses/ terminate \ --terminal-status INVALIDATED --exit-reason "thesis broke" ``` `close` accepts an `ACTIVE` **or** `PARTIALLY_CLOSED` thesis; from PARTIALLY_CLOSED it adds the final leg and reports the cumulative outcome. Python: `thesis_store.terminate(state_dir, thesis_id, terminal_status, exit_reason, actual_price, actual_date)`. For CLOSED, delegates to `close()` which computes P&L (fractional-share aware). For INVALIDATED, P&L is computed if entry/exit prices are available. **Record review** (any non-terminal): Use `thesis_store.mark_reviewed(state_dir, thesis_id, review_date=..., outcome="OK"|"WARN"|"REVIEW")` to advance next_review_date and record alerts. **Attach position-sizer output:** ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ attach-position \ --report reports/position_report.json ``` Python: `thesis_store.attach_position(state_dir, thesis_id, report_path)` to link position sizing data. Validates that the report mode is "shares" (not budget). #### Futures positions (contracts / multiplier / direction) A thesis whose `position.asset_type == "futures"` (or `quantity_unit == "contracts"`) is a **futures** thesis. Futures theses use `quantity` / `quantity_remaining` (whole contracts — no fractional contracts) instead of `shares` / `shares_remaining`, carry a `direction` (`LONG` or `SHORT`) and a `multiplier`, and every P&L computation (`close`, `terminate`, `trim`) applies `(exit_price - entry_price) × multiplier × quantity × sign` (sign = +1 LONG, −1 SHORT) instead of the equity per-unit formula. `close` / `terminate` / `trim` / `open-position` all dispatch on `position.asset_type` automatically — no separate futures subcommands for those four operations. **USD-denominated contracts only** — there is no FX conversion in the P&L path, so a non-USD `contract_spec.currency` is rejected outright rather than computing P&L in the wrong currency's magnitude. **Attach a futures-position-sizer SIZED report** (step 6 of the Shapiro contrarian pipeline — futures-position-sizer → trader-memory-core): ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ \ attach-futures-position --report reports/futures_position_es_2026-05-10.json ``` Rejects a `NO_TRADE` report (`sizing_status != "SIZED"`), an invalid `direction`, a non-positive/fractional `contracts` count, a non-finite/non-positive `contract_spec.multiplier`, or a non-USD `contract_spec.currency`. Re-attach status guard is `IDEA`/`ENTRY_READY` **only** — stricter than equity's `attach-position` (which also allows `ACTIVE`): re-attaching a futures position on `ACTIVE` would silently overwrite the entire position dict including `direction`, flipping the sign of every subsequent P&L computation. Correcting an already-open futures position needs a fresh thesis (or a future dedicated "amend" operation) — not a re-attach. **Direct open, no attach** (build the position from CLI flags instead of a SIZED report — `--contract-currency` is **required** here since there is no `contract_spec` to read a currency from, and must be `USD`): ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ open-position \ --actual-price 5000 --actual-date 2026-05-10 \ --contracts 2 --multiplier 50 --direction SHORT --contract-symbol ES \ --contract-currency USD ``` **Trim / close / terminate** — same subcommands as equity, `--contracts-sold` in place of `--shares-sold`: ```bash python3 .../trader_memory_cli.py store --state-dir state/theses/ trim \ --contracts-sold 1 --price 4950.00 --date 2026-05-12 python3 .../trader_memory_cli.py store --state-dir state/theses/ close \ --exit-reason target_hit --actual-price 4900.00 --actual-date 2026-05-15 ``` Python: `thesis_store.attach_futures_position(state_dir, thesis_id, report_path)`, `thesis_store.open_position(state_dir, thesis_id, actual_price, actual_date, contracts=..., multiplier=..., direction=...)`. **Link related reports:** Use `thesis_store.link_report(state_dir, thesis_id, skill, file, date)` to cross-reference analysis documents. ### 4. Review — Check due dates and monitoring status ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py review \ --state-dir state/theses/ review-due --as-of 2026-04-15 ``` List theses with `next_review_date <= as_of`. Use with kanchi-dividend-review-monitor triggers (T1-T5) for systematic review. ### 5. Postmortem — Close and reflect ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py review \ --state-dir state/theses/ postmortem th_aapl_div_20260314_a3f1 ``` Generate a structured postmortem in `state/journal/`. If FMP API key is available, includes MAE/MFE (Maximum Adverse/Favorable Excursion) metrics. **Summary statistics:** ```bash python3 skills/trader-memory-core/scripts/trader_memory_cli.py review \ --state-dir state/theses/ summary ``` Shows win rate, average P&L%, and per-type breakdown across all closed theses. ## Output Format ### Thesis YAML (state/theses/) Each thesis is a YAML file with: - Identity: thesis_id, ticker, created_at - Classification: thesis_type, setup_type, catalyst - Lifecycle: status, status_history - Entry/Exit: target prices, actual prices, conditions - Position: shares (fractional supported), value, risk (from position-sizer or `open-position --shares`); or, for futures, quantity/multiplier/direction/contract_spec (from futures-position-sizer or `open-position --contracts`) - Monitoring: review dates, triggers, alerts - Origin: source skill, screening grade, raw provenance - Outcome: P&L, holding days, MAE/MFE, lessons learned ### Index (state/theses/_index.json) Lightweight index for fast queries without loading full YAML files. ### Journal (state/journal/) Postmortem markdown reports: `pm_{thesis_id}.md`. ## Supported Python Read and Validation API Use these public functions from `scripts/thesis_store.py` for replay and other Python consumers: ```python from pathlib import Path import thesis_store thesis = thesis_store.get(Path("state/theses"), thesis_id) thesis_store.validate_thesis(thesis) ``` - `get(state_dir, thesis_id)` loads fresh YAML data without modifying the thesis file or index. It does not validate the loaded content. Missing files raise `FileNotFoundError`; malformed YAML raises `yaml.YAMLError`. - `validate_thesis(thesis)` checks the JSON Schema and business invariants, returns `None` on success, and raises `ValueError` on validation failure. It does not mutate the input or write state. - Validate loaded or normalized records explicitly before relying on their schema and business invariants. Keep consumers on these supported functions; underscore-prefixed helpers are internal implementation details. ## Key Principles - **Forward-only transitions**: IDEA → ENTRY_READY → ACTIVE → CLOSED (no backtracking) - **Raw provenance**: All original screener data preserved in `origin.raw_provenance` - **Atomic writes**: All file operations use tempfile + os.replace - **Git-tracked state**: `state/` directory is committed, providing audit trail - **Phase 1 scope**: Single-ticker theses only (pair trades and options in Phase 2) ## Resources - `references/thesis_lifecycle.md` — Status states and valid transitions - `references/field_mapping.md` — Source skill → canonical field mapping - `schemas/thesis.schema.json` — JSON Schema for thesis validation - `../../examples/workflows/trade-memory-loop/sample-run-full-path/` — Worked end-to-end Plan → Trade → Record → Postmortem → Backtest → Journal example