trade-journal
Purpose
Summarize what happened across closed trades. Three outputs:
- Overall + by-symbol + by-hour P&L — cheap. It runs over fill history alone.
- Streaks + hold-time distribution — from the same pass.
- Per-fill slippage vs arrival mid + interval VWAP — on demand. One fill runs per invocation, because the script fetches bars around that fill.
This skill is descriptive only. For "what should I change", use trade-debrief.
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.
fill_history— per-fill times, prices, and quantities.performance_summary— pre-aggregated win rate, profit factor, and drawdown. Cross-check it againststreaks.py's output.position_history— P&L, quantity, and timestamps per closed position (aggregate view). No hold-time or MFE/MAE field — usetimeline_report/timeline_detailsfor those.timeline_report— existing timeline annotations. Also the source for per-trademae/mfe/hold_minutes.market_history— arrival-window bars for slippage. Use this only when the user asks about slippage.order_historyandorder_details— to findarrivalTimestamp(order-submit time) for the slippage calculation.user_profile— discover available account names when the account is unknown.describe— call withtopic='response_format'for the compactformat="columns"output shape.
Workflow
Account resolution. Every history call below requires account=<name>. If the account name is not in the conversation context, call user_profile() first. Its response's accounts[].name field lists every account on this MCP server. Pass that name as account= on every later call.
1. Day/week summary — "how did I trade today?"
fill_history(account=<acct>, startDate=<YYYY-MM-DD or "today">, endDate=<YYYY-MM-DD>)
startDate/endDate are date-level. They use YYYY-MM-DD or a natural term like "today" or "this week", never a timestamp. format="columns" cuts multi-day responses by about 40-60% on result sets over 50 fills. See describe(topic='response_format').
To get P&L in dollars, run streaks.py with a per-product value-per-point. Save the fill_history result to a file. Pass its path with --file. Never re-type or inline a large JSON payload in the command.
python3 scripts/streaks.py --file fill_history.json \
--value-per-point-map ES:50 MES:5 NQ:20 MNQ:2 CL:1000
When the day is single-product, pass --value-per-point <N> instead.
Output fields consumed downstream:
overall.trades,overall.win_rate,overall.total_pnl_usdoverall.avg_hold_minutes_winnersvsavg_hold_minutes_losers— if there is a material gap (for example, winners held twice as long), narrate it.overall.longest_win_streak/longest_loss_streak/current_streakby_symbol— per-product breakdown for mixed sessionsby_hour_utc— time-of-day distribution
2. Single-fill slippage — "what was the slippage on that ES fill?"
Only run this when the user names a specific fill or a small set. It is expensive: one market_history call runs per fill.
Fetch bars around the fill. Two minutes before and two minutes after is enough. This window covers the arrival mid and the interval VWAP. The arrival mid comes from the bar that contains the order-submit time. The interval VWAP defaults to ±60s around the fill:
market_history(
symbol=<sym>,
barType="Minute",
barSize=1,
from=<fill_ts - 2min, ISO-8601>,
to=<fill_ts + 2min, ISO-8601>
)
(One-minute bars are the finest time granularity. There is no 30-second bar. from requires to.)
Pair the bars with the fill, pulled from fill_history or order_details. Save the combined {"fill": {...}, "bars": [...]} object to a file.
python3 scripts/slippage.py --file fill_and_bars.json \
--tick-size 0.25 \
--vwap-window-seconds 60
Output fields:
arrival_mid— midpoint at submit timeslippage_vs_mid(price units) +slippage_ticksinterval_vwap+slippage_vs_vwap
Sign convention: positive = adverse. See references/tca-definitions.md.
3. Position-level recap — MFE/MAE
When the user asks "how far in the money did that trade go?", pull timeline_report's trades[].mae/mfe/hold_minutes, or timeline_details's summary.mae/mfe/hold_minutes for one trade. position_history carries P&L, quantity, and timestamps for the closed position, but no MFE/MAE/hold-time field. No script runs for the aggregate view. If the user wants a bar-level replay, hand off to trade-replay.
Cost posture
streaks.pyruns overfill_historyalone. This is cheap and always safe.slippage.pyrequires a per-fillmarket_historyfetch. On-demand only. Never batch across a day's fill list.
Output idioms
- Day summary: "12 trades today, 7 winners (58% win rate), net +$1,420. Winners held avg 18 min, losers avg 8 min — you're cutting losers faster than winners, which is the right shape. Current streak: -2 (last two NQ trades lost). ES contributed +$1,610; NQ -$190."
- Per-fill slippage: "ES buy at 7102.50 was 2.5 ticks adverse vs arrival mid (7101.875), 0.4 pts above 60s VWAP (7102.14). Fills on the ask are normal in a rising tape — not a venue issue."
- Pattern callout: "Your losing trades cluster 18:00–20:00 UTC (14–16 ET) — the post-lunch drift window. 0 wins, 3 losses there today."
Disambiguation
- vs
trade-debrief: journal reports descriptive TCA facts. Debrief proposes prescriptive coaching prompts and rule mining. - vs
trade-replay: journal summarizes many trades. Replay goes deep on one trade, with MFE/MAE and counterfactual exits. - vs
position-watchdog: journal is retrospective, on closed trades. Watchdog is live, on open positions. - vs MCP
performance_summary: it pre-aggregates P&L, win rate, and profit factor across an account's history, with different bucketing. Prefer it for multi-month views. Usestreaks.pyfor same-day or same-week granularity, with an hour-of-day breakdown.
Explicit non-goals
- No judgment. Journal reports facts. Whether a pattern is good
or bad belongs in
trade-debrieforrisk-coach. - No slippage batch runs. Per-fill only, per user ask.
- No multi-leg strategy attribution. Brackets / OCOs appear as
independent open-close pairs. See
timeline_reportfor strategy- level aggregation. - No forecasting. Hold-time distributions are historical only.
Resource layout
scripts/streaks.py— FIFO round-trip pairing + overall/by-symbol/ by-hour stats fromfill_history. Input via--fileor stdin.scripts/slippage.py— per-fill slippage vs arrival mid + interval VWAP. Input via--fileor stdin.scripts/fixtures/— synthetic samples:fills_day_synthetic.json— 8 fills across ES + NQ for streaksslippage_fill_synthetic.json— 1 fill + 4 bars for slippage
references/tca-definitions.md— slippage sign convention, round- trip pairing rules, P&L math, streak semantics, known gaps. Load it when you narrate a slippage sign or a round-trip pairing.