trade-replay
Purpose
Answer deep questions about ONE closed trade, or a small set of them. Where did the trade peak? How close did MAE come to the stop? What do four alternate exit rules return, on the same bar window?
Environment routing
Demo (simulation) and live are two separate MCP servers. Account names are unique to one server. Once a workflow resolves an account on a server, every downstream call must go through that same server. This includes my_portfolio, market_snapshot, place_order, create_alert, history tools, etc. Cross-routing fails or hits the wrong environment.
MCP tools used
Tool names below are bare. The NinjaTrader MCP server provides them. Your client adds its own prefix. See AGENTS.md at the repo root.
position_history — closed-position pairs
with entry/exit prices and times
fill_history — fill-level timestamps for
trades not fully covered by position_history
market_history — bar OHLC across the trade
lifetime + a short lookback after for counterfactual runs
search_contracts — tickSize,
valuePerPoint for the trade
Skills consumed
market-context's atr.py — ATR for atr_trail scenarios
trade-journal's streaks.py — if the user references a trade
that is not in position_history's aggregation, use this to pair
fills into trades
Workflow
1. Identify the trade
The user names a trade by date/time/symbol or picks from a recent list. Resolve via position_history:
position_history(
account=<acct>,
startDate=<YYYY-MM-DD>,
endDate=<YYYY-MM-DD>
)
(startDate and endDate are date-level. They use YYYY-MM-DD or a natural term like "yesterday", never a timestamp.)
Take avgBuyPrice / avgSellPrice (or buyPrice / sellPrice), boughtTimestamp / soldTimestamp, pairedQty, symbol — derive direction from the sign of netPos. Fetch contract specs via search_contracts for tickSize + valuePerPoint.
The caller must also know planned_stop_points — the stop distance the user chose at trade entry. This value denominates the R-multiple outputs and adverse_used_pct_of_stop. A wrong guess silently mis-scales every counterfactual. Source options, in order of preference:
- Conversation context — the user told you when they opened the
trade.
pretrade-risk's recorded output (if the trade went through
that skill).
- Ask the user. If neither (1) nor (2) gives a value, request
it explicitly before falling back. Never default silently.
- Last-resort default (e.g., 1×ATR), only after the user declines
to give one.
Flag every output with "reconstructed stop — R-values are approximate", so the user reads them as such.
Descriptive asks do not block on the stop value. A descriptive ask is a question such as "how far did it go my way / against me?" or MFE/MAE. For such an ask, run the replay immediately, without planned_stop_points. excursion.py handles its absence and emits null R-fields. Report excursion in points and dollars. Note "no stop distance on record — R-multiples omitted". Offer to recompute in R, if the user supplies the stop. Only R-denominated outputs and stop-referencing counterfactuals, such as breakeven_after, atr_trail, and R-targets, need a value up front.
2. Fetch bars covering the trade + a lookback
market_history(
symbol=<sym>,
barType="Minute",
barSize=1,
from=<entry_time - 1min, ISO-8601>,
to=<exit_time + 15min, ISO-8601> # buffer for what-ifs that might exit later
)
(from requires to. If you pass only one, it errors.)
The 15-min post-exit buffer lets counterfactuals explore "what if I'd held longer", so the bar scan does not run out of data.
3. Compute excursion + derived metrics
Save the trade-with-bars payload to a file. Pass its path with --file. Never re-type or inline a large JSON payload in the command.
python3 scripts/excursion.py --file trade_with_bars.json
Output fields:
realized.{points,dollars,R} — actual trade P&L
mfe.{points,dollars,R,at_timestamp,at_price} — peak favorable
mae.{points,dollars,R,at_timestamp,at_price} — peak adverse
derived.giveback_from_mfe_pct — (mfe − realized) / mfe × 100
derived.realized_vs_mfe_pct — realized / mfe × 100
derived.adverse_used_pct_of_stop — mae / planned_stop × 100
derived.hold_minutes
4. Run counterfactual(s)
Choose a scenario based on user intent. See references/replay-patterns.md for the catalog:
| User phrasing |
Script invocation |
| "What if 2R target?" |
whatif.py --scenario r_target --r-target 2.0 |
| "What if a 1×ATR trail?" |
whatif.py --scenario atr_trail --atr-points <atr> --atr-multiple 1.0 |
| "What if breakeven after 1R?" |
whatif.py --scenario breakeven_after --trigger-R 1.0 |
| "What if I'd held 30 min?" |
whatif.py --scenario time_exit --minutes 30 |
Each run produces:
exit.{triggered,time,price,reason} — whether the scenario fired
and where
realized.{points,dollars,R} — simulated P&L
vs_actual.{delta_points,delta_dollars,delta_R} — vs the user's
actual exit, when the trade input includes actual_exit_price
5. Multiple scenarios — sequential compare
For "which rule would have won?" questions, run each scenario in sequence. Rank the results by vs_actual.delta_dollars. There is no batch.py script. Just call whatif.py N times in the workflow:
for spec in \
"r_target --r-target 2.0" \
"r_target --r-target 3.0" \
"atr_trail --atr-points 8 --atr-multiple 1.0" \
"breakeven_after --trigger-R 1.0"; do
python3 scripts/whatif.py --file trade_with_bars.json --scenario $spec
done
6. Narrate — with survivorship caveat if prescriptive
When the user's question implies a rule they might adopt ("should I start using 2R targets?"), include the survivorship caveat from references/survivorship-caveat.md:
"One-trade counterfactual. For a rule-level evaluation across your
book, route through trade-debrief."
For descriptive-only asks ("how far did it go?"), skip the caveat.
Output idioms
MFE + giveback:
"Long 4 ES @ 7150, exited 7164 (+$2,800 / 1.75R). MFE +22.5 pts
(+$4,500 / 2.8R) at 14:02 — left $1,700 on the table. MAE only
1 pt (used 12% of your 8-pt stop). Held 47 min."
Single what-if:
"With a 2R target (7166), you'd have exited at 13:52 for +$3,200
— $400 better than your actual $2,800. One-trade counterfactual,
not a rule endorsement."
Multi-scenario compare:
"Ranked by $ vs your actual $2,800:
- time_exit 30min (exit 14:02 @ 7168.75): +$3,750 (+$950)
- r_target 2.0 (exit 13:52 @ 7166): +$3,200 (+$400)
- atr_trail 1×ATR (exit 14:07 @ 7164.50): +$2,900 (+$100)
- r_target 3.0 (never hit, fell through): +$2,250 (−$550)
- breakeven_after 1R (armed, fell through): +$2,250 (−$550)
Patient rules (time_exit, r_target 2.0) beat your actual exit by
$400-950. Tighter rules (atr_trail) roughly matched. Breakeven
pin was tied with the hopeful 3R target at the bottom — classic
wait-too-long pattern. Still just one trade — run across N trades
in trade-debrief if you're serious about a rule change."
Disambiguation
- vs
trade-journal: journal is many-trades descriptive —
streaks, hold times, slippage.
Replay is a one-trade deep dive.
For "how far did trade #3 go today?", use replay.
For "how did I trade today?", use journal.
- vs
trade-debrief: debrief synthesizes mistake, pattern, and
rule mining across a whole session, with the trade-debrief prompt
library. Replay gives numeric counterfactuals on one trade. Use
debrief when the user asks for prescriptive rules.
- vs
position-watchdog: watchdog's what-if runs on the
currently OPEN position (live). Replay covers closed trades. Do
not use replay on an open position. The bars are not all there
yet.
Explicit non-goals
- Not backtesting. No rule simulation across market history. Only
counterfactuals on the user's actual fills with real bar data in
the trade's window.
- Not a rule endorsement engine. One-trade deltas are not rule
claims. The survivorship caveat is mandatory for prescriptive asks.
- Not live. Replay is for CLOSED trades only.
- No stop-tightening-at-time-T scenario.
For "what if I had tightened the stop 5 min in", the skill does not script this.
It needs a rerun with a modified
planned_stop_points.
Explain this to the user.
Resource layout
scripts/excursion.py — MFE/MAE + derived metrics for one trade.
Same math family as trade-debrief/scripts/compute_derived.py.
Keep them in sync when the math evolves. Returns realized, mfe,
mae, and a derived sub-object.
scripts/whatif.py — counterfactual exit simulator. Four scenarios:
r_target, atr_trail, breakeven_after, time_exit. Input is
one trade plus bars. Output is the hypothetical exit, the realized
stats, and the delta vs actual.
scripts/fixtures/es_long_trade.json — one ES long trade (4 @ 7150
→ 7164 in 47 min) with 11 1-min bars covering entry through 15min
post-exit. The skill uses it to smoke-test all four scenarios.
references/replay-patterns.md — the 7-entry what-if library with
script invocations, common parameter values, and interpretation
notes. Load it when you choose a what-if scenario.
references/survivorship-caveat.md — mandatory framing for rule-
shaped what-ifs. When NOT to cite it and what to route the user to
instead (trade-journal, trade-debrief).
1---2name: trade-replay3description: A deep dive on ONE closed trade. It reconstructs the trade's MFE (maximum favorable excursion) and MAE (maximum adverse excursion) across its lifetime, from bar OHLC. Then it runs alternate-exit counterfactuals against the same bars. The counterfactual scenarios are a fixed R target, an N-times-ATR trailing stop, a breakeven-after-X-R pin, and a time-based exit. Outputs include the exit timestamp, price, and reason. They also include the P&L in points, dollars, and R, and the delta vs the actual exit. Use it when the user asks "replay that trade", or asks for "MFE", "MAE", or "excursion" on a specific trade. Also use it for "what if I had held for X", "what if I'd used a trailing stop", "what if I'd moved my stop to breakeven", "how far did it go", "could have had", or "should have held". This is NOT backtesting. It runs no rule simulation across market history. Counterfactuals use only the user's actual fills. For many-trades summaries, use trade-journal. For session-level rule-mining, use trade-debrief.4---56# trade-replay78## Purpose910Answer deep questions about ONE closed trade, or a small set of them. Where did the trade peak? How close did MAE come to the stop? What do four alternate exit rules return, on the same bar window?1112## Environment routing1314Demo (simulation) and live are two separate MCP servers. Account names are unique to one server. Once a workflow resolves an account on a server, every downstream call must go through that same server. This includes `my_portfolio`, `market_snapshot`, `place_order`, `create_alert`, history tools, etc. Cross-routing fails or hits the wrong environment.1516## MCP tools used1718Tool names below are bare. The NinjaTrader MCP server provides them. Your client adds its own prefix. See `AGENTS.md` at the repo root.1920- `position_history` — closed-position pairs21 with entry/exit prices and times22- `fill_history` — fill-level timestamps for23 trades not fully covered by `position_history`24- `market_history` — bar OHLC across the trade25 lifetime + a short lookback after for counterfactual runs26- `search_contracts` — `tickSize`,27 `valuePerPoint` for the trade2829## Skills consumed3031- `market-context`'s `atr.py` — ATR for `atr_trail` scenarios32- `trade-journal`'s `streaks.py` — if the user references a trade33 that is not in `position_history`'s aggregation, use this to pair34 fills into trades3536## Workflow3738### 1. Identify the trade3940The user names a trade by date/time/symbol or picks from a recent list. Resolve via `position_history`:4142```43position_history(44 account=<acct>,45 startDate=<YYYY-MM-DD>,46 endDate=<YYYY-MM-DD>47)48```4950(`startDate` and `endDate` are date-level. They use `YYYY-MM-DD` or a natural term like `"yesterday"`, never a timestamp.)5152Take `avgBuyPrice` / `avgSellPrice` (or `buyPrice` / `sellPrice`), `boughtTimestamp` / `soldTimestamp`, `pairedQty`, `symbol` — derive direction from the sign of `netPos`. Fetch contract specs via `search_contracts` for `tickSize` + `valuePerPoint`.5354The caller must also know `planned_stop_points` — the stop distance the user chose at trade entry. **This value denominates the R-multiple outputs and `adverse_used_pct_of_stop`. A wrong guess silently mis-scales every counterfactual.** Source options, in order of preference:55561. Conversation context — the user told you when they opened the57 trade.582. `pretrade-risk`'s recorded output (if the trade went through59 that skill).603. **Ask the user.** If neither (1) nor (2) gives a value, request61 it explicitly before falling back. Never default silently.624. Last-resort default (e.g., 1×ATR), only after the user declines63 to give one.64 Flag every output with "reconstructed stop — R-values are approximate", so the user reads them as such.6566**Descriptive asks do not block on the stop value.** A descriptive ask is a question such as "how far did it go my way / against me?" or MFE/MAE. For such an ask, run the replay immediately, without `planned_stop_points`. `excursion.py` handles its absence and emits null R-fields. Report excursion in points and dollars. Note "no stop distance on record — R-multiples omitted". Offer to recompute in R, if the user supplies the stop. Only R-denominated outputs and stop-referencing counterfactuals, such as `breakeven_after`, `atr_trail`, and R-targets, need a value up front.6768### 2. Fetch bars covering the trade + a lookback6970```71market_history(72 symbol=<sym>,73 barType="Minute",74 barSize=1,75 from=<entry_time - 1min, ISO-8601>,76 to=<exit_time + 15min, ISO-8601> # buffer for what-ifs that might exit later77)78```7980(`from` requires `to`. If you pass only one, it errors.)8182The 15-min post-exit buffer lets counterfactuals explore "what if I'd held longer", so the bar scan does not run out of data.8384### 3. Compute excursion + derived metrics8586Save the trade-with-bars payload to a file. Pass its path with `--file`. Never re-type or inline a large JSON payload in the command.8788```bash89python3 scripts/excursion.py --file trade_with_bars.json90```9192Output fields:93- `realized.{points,dollars,R}` — actual trade P&L94- `mfe.{points,dollars,R,at_timestamp,at_price}` — peak favorable95- `mae.{points,dollars,R,at_timestamp,at_price}` — peak adverse96- `derived.giveback_from_mfe_pct` — (mfe − realized) / mfe × 10097- `derived.realized_vs_mfe_pct` — realized / mfe × 10098- `derived.adverse_used_pct_of_stop` — mae / planned_stop × 10099- `derived.hold_minutes`100101### 4. Run counterfactual(s)102103Choose a scenario based on user intent. See `references/replay-patterns.md` for the catalog:104105| User phrasing | Script invocation |106|---------------|-------------------|107| "What if 2R target?" | `whatif.py --scenario r_target --r-target 2.0` |108| "What if a 1×ATR trail?" | `whatif.py --scenario atr_trail --atr-points <atr> --atr-multiple 1.0` |109| "What if breakeven after 1R?" | `whatif.py --scenario breakeven_after --trigger-R 1.0` |110| "What if I'd held 30 min?" | `whatif.py --scenario time_exit --minutes 30` |111112Each run produces:113- `exit.{triggered,time,price,reason}` — whether the scenario fired114 and where115- `realized.{points,dollars,R}` — simulated P&L116- `vs_actual.{delta_points,delta_dollars,delta_R}` — vs the user's117 actual exit, when the trade input includes `actual_exit_price`118119### 5. Multiple scenarios — sequential compare120121For "which rule would have won?" questions, run each scenario in sequence. Rank the results by `vs_actual.delta_dollars`. There is no `batch.py` script. Just call `whatif.py` N times in the workflow:122123```bash124for spec in \125 "r_target --r-target 2.0" \126 "r_target --r-target 3.0" \127 "atr_trail --atr-points 8 --atr-multiple 1.0" \128 "breakeven_after --trigger-R 1.0"; do129 python3 scripts/whatif.py --file trade_with_bars.json --scenario $spec130done131```132133### 6. Narrate — with survivorship caveat if prescriptive134135When the user's question implies a rule they might adopt ("should I start using 2R targets?"), include the survivorship caveat from `references/survivorship-caveat.md`:136137> "One-trade counterfactual. For a rule-level evaluation across your138> book, route through `trade-debrief`."139140For descriptive-only asks ("how far did it go?"), skip the caveat.141142## Output idioms143144**MFE + giveback:**145146> "Long 4 ES @ 7150, exited 7164 (+\$2,800 / 1.75R). MFE +22.5 pts147> (+\$4,500 / 2.8R) at 14:02 — left \$1,700 on the table. MAE only148> 1 pt (used 12% of your 8-pt stop). Held 47 min."149150**Single what-if:**151152> "With a 2R target (7166), you'd have exited at 13:52 for +\$3,200153> — \$400 better than your actual \$2,800. One-trade counterfactual,154> not a rule endorsement."155156**Multi-scenario compare:**157158> "Ranked by $ vs your actual \$2,800:159> 1. time_exit 30min (exit 14:02 @ 7168.75): +\$3,750 (+\$950)160> 2. r_target 2.0 (exit 13:52 @ 7166): +\$3,200 (+\$400)161> 3. atr_trail 1×ATR (exit 14:07 @ 7164.50): +\$2,900 (+\$100)162> 4. r_target 3.0 (never hit, fell through): +\$2,250 (−\$550)163> 5. breakeven_after 1R (armed, fell through): +\$2,250 (−\$550)164>165> Patient rules (time_exit, r_target 2.0) beat your actual exit by166> \$400-950. Tighter rules (atr_trail) roughly matched. Breakeven167> pin was tied with the hopeful 3R target at the bottom — classic168> wait-too-long pattern. Still just one trade — run across N trades169> in `trade-debrief` if you're serious about a rule change."170171## Disambiguation172173- **vs `trade-journal`**: journal is many-trades descriptive —174 streaks, hold times, slippage.175 Replay is a one-trade deep dive.176 For "how far did trade #3 go today?", use replay.177 For "how did I trade today?", use journal.178- **vs `trade-debrief`**: debrief synthesizes mistake, pattern, and179 rule mining across a whole session, with the trade-debrief prompt180 library. Replay gives numeric counterfactuals on one trade. Use181 debrief when the user asks for prescriptive rules.182- **vs `position-watchdog`**: watchdog's what-if runs on the183 currently OPEN position (live). Replay covers closed trades. Do184 not use replay on an open position. The bars are not all there185 yet.186187## Explicit non-goals188189- **Not backtesting.** No rule simulation across market history. Only190 counterfactuals on the user's actual fills with real bar data in191 the trade's window.192- **Not a rule endorsement engine.** One-trade deltas are not rule193 claims. The survivorship caveat is mandatory for prescriptive asks.194- **Not live.** Replay is for CLOSED trades only.195- **No stop-tightening-at-time-T scenario.**196 For "what if I had tightened the stop 5 min in", the skill does not script this.197 It needs a rerun with a modified `planned_stop_points`.198 Explain this to the user.199200## Resource layout201202- `scripts/excursion.py` — MFE/MAE + derived metrics for one trade.203 Same math family as `trade-debrief/scripts/compute_derived.py`.204 Keep them in sync when the math evolves. Returns `realized`, `mfe`,205 `mae`, and a `derived` sub-object.206- `scripts/whatif.py` — counterfactual exit simulator. Four scenarios:207 `r_target`, `atr_trail`, `breakeven_after`, `time_exit`. Input is208 one trade plus bars. Output is the hypothetical exit, the realized209 stats, and the delta vs actual.210- `scripts/fixtures/es_long_trade.json` — one ES long trade (4 @ 7150211 → 7164 in 47 min) with 11 1-min bars covering entry through 15min212 post-exit. The skill uses it to smoke-test all four scenarios.213- `references/replay-patterns.md` — the 7-entry what-if library with214 script invocations, common parameter values, and interpretation215 notes. Load it when you choose a what-if scenario.216- `references/survivorship-caveat.md` — mandatory framing for rule-217 shaped what-ifs. When NOT to cite it and what to route the user to218 instead (`trade-journal`, `trade-debrief`).