If you are building custom trade logs, stats panels, or account synchronizers in NinjaTrader 8, you have likely run into this issue: you wait for a trade to close, query SystemPerformance.AllTrades, and get an empty list or find the last trade is missing.
Relying on SystemPerformance inside execution events triggers a classic race condition. The trade collection is updated asynchronously, meaning the data isn’t there yet at the exact millisecond the order fills.
Here is why querying SystemPerformance fails in real-time events, and how to reliably detect trade closures in C# using state events.
The Race Condition: Why AllTrades is Stale
When an order is filled, NinjaTrader 8 fires execution events like OnOrderUpdate and OnExecutionUpdate.
However, the internal collections in SystemPerformance.AllTrades are not populated in the same synchronous execution path. The platform updates performance stats on a separate thread or event cycle shortly after the fill occurs.
This delay is only a few milliseconds, but to a running program executing code at the exact moment of the fill, those milliseconds are an eternity.
As Reddit developer u/jrolla238 observed during a debugging session:
“I’ve run into similar timing issues around NinjaTrader execution/performance data. My instinct would be to avoid relying on SystemPerformance.AllTrades as the source of truth at the exact millisecond of execution.”
If you try to read AllTrades[AllTrades.Count - 1] inside your execution event handler, you will fetch the previous closed trade instead of the one that just closed.
What We Tried That Failed: Thread Sleeps and Delays
When first encountering this timing issue, many developers try to work around it by pausing the execution thread:
// DO NOT DO THIS
System.Threading.Thread.Sleep(50);
var lastTrade = SystemPerformance.AllTrades[SystemPerformance.AllTrades.Count - 1];
This is a dangerous anti-pattern. Pausing the primary NinjaTrader execution thread freezes the entire client interface, stalls data feeds, and introduces slippage on subsequent orders.
Even worse, on a heavily loaded system, a 50ms delay might still not be enough for the async statistics collection to update, leading to intermittent bugs that only show up during high-volatility events.
How to Solve It: State-Based Detection
To reliably detect when a trade closes in real-time, you must track position changes and order states directly rather than checking performance history.
Method 1: Tracking OrderState in OnOrderUpdate
The most direct way to catch a fill is checking order state changes in OnOrderUpdate.
As community developer u/EveryLengthiness183 points out:
“You are looking for OnOrderUpdate. The condition you need to check is: if (orderState == OrderState.Filled) This will give you the first possible notification of a fill.”
By checking when your exit orders switch to OrderState.Filled and comparing this with the current position size, you can instantly flag a closed trade.
Method 2: Handling OnExecutionUpdate
For the highest precision, monitor OnExecutionUpdate. This method fires for every execution (entry, partial fill, or exit) and provides a direct reference to the Execution object containing the execution price, quantity, and instrument.
Here is a clean implementation pattern to track trade closures safely:
protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, DateTime time)
{
// Ensure we are only checking execution updates for our strategy
if (execution == null || execution.Order == null)
return;
// Check if the execution has closed or reduced our position to flat
if (Position.MarketPosition == MarketPosition.Flat)
{
// We just went flat, meaning the last execution closed the trade
decimal realizedPnl = PriceToCurrency(Position.GetUnrealizedProfitLoss(PerformanceUnit.Currency, price));
LogTradeClosure(execution.Order.Name, price, quantity, realizedPnl, time);
}
}
private void LogTradeClosure(string orderName, double exitPrice, int qty, decimal pnl, DateTime exitTime)
{
// Process your trade statistics here
Print(string.Format("Trade Closed: {0} | Exit Price: {1} | Qty: {2} | Realized PNL: {3} | Time: {4}",
orderName, exitPrice, qty, pnl, exitTime));
}
The Trade-Offs: Performance vs. Simplicity
While state-based detection is highly responsive, it requires you to maintain your own internal tracking state (like average entry price, total position size, and running PnL) if you want to calculate trade-specific statistics.
| Method | Latency | Complexity | Use Case |
|---|---|---|---|
| SystemPerformance.AllTrades | High (Async Lag) | Low | Post-trade analysis, historical journaling |
| OnExecutionUpdate | Zero | Medium | Real-time dashboards, copier sync |
| OnPositionUpdate | Zero | Low | General position tracking, margin checks |
If you only need historical metrics for daily reviews or journal exports, SystemPerformance.AllTrades is fine to query during OnStateChange when transitioning to State.Terminated.
But for real-time risk checks, you must stick to execution-event state monitoring.
Pre-Built Solutions for Manual Execution
Building a reliable execution pipeline that manages entry/exit states, tracks average prices across multiple accounts, and prevents timing bugs requires writing hundreds of lines of boilerplate C# code.
If you are a manual trader looking to manage executions across prop firm accounts without writing custom state-tracking scripts, using a dedicated execution tool is often a better option.
For instance, the Nexus Chart Trader replaces the default NinjaTrader panel with a optimized interface. It handles execution feedback, risk-limiting brackets, and position tracking across multiple funded desks without relying on delayed historical data, keeping your manual execution synchronized and slippage-free.
Frequently Asked Questions
Why does Position.MarketPosition show Flat before OnExecutionUpdate finishes?
NinjaTrader updates the core Position object immediately when a fill is processed, before firing the execution event hooks. This is why checking Position.MarketPosition == MarketPosition.Flat inside OnExecutionUpdate works perfectly for detecting trade closure.
How do I access historical trades safely?
If you need to query historical trades, do so outside the execution loop. You can query SystemPerformance.AllTrades in a custom Grid or background window on a timer, or during the strategy’s cleanup phase.
Does this apply to automated order copying?
Yes. Copying systems that listen to execution events must use the execution object data directly rather than inspecting performance statistics to ensure instant sync.