Understanding OrderUpdate event

i’m trying to understand the orders logic so i’ve developped a fast addons that contains a simple TextBlock and OrderUpdate event is catched i get the order infos and print they in the texbox

Blockquote
private void onOrderUpdate (object sender, OrderEventArgs e)
{
if (_block != null)
{
_block.Dispatcher.InvokeAsync(() =>
{
_block.AppendText(“\n” + e.Order.ToString());
});
}
}

but sometime i’ve an “combined event” that seems the same event repeated and i cant understand the reason. This is the output of one sell limit order. The event is called 4 times but the first 2 times the the orderState is the same. It’s occurs also when i delete an order but seem to be random

orderId=‘e17cf215792349398498c0f7c07e1404’ account=‘Sim101’ name=‘’ orderState=Submitted instrument=‘NQ 06-25’ orderAction=Sell orderType=‘Limit’ limitPrice=5391.75 stopPrice=0 quantity=1 tif=Day oco=‘’ filled=0 averageFillPrice=0 onBehalfOf=‘’ id=15 time=‘2025-06-10 17:24:06’ gtd=‘2099-12-01’ statementDate=‘2025-06-10’
orderId=‘e17cf215792349398498c0f7c07e1404’ account=‘Sim101’ name=‘’ orderState=Submitted instrument=‘NQ 06-25’ orderAction=Sell orderType=‘Limit’ limitPrice=5391.75 stopPrice=0 quantity=1 tif=Day oco=‘’ filled=0 averageFillPrice=0 onBehalfOf=‘’ id=15 time=‘2025-06-10 17:24:06’ gtd=‘2099-12-01’ statementDate=‘2025-06-10’
orderId=‘e17cf215792349398498c0f7c07e1404’ account=‘Sim101’ name=‘’ orderState=Working instrument=‘NQ 06-25’ orderAction=Sell orderType=‘Limit’ limitPrice=5391.75 stopPrice=0 quantity=1 tif=Day oco=‘’ filled=0 averageFillPrice=0 onBehalfOf=‘’ id=15 time=‘2025-06-10 17:24:06’ gtd=‘2099-12-01’ statementDate=‘2025-06-10’
orderId=‘e17cf215792349398498c0f7c07e1404’ account=‘Sim101’ name=‘’ orderState=Working instrument=‘NQ 06-25’ orderAction=Sell orderType=‘Limit’ limitPrice=5391.75 stopPrice=0 quantity=1 tif=Day oco=‘’ filled=0 averageFillPrice=0 onBehalfOf=‘’ id=15 time=‘2025-06-10 17:24:06’ gtd=‘2099-12-01’ statementDate=‘2025-06-10’

i hope that this forum works a quarter of the old one…

thanks !

I think its event driven. So multiple things might trigger it. You can just put a break point in and see if you can see the stack trace for it.

asking to IA seems that this issue is a critical point of ninjatrader. I think that this can not be true but one solution should be using “a logic” to filter duplicate events checking the new orderstate with the last received for the same orderID but really i’m not thinking that this should be a real solution.

i’ve watched the code of the 2 tradecopier in ninjaecosystem and also uses OrderUpdate but i cant see any kind of filter

There are no “duplicate” events. It’s doing its job. Events are not supposed to hold some kind of state. It’s just a notification. It’s up to you to implement whatever filter you need when you get the notification. For example, if you need to keep track of the orders for your purpose then you need to implement that yourself. Once you get a notification from the function.

I am not a programmer but could it be because Order object is called multiple times for the same order? Just look how many order states there can be:

OrderState Values

OrderState.Initialized Order is initialized in NinjaTrader
OrderState.Submitted Order is submitted to the broker
OrderState.Accepted Order is accepted by the broker or exchange
OrderState.TriggerPending Order is pending submission
OrderState.Working Order is working in the exchange queue
OrderState.ChangePending Order change is pending in NinjaTrader
OrderState.ChangeSubmitted Order change is submitted to the broker
OrderState.CancelPending Order cancellation is pending in NinjaTrader
OrderState.CancelSubmitted Order cancellation is submitted to the broker
OrderState.Cancelled Order cancellation is confirmed by the exchange
OrderState.Rejected Order is rejected
OrderState.PartFilled Order is partially filled
OrderState.Filled Order is completely filled
OrderState.Unknown An unknown order state. Default if broker does not report current order state.

So, if you wanted to monitor for when order was submitted you’d
_block.AppendText(“/”+e.OrderState.Submitted.ToString())

ok but read my first post. Why OrderUpdate should be called more time if the orderstate remains the same ? If you read the first 2 lines you can see the same orderId, same sender, same orderstatus= submitted. I’m setting only 1 order 1 time but ther orderupdate event is called 2 times

yes this is true but i get 2 or more calls with the same orderstate

It’s an event driven design. You listen to it and do whatever you want when it triggers. Event-driven architecture style - Azure Architecture Center | Microsoft Learn

Why would the same event occur twice? Two Submitted and two Working in OP’s example.

It doesn’t matter if it occurred a million times. It’s just a notification. The user is responsible for handling the notification. Something just fires off a notification to it and you as the developer will have to do something when you get notified.

1 Like

Thanks, I understand now, as per your link:
" Basic event correlation: A consumer processes a few discrete business events, correlates them by some identifier, and persists information from earlier events for use when processing later events"

Would you say that TradeCopierFree is handling order events incorrectly, given that it’s not tracking events by ID?

private void OnOrderUpdate(object sender, OrderEventArgs e)
    	{
			bool isDebugOk					= DEBUG && (e.OrderState == OrderState.Submitted || e.OrderState == OrderState.Filled || e.OrderState == OrderState.PartFilled);
			if(isDebugOk && IsCopyAllowed)	Print(GetPreFix+" OnOrderUpdate:  Acct: "+e.Order.Account+" \t Inst: "+e.Order.Instrument.FullName+"  \t Qty: "+e.Quantity+" \t Action: "+e.Order.OrderAction+" \t Type: "+e.Order.OrderTypeString+"  \t State: "+e.Order.OrderState);//+"  \t Name: "+e.Order.Name);
//			if(DEBUG) Print(orderPreName+" OnOrderUpdate:  MasterAccount: "+MasterAccount+" \t MasterInstr: "+MasterInstr+" \t Test #!: "+(e.Order.Account != MasterAccount)+" \t Test #2 = "+(e.Order.Instrument != MasterInstr) );
			
			if (IsNotMasterTest(e.Order.Account, e.Order.Instrument) )
			{
//				if(isDebugOk && IsCopyAllowed) Print(GetPreFix+"     - RETURN -   Acct: "+e.Order.Account+" \t  Inst: "+e.Order.Instrument.FullName+"  \t Qty: "+e.Quantity+" \t Action: "+e.Order.OrderAction+" \t Type: "+e.Order.OrderTypeString+"  \t State: "+e.Order.OrderState+"  \t  Name: "+e.Order.Name);
				return;
			}
			
			bool isSubmittedNotCancelled	= e.OrderState == OrderState.Submitted && e.OrderState != OrderState.CancelSubmitted;
			bool isLimitOrStop				= e.Order.IsLimit || e.Order.IsStopMarket || e.Order.IsStopLimit;
			bool isFilled					= e.OrderState == OrderState.Filled || e.OrderState == OrderState.PartFilled;

			if((e.Order.IsMarket && isSubmittedNotCancelled) || (isLimitOrStop && isFilled) )
			{
				if(isDebugOk)
				{
//					if(e.Order.IsMarket)
//						Print(GetPreFix+" \t\t\t\t\t Copying Master Position.  "+e.Quantity+" "+e.Order.OrderAction+" Market order  "+e.OrderState+".");
//					else
						Print(GetPreFix+" \t\t\t\t\t Copying Master Position.  "+e.Quantity+" "+e.Order.OrderAction+" "+e.Order.OrderTypeString+" order "+e.OrderState+".");
				}
				
				if (IsAcct1Trading)		sendOrder(Acct1Instrument, Acct1, e.Order.OrderAction, e.Order.OrderEntry, GetRatioQuantity(e.Quantity, Ratio1), (orderPreName+e.Order.Name));
				if (IsAcct2Trading)		sendOrder(Acct2Instrument, Acct2, e.Order.OrderAction, e.Order.OrderEntry, GetRatioQuantity(e.Quantity, Ratio2), (orderPreName+e.Order.Name));
				if (IsAcct3Trading)		sendOrder(Acct3Instrument, Acct3, e.Order.OrderAction, e.Order.OrderEntry, GetRatioQuantity(e.Quantity, Ratio3), (orderPreName+e.Order.Name));
				if (IsAcct4Trading)		sendOrder(Acct4Instrument, Acct4, e.Order.OrderAction, e.Order.OrderEntry, GetRatioQuantity(e.Quantity, Ratio4), (orderPreName+e.Order.Name));
//				if (IsAcct5Trading)		sendOrder(Acct5Instrument, Acct5, e.Order.OrderAction, OrderType.Market, e.Order.OrderEntry, GetRatioQuantity(e.Order.Quantity, Ratio5), (orderPreName+e.Order.Name));
			}

This is a common design pattern so threads don’t get blocked (waiting for something to complete). Threads will just sit around using the resources while not doing anything if its just waiting for a task to finish.

I don’t know enough about TradeCopierFree. In that function, it appears that it has logic to check whatever it needs. The point is that whenever function gets notified you do something with it even if you are seeing the same data. For example, just don’t do anything if the data is the same and only do something if its different.

Thanks! Looking at the TradeCopierFree code again, multiple events of the same kind would not affect the flags (isFilled, isLimitOrStop, isSubmittedNotCancelled).

Who is your data provider?

I’m just curious, I don’t seem to see this happening
on Sim101 with live data from Apex/Rithmic.

i’m using apex tradovate and tradeify tradovate

@BillMilton i’ve looked that code and it ignore all the other status (they dipended by broker/dataprovider) so the duplicate events seems to not modify the logic. Is an idea… my alternative idea was do a dictionary or an hashset of all the order events and check by orderId and orderState the duplicates but the tradeCopierFree solution seems to be better.
I will think about it tomorrow, it’s 12 pm here