I am having an issue where my OnOrderUpdate is giving me my order fill before it is actually should be filled. In this example for NQ, the current price is 20006.75, I set limit to 19983.25, it immediate gives an OnOrderUpdate with state 'Filled'. Then more than 10 minutes later, when it hits the limit price OnExecutionUpdate triggers when it should giving me the state filled.
This seems like the backtest is filling future entries? This causes issues as I have order logic in the OnOrderUpdate that only happens when state is not filled, i.e. setting entryOrder to be the order, then in the execution update I will need to set my stop/profit orders.
I followed the references in https://ninjatrader.com/support/help...rderupdate.htm section "Properly assigning order object values and https://ninjatrader.com/support/help...rderupdate.htm "OnExecutionUpdate Example".
I tried both Managed and Unmanged orders, and they give the same results. I have put in this a screenshot of the log showing the above scenario, and snippets of my code. In the code snippets, I have created a wrapper class around my order, I just then call the OnOrderUpdate/OnExecutionUpdate from the actual strategy methods.
Thank you.
Log:
2024-10-02 10:45:00 AM Confirmed Long at 10:40:00 Current=20006.75 Entry=19983.2625 TP=20171.5 SL=19832.5 2024-10-02 10:45:00 AM Strategy 'DRStrategy/340075344': Entered internal SubmitOrderUnmanaged() method at 2024-10-02 10:45:00 AM: BarsInProgress=0 Action=Buy OrderType=Limit Quantity=1 LimitPrice=19983.25 StopPrice=0 SignalName='entry_a65c9621-c5c0-40f4-86eb-2d90167542fb' 2024-10-02 10:45:00 AM Order Update: OrderName: entry_a65c9621-c5c0-40f4-86eb-2d90167542fb State:Filled filed 1 Strategy position: 1 2024-10-02 10:56:00 AM Execution Update: OrderName: entry_a65c9621-c5c0-40f4-86eb-2d90167542fb State:Filled
strategy.SubmitOrderUnmanaged(0, this.action, OrderType.Limit, this.qty, this.limitPrice, 0, this.oco, this.ENTRY_ID);
public void OnOrderUpdate(Order order, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, OrderState orderState, DateTime time, ErrorCode error, string comment)
{
strategy.Print(time.ToString() + " Order Update: OrderName: " + order.Name + " State:" + order.OrderState.ToString() + " filed " + filled.ToString() + " Strategy position: " + strategy.Position.Quantity);
// One time only, as we transition from historical
// Convert any old historical order object references to the live order submitted to the real-time account
if (strategy.State == State.Realtime) {
if ((entryOrder != null) && (entryOrder.IsBacktestOrder))
{
entryOrder = strategy.GetRealtimeOrder(entryOrder);
}
if ((stopOrder != null) && (stopOrder.IsBacktestOrder))
{
stopOrder = strategy.GetRealtimeOrder(stopOrder);
}
if ((targetOrder != null) && (targetOrder.IsBacktestOrder))
{
targetOrder = strategy.GetRealtimeOrder(targetOrder);
}
}
// Assign entryOrder in OnOrderUpdate() to ensure the assignment occurs when expected.
// This is more reliable than assigning Order objects in OnBarUpdate, as the assignment is not gauranteed to be complete if it is referenced immediately after submitting
if (orderState != OrderState.Filled) {
if (order.Name == ENTRY_ID)
{
entryOrder = order;
}
else if (order.Name == SL_ID)
{
stopOrder = order;
}
else if (order.Name == TP_ID)
{
targetOrder = order;
}
}
// Null Entry order if filled or cancelled. We do not use the Order objects after the order is filled,
// so we can null it here
if ((entryOrder != null) && (entryOrder == order))
{
// Check if entryOrder is cancelled.
if ((order.OrderState == OrderState.Cancelled) && (order.Filled == 0))
entryOrder = null;
if (order.OrderState == OrderState.Filled)
entryOrder = null;
}
if ((stopOrder != null) && (stopOrder == order))
{
// Check if entryOrder is cancelled.
if ((order.OrderState == OrderState.Cancelled) && (order.Filled == 0))
stopOrder = null;
if (order.OrderState == OrderState.Filled)
stopOrder = null;
}
if ((targetOrder != null) && (targetOrder == order))
{
// Check if entryOrder is cancelled.
if ((order.OrderState == OrderState.Cancelled) && (order.Filled == 0))
targetOrder = null;
if (order.OrderState == OrderState.Filled)
targetOrder = null;
}
}
public void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time)
{
strategy.Print(time.ToString() + " Execution Update: OrderName: " + execution.Order.Name + " State:" + execution.Order.OrderState.ToString());
/* We advise monitoring OnExecution to trigger submission of stop/target orders instead of OnOrderUpdate() since OnExecution() is called after OnOrderUpdate()
which ensures your strategy has received the execution which is used for internal signal tracking. */
if (entryOrder != null && entryOrder == execution.Order)
{
if (execution.Order.OrderState == OrderState.Filled || execution.Order.OrderState == OrderState.PartFilled || (execution.Order.OrderState == OrderState.Cancelled && execution.Order.Filled > 0))
{
// We sum the quantities of each execution making up the entry order
sumFilled += execution.Quantity;
// Submit exit orders for partial fills
if ((execution.Order.OrderState == OrderState.PartFilled) || ((execution.Order.OrderState == OrderState.Filled) && (sumFilled == execution.Order.Filled)))
{
if (action == OrderAction.Buy) {
strategy.Print("Setting stop and tp");
//stopOrder = strategy.ExitLongStopMarket(0, true, execution.Order.Filled, slPrice, SL_ID, ENTRY_ID);
//targetOrder = strategy.ExitLongLimit(0, true, execution.Order.Filled, tpPrice, TP_ID, ENTRY_ID);
strategy.SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.Limit, execution.Order.Filled, tpPrice, 0, BRACKET_OCO, TP_ID);
strategy.SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.StopMarket, execution.Order.Filled, 0, slPrice, BRACKET_OCO, SL_ID);
} else {
//stopOrder = strategy.ExitShortStopMarket(0, true, execution.Order.Filled, slPrice, SL_ID, ENTRY_ID);
//targetOrder = strategy.ExitShortLimit(0, true, execution.Order.Filled, tpPrice, TP_ID, ENTRY_ID);
strategy.SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, execution.Order.Filled, tpPrice, 0, BRACKET_OCO, TP_ID);
strategy.SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.StopMarket, execution.Order.Filled, 0, slPrice, BRACKET_OCO, SL_ID);
}
}
// Resets the entryOrder object and the sumFilled counter to null / 0 after the order has been filled
if (execution.Order.OrderState != OrderState.PartFilled && sumFilled == execution.Order.Filled)
{
entryOrder = null;
}
}
}
// Reset our stop order and target orders' Order objects after our position is closed. (1st Entry)
if ((stopOrder != null && stopOrder == execution.Order) || (targetOrder != null && targetOrder == execution.Order))
{
if (execution.Order.OrderState == OrderState.Filled || execution.Order.OrderState == OrderState.PartFilled)
{
stopOrder = null;
targetOrder = null;
}
}
}

Comment