| name | retrospective |
| description | Weekly pattern-mining pass over the trade journal. Reads the journal via bt-gateway (`store.listJournal()`), clusters closed trades by trade_type, sector, exit_reason, and thesis_verdict, identifies patterns that repeat (both successes and failures), and appends distilled observations to `LESSONS.md`. Run this skill once a week, typically Friday evening after the post-close run, or on-demand when the user asks for a performance review, a post-mortem on a losing streak, or wants to revisit what the strategy has learned. Also trigger before making larger-than-usual strategy changes — the lessons are the empirical grounding for any rule adjustments. |
Retrospective
Turn the raw trade journal into durable lessons. This skill is the feedback loop that keeps PROJECT.md honest.
Cadence
- Weekly: Friday evening run, after
tax-tracker. Covers trades closed in the last 7 days.
- Monthly: First Friday of each month, covers the prior month and consolidates weekly entries.
- On-demand: When the user asks for a review, after a streak (3+ losses in a row), or before any proposed change to
PROJECT.md.
Inputs
- The trade journal via bt-gateway — full history (read via
store.listJournal())
LESSONS.md — current distilled lessons (to avoid duplicating what's already there)
PROJECT.md — so lessons can be flagged against specific rules they reinforce or challenge
Process
0. Run the stats script first
Do not hand-parse the journal. Call the committed script, which owns the clustering math and produces the numbers this skill then interprets:
node scripts/journal_stats.mjs --window=7 # weekly
node scripts/journal_stats.mjs --window=30 # monthly
node scripts/journal_stats.mjs --since=2026-01-01 # on-demand range
Output is a JSON blob with overall stats, per-cluster breakdowns (trade_type, sector, exit_reason, thesis_verdict, conviction bucket, theme_tag, rule_id, catalyst×mechanism grid), and the failure-mode hot cells already tagged. Steps 2-4 below describe what the script computes — they're documented so this skill's audit trail is reproducible, but do not re-implement them in prose.
1. Scope the window
For weekly: last 7 days of exit records. For monthly: last 30. For on-demand: whatever the user asks for, defaulting to "since last retrospective." Pass the window to journal_stats.py; do not filter in this skill.
2. Load and cluster (handled by journal_stats.py)
Parse the journal records (via store.listJournal()). Pair each entry with its exit (via trade_id). Drop pairs where the exit is outside the window. Cluster by:
- Trade type (swing / event / trend)
- Sector (energy, financials, utilities, etc.)
- Exit reason (take_profit, stop_loss, trailing_stop, time_stop, thesis_invalidated, override_exit, manual)
- Thesis verdict (correct / partially_correct / wrong / inconclusive)
- Entry conviction bucket (low 0-4, mid 5-7, high 8-10)
- Theme tag (from entry record — lets us compute per-theme win rates)
- Failure-mode pair
(catalyst_occurred, mechanism_worked) — the diagnostic grid below
3. Compute the numbers (handled by journal_stats.py)
For each cluster, compute:
- Count
- Win rate (pnl > 0)
- Average P&L %, median P&L %
- Average days held
- Expectancy:
(win_rate × avg_win) - (loss_rate × avg_loss)
4. Look for patterns
Write down any cluster where:
- Win rate deviates from overall by more than 15 points
- Expectancy is strongly positive or negative
- Thesis verdict skews in one direction
- The same exit reason appears 3+ times
- Overrides changed the outcome (compare pnl with/without override)
Failure-mode grid (from catalyst_occurred × mechanism_worked)
Build a 4×4 matrix and surface the hot cells:
| mechanism: yes | mechanism: no | mechanism: reversed | mechanism: different_path |
|---|
| catalyst: yes | correct | diagnosis wrong | exit wrong | lucky — don't generalize |
| catalyst: no | n/a | thesis never tested | n/a | n/a |
| catalyst: delayed | right-but-late | wrong horizon | complicated | n/a |
| catalyst: partial | partial win | soft wrong | soft wrong | n/a |
Each cell produces a distinct correction:
- diagnosis wrong (catalyst yes + mechanism no): update priors on how the catalyst drives price. Possibly the market no longer cares about this catalyst type, or we misread which lever moves.
- exit wrong (catalyst yes + mechanism yes_but_price_reversed): book partial at the catalyst next time; don't hold through the reversal.
- thesis never tested (catalyst no): investigate whether timing was wrong (catalyst delayed on our side) or the catalyst was never going to happen. If the latter repeats, tighten catalyst research.
- lucky (via_different_path): flag as
inconclusive in the lesson extraction; do NOT generalize this trade into a rule.
Trigger a proposed lesson whenever a single cell has 3+ trades with consistent outcome. Surface the cell and sample trade_ids in the weekly Telegram briefing so the user can see the specific pattern.
5. Draft lessons
Each candidate lesson must be:
- Specific — names a trade type, sector, exit mechanism, or market condition
- Evidenced — references the cluster and the sample size
- Actionable — suggests a concrete adjustment or confirmation of current rule
- Falsifiable — phrased so future data can prove it wrong
Bad: "Energy trades work well."
Good: "Energy swing trades entered on RSI<30 with above-average volume: 7 trades, 71% win rate, avg +4.2%. Confirms the current RSI oversold rule — continue."
Bad: "Stop losses are too tight."
Good: "4 of last 10 stop-losses triggered within 2 sessions on moves that reversed within a week. Suggests initial 10% stop may be too tight for BVB midcap volatility. Candidate: widen to 12% for trade_type=swing, sector=financials only. Revisit after 10 more trades."
6. Merge into LESSONS.md
Append new lessons under a dated heading. When a new lesson contradicts or refines an existing one, update the existing entry in place with a Revised: note and the date — don't orphan stale rules.
Mark lessons as:
[candidate] — observed but not yet enough data (n < 10)
[active] — enough evidence, feeds into daily synthesis
[retired] — contradicted by later data, kept for history with reason
7. Flag changes to PROJECT.md
If a lesson reaches [active] status and conflicts with a rule in PROJECT.md, add an entry to the "Proposed Rule Changes" section at the bottom of LESSONS.md with the specific rule and suggested edit. Do NOT edit PROJECT.md directly — changes go through the user for review.
7.5 Graph queries — causal patterns and skipped-setup counterfactuals
The numerical clustering above (steps 2-4) tells us what worked and what didn't — sorted by trade_type, sector, exit_reason. The graph adds why queries that the journal alone can't answer.
After journal_stats.mjs produces its JSON, run the following graphiti queries scoped to the same window.
Reality check before you run these: the queries below are semantic (search_nodes / search_facts) and frequently time out entirely at the current graph size (verified live — search_nodes returns "Query timed out"; get_episodes also times out above ~8 episodes). Treat this whole sub-section as optional graph enrichment, never load-bearing. The retrospective's actual substance comes from journal_stats.mjs (step 0, reliable) — these graph queries only add causal color when they happen to return.
Timeout policy: use a 60-second timeout per call. If a query times out (the common case), log it and continue. If all of A–E time out, that is fine — produce the retrospective from journal_stats alone and note "graph causal-query pass unavailable this week (MCP timeouts)." Do not block, retry in a loop, or raise max_episodes/limit to compensate — larger queries just time out harder. The durable fix is to move the underlying data (skips, counterfactuals) into the Firestore store so these become reliable journal-style reads (see the KNOWN GAP in session-context / counterfactual-mapper).
A. Mechanism × outcome — diagnosis vs. execution failures
mcp__graphiti__search_facts(
query: "mechanism failed catalyst occurred verdict wrong",
group_id: "auto_trader",
limit: 50
)
Cluster the returned facts by mechanism field. Surface any mechanism that:
- Failed (
mechanism_worked: no) in ≥3 trades within the window → the type of causal chain is misdiagnosed, not just the trade
- Reversed (
mechanism_worked: yes_but_price_reversed) in ≥3 trades → entry was right, exit/sizing is the bug
B. Theme × outcome
mcp__graphiti__search_nodes(
query: "Theme evidenced_by Trade verdict",
group_id: "auto_trader",
limit: 30
)
For each Theme node, count linked trades by verdict. A theme with ≥3 trades and >70% wrong/partially_correct → theme is weaker than we believed; flag it for the next THEMES.md edit pass.
C. News-to-trade lag — were entries grounded in recent news?
mcp__graphiti__search_facts(
query: "Trade entry preceded_by News materiality:high",
group_id: "auto_trader"
)
For each trade, find the most recent material news on its ticker before entry. Look for two signals:
- Trades where no
News event preceded entry within 7 days → we entered on technicals alone. Check win rate of this subset vs. news-anchored trades.
- Trades where high-materiality negative news preceded entry → check if those underperformed (we may be fading the wrong signals).
D. Skipped-setup counterfactuals — what did we miss?
mcp__graphiti__search_nodes(
query: "SkippedSetup counterfactual move_pct",
group_id: "auto_trader",
limit: 100
)
For every SkippedSetup in the window with an attached Counterfactual edge (i.e., the invalidation window has elapsed and counterfactual-mapper has scored it), cluster by reason and compute:
- Count
- Mean
move_pct_since_skip
- % that moved >10% in the same direction the setup predicted
Surface reasons where skipped setups consistently moved >10% in the predicted direction — those skip rules are leaving money on the table. Examples:
- If
liquidity_below_threshold skips moved +12% average → the 50,000 RON ADV cutoff is too strict
- If
competing_setup_higher_conviction skips outperformed the chosen trade by >5% → conviction scoring is miscalibrated
- If
regime_risk_off skips moved up >8% → REGIME-1 cash floor is too aggressive
These produce a distinct kind of lesson: non-action lessons. Format them with status [active-skip-rule] so they're visible alongside trade lessons but recognizable as decisions-about-decisions.
E. Retired-prior check — anything contradicted this week?
mcp__graphiti__search_nodes(
query: "prior superseded_by week",
group_id: "auto_trader"
)
If a prior in LESSONS.md [active] was contradicted by graphiti's automatic supersedence within the window, surface it. Either confirm the retirement in LESSONS.md (move to [retired]) or push back on the graph's call with the reason it should remain active.
8. Report via Telegram
Send a concise summary via telegram-reporter:
🔍 WEEKLY RETROSPECTIVE — [DATE]
WINDOW: [date range], N closed trades
OVERALL: [win rate]%, avg [pnl %], expectancy [X] RON
📈 WHAT WORKED
[1-3 bullets with numbers]
📉 WHAT DIDN'T
[1-3 bullets with numbers]
🧠 NEW LESSONS
[Title + status of lessons added to LESSONS.md]
⚠️ RULE CHANGE CANDIDATES
[Any proposed edits to PROJECT.md for user review]
LESSONS.md Structure
# BVB Engine — Lessons Learned
Distilled from the trade journal. Entries fall into three statuses: [candidate] (n<10), [active] (drives daily synthesis), [retired] (kept for history).
## Active Lessons
### [active] Energy swing trades on RSI<30 + volume surge
Added 2026-04-26. n=12, win rate 67%, expectancy +2.8% per trade.
Confirms PROJECT.md swing trade entry rule for this sector. Continue.
### [active] Stop-losses too tight for financials midcaps
Added 2026-05-10. n=11, 45% of financial sector stops triggered on day-1 or day-2 before reverting.
Suggests widening initial stop to 12% for sector=financials, trade_type=swing.
See Proposed Rule Changes below.
## Candidate Lessons
### [candidate] Event-driven trades around ex-dividend dates underperform
Added 2026-04-26. n=4, win rate 25%, avg -1.9%.
Too early to be conclusive but worth watching. Revisit at n=10.
## Retired Lessons
### [retired] Avoid trading on CPI release days
Added 2026-03-01, retired 2026-04-26.
Initial pattern (n=5) did not hold with more data (n=14, win rate flat vs baseline).
Kept for history; no action.
## Proposed Rule Changes
### Widen stop-loss for financials swing trades to 12%
Source: "Stop-losses too tight for financials midcaps" (active, n=11)
Current PROJECT.md rule: `Hard stop-loss at 10% per position`
Suggested edit: `Hard stop-loss at 10% per position (12% for sector=financials, trade_type=swing)`
Pending user review.
Persistence — commit LESSONS.md and THEMES.md to the current branch
LESSONS.md and THEMES.md are the only two pieces of long-term memory that live as files in git (everything else — journal, fills, portfolio state, considered candidates, market snapshots — is in Firestore via bt-gateway, scoped by the mode encoded in the API key). The routine's sandbox is ephemeral; any edit made to these files during a run is lost unless it's pushed to the repo.
Demo and live accounts run against different branches (demo and live) so their lessons and themes evolve independently — a demo-only failure mode should not feed live synthesis, and vice-versa. Always push to the branch the sandbox was checked out from, never hardcode main.
Whenever this skill modifies LESSONS.md (or macro-analyst modifies THEMES.md), the run MUST commit and push the change to the current branch:
cd <repo-root>
git add LESSONS.md THEMES.md
git commit -m "retrospective: <date> weekly lessons"
git push origin HEAD
HEAD resolves to whatever branch the sandbox is on (demo for demo runs, live for live runs) — so no env var or branch-name lookup is needed here.
If the push fails (auth, conflict, network), surface it via telegram-reporter and do NOT silently carry on — the next run will start from stale lessons. Treat a failed push the same as a missed run.
Anti-patterns
- Don't mine lessons from fewer than 4 closed trades. Noise dominates.
- Don't let lessons accumulate indefinitely. If
[active] count passes ~15, consolidate — the daily synthesis can't reason over 30 rules.
- Don't edit
PROJECT.md from this skill. The user is the gate.
- Don't drop
[retired] lessons. Future data may revive them, and the history of what didn't work is itself a lesson.