Journal and Diagnostics
These tools let an AI trading agent answer the question "what just happened?" by reading three different timelines:
- The user's matched-trade journal (NT8 fills, post-edit).
- The webhook signal history (TradingView and other alert sources).
- The CrossTrade XT Add-On Activity Log (in-process events).
All three are read-only and Elite-gated like the rest of the MCP surface.
Tools at a Glance
| Tool | Reads | Source of truth |
|---|---|---|
GetJournalTrades | Matched trades (entry + exit) with computed P/L, MFE/MAE, efficiency. | nt8_trades (NT8-sourced) + manual_trades (user-entered), with trade_overrides applied. |
GetSignalHistory | Webhook signals: command, account, symbol, ack/error, source IP. | API ingest log. |
GetActivityLog | XT Add-On in-process Info/Warning/Hidden events. | Add-on local SQLite logs table. |
GetJournalTrades
Paginated read of the materialized journal. This is the same trade list the user sees on /user/journal after their edits and manual entries are applied.
Why call this
An AI trading agent that wants to discuss outcomes ("how did MES do this morning?", "did my reversal strategy hit its target?") needs the corrected trade view, not the raw executions. GetJournalTrades returns:
- NT8-sourced trades from
nt8_trades(FIFO-matched executions, with per-trade P/L, MFE/MAE, efficiency). - User overrides applied per column. Editing a single field (commission, exit price) replaces only that field; everything else stays from the original NT8 record.
- Soft-deleted trades excluded by default.
- Manual trades the user typed in by hand (
manual_trades), merged into the same timeline byexit_time.
Arguments
| Name | Type | Default | Notes |
|---|---|---|---|
sync_first | boolean | false | When true, drain the add-on's local SQLite into the journal before reading. No-op if the add-on is offline; the read still runs against existing data. |
account | string | (all) | Filter to one account. Matches the exact name or the prop-firm suffix form (e.g. BX101476-01 also picks up BX101476-01!Bulenox!Bulenox). |
start | string (ISO 8601) | (none) | Lower bound on exit_time. Date-only (2026-04-17) or full datetime; naive inputs are treated as UTC. |
end | string (ISO 8601) | (none) | Upper bound on exit_time. Same parsing rules. |
limit | integer | 100 | Page size. Range 1 to 1000. |
offset | integer | 0 | Pagination offset into the combined (NT8 + manual) timeline. |
direction | enum | desc | desc returns newest exits first. asc returns oldest first. |
include_flagged | boolean | true | Include trades NT8 could not match cleanly (parse errors the user has not reviewed). Defaults to true so the agent can see and explain them. |
include_deleted | boolean | false | Include trades the user soft-deleted via override or by removing a manual entry. |
include_manual | boolean | true | Include user-entered manual trades from manual_trades. |
Response
{
"success": true,
"trades": [ /* see per-trade shape below */ ],
"count": 42,
"total": 1380,
"offset": 0,
"limit": 100,
"direction": "desc",
"last_sync": "2026-05-15T13:42:11Z",
"sync": { /* present only when sync_first:true was sent */ }
}
countis the size oftradeson this page.totalis the size of the merged result set before paging.last_syncis the user'sjournal_last_syncsetting. Useful for deciding whether to setsync_first:trueon a subsequent call.syncis the raw return ofjournal.syncwhensync_first:truewas passed. On a connected add-on it looks like{"executions": N, "orders": N, "balances": N, "instruments": N, "trades": N}. On a disconnected add-on it looks like{"error": "XT Add-On is not connected"}. The trade read still runs in either case.
Per-trade shape
NT8-sourced trades carry the full performance metric set:
{
"tradeHash": "8c9b…",
"account": "Sim101",
"instrument": "MES 06-26",
"instrumentRoot": "MES",
"side": "long",
"quantity": 2,
"entryPrice": 5314.25,
"exitPrice": 5318.00,
"entryTime": "2026-05-15T13:42:11+00:00",
"exitTime": "2026-05-15T13:55:04+00:00",
"profitCurrency": 37.50,
"profitPoints": 3.75,
"profitTicks": 15.0,
"profitPercent": 0.0705,
"mfeCurrency": 50.00,
"maeCurrency": -12.50,
"mfePoints": 5.0,
"maePoints": -1.25,
"mfeTicks": 20.0,
"maeTicks": -5.0,
"entryEfficiency": 0.85,
"exitEfficiency": 0.75,
"totalEfficiency": 0.80,
"commission": 1.84,
"barsInTrade": 13,
"timeInMarketSeconds": 773,
"entryName": "EntryLong",
"exitName": "ProfitTarget",
"isFlagged": false,
"flagReason": null,
"isManual": false,
"isEdited": false,
"isDeleted": false
}
Manual trades carry a slimmer set (entry/exit prices and times, profit, commission). Their tradeHash is the manual trade_id. isManual is true.
Sync behavior
sync_first:true runs the same drain the user gets from the Sync button on /user/journal:
- Read
SysInfofrom the add-on to identify the NT8 install. - Query the add-on's local SQLite for new executions, orders, and balances.
- Pull new instrument specs.
- Rebuild matched trades from the new executions using FIFO.
It can take several seconds for users with many accounts or a full month of fills. If last_sync from a previous read is recent and the user is only asking about closed trades from yesterday, skip the sync and use the cached data.
Suggested patterns
# Quick read, no sync.
GetJournalTrades(limit: 50)
# Today only, one account, freshest possible.
GetJournalTrades(
sync_first: true,
account: "Sim101",
start: "2026-05-15",
limit: 200
)
# All-time count + first page for an overview, newest first.
GetJournalTrades(limit: 25, direction: "desc")
# Only trades the agent can explain (no flagged, no deleted).
GetJournalTrades(include_flagged: false, include_deleted: false)
When the user has overridden a column on an NT8 trade, the response returns the override value, not the original. isEdited:true flags this. The unedited NT8 fields are not exposed through this tool; if an agent needs them for forensic comparison, read nt8_trades directly through admin tooling.
GetSignalHistory
Reads the webhook Signal History (a.k.a. Alert History on the user dashboard). Each row is one webhook hit: TradingView alert, custom POST, or any other configured source.
Why call this
When the user asks "did my alert fire?" or "why didn't my order go through?", GetSignalHistory returns the same rows that power the dashboard, plus a built-in diagnostic when the signal failed for a known reason.
Arguments
| Name | Type | Default | Notes |
|---|---|---|---|
offset | integer | 0 | Pagination offset. |
limit | integer | 50 | Page size. Capped at 200. |
status | enum | * | * returns all rows. success, warning, failed narrow to one outcome bucket. |
search | string | (none) | Substring match across signal id, multi-account, request body, and error text. |
direction | enum | desc | desc returns newest signals first. |
include_help | boolean | true | Attach a full help block (title, causes, fixes) when the signal's error or warning matches a known pattern. Set false to skip the help payload and only get a help_key reference. |
Response
{
"success": true,
"signals": [
{
"id": "sig_…",
"time": "2026-05-15T13:55:04Z",
"epoch": 1747318504,
"command": "place",
"account": "Sim101",
"symbol": "MES",
"status": "warning",
"valid": true,
"ack": true,
"error": null,
"warning": "instrument_rolled",
"exectime_ms": 412,
"multiacct": null,
"source": "192.0.2.10",
"help_key": "instrument_rolled",
"help": {
"title": "Instrument rolled to next contract",
"causes": [ "Front-month contract expired between alert authoring and fire time." ],
"fixes": [ "Use a continuous-contract root (e.g. MES) in your alert and let CrossTrade resolve the active contract.", "Update the hard-coded contract in the alert payload." ]
}
}
],
"count": 1,
"offset": 0,
"limit": 50
}
help_keyis set whenever the row matched a known failure pattern. The correspondinghelpblock is only attached wheninclude_help:true.multiacctis the comma-separated account list when the original signal targeted multiple accounts, otherwisenull.
Suggested patterns
# Why didn't anything fire in the last 30 minutes?
GetSignalHistory(status: "failed", limit: 25)
# Find the exact signal the user is talking about.
GetSignalHistory(search: "MNQ", limit: 10)
# Skip the help payload when paginating many pages.
GetSignalHistory(include_help: false, limit: 200, offset: 200)
GetActivityLog
Reads the CrossTrade XT Add-On Activity Log straight from the add-on's local SQLite logs table. Useful for time-correlating signal failures with what the add-on saw locally (connection drops, sync events, bracket placements, kill-switch trips).
Why call this
The Activity Log is the add-on's own narrative of what happened on the user's machine. It captures things that never reach the API server: NT8 disconnects, local order acknowledgements, instrument rolls, internal kill-switch trips, and add-on-side warnings that explain why an API call might have looked like it succeeded but produced no trade.
Arguments
| Name | Type | Default | Notes |
|---|---|---|---|
limit | integer | 100 | Max log rows. Range 1 to 500. |
lookback_hours | integer | 24 | How far back to look. Range 1 to 168 (one week). |
levels | array | ["Info","Warning","Hidden"] | Subset of levels to return. The server-side allowlist is exactly these three. |
Level visibility
The user-visible levels are Info, Warning, and Hidden. Error and Debug are admin-only and are silently dropped if requested. This matches the visibility split inside the add-on: end users see normal operational events and surfaceable warnings, but raw exception logs are reserved for support diagnostics.
When the agent passes levels:["Error"], the response is an empty logs array with a detail string explaining the allowlist.
Response
{
"success": true,
"logs": [
{
"timestamp": "2026-05-15 13:55:04.812",
"epoch": 1747318504,
"level": "Info",
"message": "[MES 06-26] Bracket placed (target 5318.00 / stop 5311.50) for Sim101"
}
],
"count": 1,
"lookback_hours": 24,
"levels": ["Info", "Warning", "Hidden"]
}
Add-on required
This tool requires the user's NT8 to be running and connected to the CrossTrade add-on. When the add-on is offline:
{ "success": false, "error": "addon_not_connected",
"detail": "Start NinjaTrader with the CrossTrade add-on." }
When the add-on is reachable but the query fails or times out (30s):
{ "success": false, "error": "addon_query_failed", "detail": "…" }
{ "success": false, "error": "timeout", "detail": "Add-on did not respond within 30 seconds." }
Suggested patterns
# What's been happening in the last hour?
GetActivityLog(limit: 200, lookback_hours: 1)
# Just the warnings from the last day.
GetActivityLog(levels: ["Warning"], lookback_hours: 24)
# Wide window for a post-mortem.
GetActivityLog(limit: 500, lookback_hours: 168)
Correlating Across Tools
A common forensic pattern for an AI trading agent:
GetSignalHistory(status: "failed", limit: 25) // which signals failed?
GetActivityLog(lookback_hours: 6, limit: 200) // what did the add-on see?
GetJournalTrades(sync_first: true, start: "<earliest signal time>")
The signal history gives the agent the user-facing event timeline. The activity log gives the add-on's view of the same window. The journal tells the agent which signals actually produced trades. Crossing all three lets the agent explain the gap when a signal landed but no trade appeared (e.g. signal arrived, add-on rejected for an open opposing position, no trade reached the journal).
None of these three tools mutate state. They are safe to call repeatedly during a diagnostic conversation. Treat GetJournalTrades(sync_first:true) as the one that does real work: it talks to the add-on and writes to the journal tables. The other two are pure reads.