Announcement

Collapse
No announcement yet.

Partner 728x90

Collapse

Exiting prematurely

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Exiting prematurely

    Hello,

    I have a strategy that I am backtesting in Market Replay with tick data. The entries fill just fine and sometimes take partial fills at different prices due to bid-ask variability with market orders. The entry is given a unique name; however, when that name is referenced in the take profit logic, the ninjatrader engine thinks the first partial fill is the entire order and will cancel the remaining exit orders once the first order is filled. I will provide an example.

    A signal is created to short at market for $MNQ. The algorithm places a short order for 4 contracts. 3 contracts are filled at 22,061.50 and 1 contract is filled at 22,061.25. Both fills are given the same name ShortSFP_1305. There are 4 take profit levels arbitrarily set to values below entry price (for short position). Once the first 3 take profit levels are executed, the last take profit order is automatically canceled. I believe this is because the order engine is processing the 3 contracts filled at 22,061.50 as the entire position. I think it's doing this because both fill values have the name "ShortSFP_1305". I don't know how to fix this problem. If anybody is able to provide a clear answer, I would be very appreciative.

    Code:
    //Short entry logic if (downSignal && Position.MarketPosition != MarketPosition.Long)
      {[INDENT]string shortSignalName = "ShortSFP_" + CurrentBar;[/INDENT][INDENT]
    // Submit a Short entry with a unique name
    EnterShort(0, ContractsPerEntry, shortSignalName);
    
    // Print($"[Bar {CurrentBar}] Entered SHORT: {shortSignalName} with stop at {sfpDownHigh}");
    // Take Profits are handled in OnExecutionUpdate[/INDENT]
      }
     
            protected override void OnExecutionUpdate(Execution execution, string executionId, double price,
                                                      int quantity, MarketPosition marketPosition,
                                                      string orderId, DateTime time)
            {
                
                if (execution.Order == null)
                    return;
    
                // Only consider filled or partially filled states
                if (execution.Order.OrderState != OrderState.Filled &&
                    execution.Order.OrderState != OrderState.PartFilled)
                    return;
    
    
                // Check if it's a Long Entry
    
                if (execution.Order.Name.StartsWith("LongSFP_") && execution.Order.OrderAction == OrderAction.Buy)
                {
                    string fromEntrySignal = execution.Order.Name;
    
                    longEntryPrices[fromEntrySignal] = price;
    
                    int filledQty = quantity;
                    longEntryQuantities[fromEntrySignal] = filledQty;
    
                    double tp1Price = price + 50;
                    double tp2Price = price + 75;
                    double tp3Price = price + 150;
                    double tp4Price = price + 200;
    
                    // Submit partial take-profit limit orders, each with unique signalNames
                    // and referencing the same fromEntrySignal so they don't cancel each other.
    
                    ExitLongLimit(0, true, 1, tp1Price, "TP1_" + fromEntrySignal, fromEntrySignal);
                    ExitLongLimit(0, true, 1, tp2Price, "TP2_" + fromEntrySignal, fromEntrySignal);
                    ExitLongLimit(0, true, 1, tp3Price, "TP3_" + fromEntrySignal, fromEntrySignal);
                    ExitLongLimit(0, true, 1, tp4Price, "TP4_" + fromEntrySignal, fromEntrySignal);
                    
                }
    
                // Check if it's a Short Entry
    
                else if (execution.Order.Name.StartsWith("ShortSFP_") && execution.Order.OrderAction == OrderAction.SellShort)
                {
                    string fromEntrySignal = execution.Order.Name;
    
                    shortEntryPrices[fromEntrySignal] = price;
    
                    int filledQty = quantity;
                    shortEntryQuantities[fromEntrySignal] = filledQty;
    
                    double tp1Price = price - 50;
                    double tp2Price = price - 75;
                    double tp3Price = price - 150;
                    double tp4Price = price - 200;
    
                    int contractsPerLimit = filledQty / 4;
    
                    ExitShortLimit(0, true, 1, tp1Price, "TP1_" + fromEntrySignal, fromEntrySignal);
                    ExitShortLimit(0, true, 1, tp2Price, "TP2_" + fromEntrySignal, fromEntrySignal);
                    ExitShortLimit(0, true, 1, tp3Price, "TP3_" + fromEntrySignal, fromEntrySignal);
                    ExitShortLimit(0, true, 1, tp4Price, "TP4_" + fromEntrySignal, fromEntrySignal);
                }
            }
        }
    }    ​

    #2
    Have you been using TraceOrders or Print statements to check if the fromEntrySignals are matching and for what quantities? Does this happen when you take longs? Not sure if it matters, but I noticed that the Long version does not have the line "int contractsPerLimit = filledQty / 4;" Not that I see that contractsPerLimit anywhere else in that snippet you provided.
    If you could add some Print statements and /or some of the output of an example you can reproduce this error, I'm sure it would help us understand what's going on.

    Comment


      #3
      Originally posted by rockmanx00 View Post
      Have you been using TraceOrders or Print statements to check if the fromEntrySignals are matching and for what quantities? Does this happen when you take longs? Not sure if it matters, but I noticed that the Long version does not have the line "int contractsPerLimit = filledQty / 4;" Not that I see that contractsPerLimit anywhere else in that snippet you provided.
      If you could add some Print statements and /or some of the output of an example you can reproduce this error, I'm sure it would help us understand what's going on.
      Hi rockmnx00,

      Thank you for your response. I have been using TraceOrders. I have noticed it happening with longs on a different strategy (using same exit logic). You are correct in noticing the "int contractsPerLimit = filledQty / 4;". I meant to comment that out because it was messing up my trades for whatever reason. The concept was to just exit 1/4th of the position entered but I wanted to simplify things so I just made the exit quantity 1 after entering a total of 4 contracts.

      I have attached a snip of my chart in this response to show the traced orders for the first 3 take profit levels. Once this third TP is hit, the algo says, "Order Canceled".

      I hadn't used a print statement to help with this just yet as I'm not sure what exactly to put into the print statement that would help me figure out what is going wrong. I would love some help in that regard if you know what to put? I apologize in advance for the terrible formatting of what I'm about to paste in here but the output window provides the following statements:

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=SellShort OrderType=Market Quantity=4 LimitPrice=0 StopPrice=0 SignalName='ShortSFP_1305' FromEntrySignal=''

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=22011.50 StopPrice=0 SignalName='TP1_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21986.50 StopPrice=0 SignalName='TP2_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21911.50 StopPrice=0 SignalName='TP3_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21861.50 StopPrice=0 SignalName='TP4_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=22011.25 StopPrice=0 SignalName='TP1_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Amended matching order at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=22011.25 StopPrice=0 SignalName='TP1_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21986.25 StopPrice=0 SignalName='TP2_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Amended matching order at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21986.25 StopPrice=0 SignalName='TP2_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21911.25 StopPrice=0 SignalName='TP3_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Amended matching order at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21911.25 StopPrice=0 SignalName='TP3_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Entered internal SubmitOrderManaged() method at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21861.25 StopPrice=0 SignalName='TP4_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 10:00:01 AM Strategy 'SFP/349816031': Amended matching order at 1/24/2025 10:00:01 AM: BarsInProgress=0 Action=BuyToCover OrderType=Limit Quantity=1 LimitPrice=21861.25 StopPrice=0 SignalName='TP4_ShortSFP_1305' FromEntrySignal='ShortSFP_1305'

      1/24/2025 1:12:17 PM Strategy '349816031/SFP: Cancelled pending exit order, since associated position is closed, orderId='296fef8fe7d3492d8920f3ff2dd15273' account='Playback101' name='TP4_ShortSFP_1305' orderState=Working instrument='MNQ 03-25' orderAction=BuyToCover orderType='Limit' limitPrice=21861.25 stopPrice=0 quantity=1 tif=Gtc oco='' filled=0 averageFillPrice=0 onBehalfOf='' id=28821 time='2025-01-24 10:00:01' gtd='2099-12-01' statementDate='2025-01-24' zz0.b37w6oza9sczz​

      Comment


        #4
        So at 1:12:17pm, that's the time the TP3_ShortSFP+1305 is hit? it's interesting that your screenshot clearly shows that there were a total of 4 lots shorted, yet there are only 3 previously taken. So it doesn't seem like the third TP was filling 2 instead of three. Yet, where is that last contract going? When it crashes, does it leave you with 1 still open? Or, is that one mysteriously closed and you are now Flat?

        Comment


          #5
          Originally posted by rockmanx00 View Post
          So at 1:12:17pm, that's the time the TP3_ShortSFP+1305 is hit? it's interesting that your screenshot clearly shows that there were a total of 4 lots shorted, yet there are only 3 previously taken. So it doesn't seem like the third TP was filling 2 instead of three. Yet, where is that last contract going? When it crashes, does it leave you with 1 still open? Or, is that one mysteriously closed and you are now Flat?
          Yes, at 1:12:17 PM is when the TP3_ShortSFP+1305 is hit and that's also when the last TP4 order gets canceled automatically. My hypothesis is that of the 4 shorts taken, the first 3 that got filled are associated with the order so then when 3 take profits get executed, the order engine assumes the position is flat and cancels any remaining orders similar to how if a stop order was hit and you had limit orders open for take profits still. I am not sure why the engine does this or how to fix it but it's quite frustrating. One can clearly see that the fourth short position is associated with the order entry at bar 1305 on the chart but unfortunately because it's a different price value that gets filled, I think it's messing up the algorithm or simply being ignored/excluded for some reason. I searched through the forum and found something somewhat similar (I think?) but there was never any follow up after the customer service representative said to email their platform for advanced support.

          To answer your last question(s), the position is still +1 because 4 shorts get entered but only 3 limit exits are executed while the last one is canceled.

          Comment


            #6
            Hmm. I haven't tried using multiple TPs before, so I'm not exactly sure what to do here. I am wondering if it matters that your code seems to be amending your orders, or if you need to think of different logic to do something to your orders once execution.Order.OrderState == Filled.
            The code that I'm using for partial fills uses int sumFilled and adds the execution.Quantity to it. For it to be considered filled, it needs to fit the OrderState = Filled, but also have the sumFilled == execution.Order.Filled. I am also taking the average of all the prices to calculate my total risk based of my placed SL. I have targetOrders and various levels for where price would need to hit before moving my stop losses to breakeven. For partial fills I place orders down and when it reaches the Filled state, I simply recalculate based off the new average position price and change the orders. Not sure if that would help you in your situation, but something to consider.

            Comment


              #7
              Hello Kmace20,

              In your code the entry signal name is the same for all 4 orders so they are all tied to a single entry. In a partial fill scenario that will cause issues with the targets. If you are trying to scale out of a position while using the managed approach you would need to use 4 separate entries with unique signal names so that each target is only associated with a specific part of the position.

              Comment


                #8
                Originally posted by NinjaTrader_Jesse View Post
                Hello Kmace20,

                In your code the entry signal name is the same for all 4 orders so they are all tied to a single entry. In a partial fill scenario that will cause issues with the targets. If you are trying to scale out of a position while using the managed approach you would need to use 4 separate entries with unique signal names so that each target is only associated with a specific part of the position.
                Thank you for the reply. Yes, I ended up switching to this approach last night and it works. But I also feel it's likely not as efficient as just submitting one order.

                rockmanx00 I did end up trying your method of recalculating the average price of all entries but the program was still canceling my remaining order. I think it's because I couldn't figure out how to rename the entry appropriately. Jesse, would you please be able to provide any guidance on this concept? If it's just as efficient of a method then I suppose it's okay. When I use market replay, I don't notice much of a difference in execution time but I'm not sure how to prove it or how that translates to real life.

                Comment


                  #9
                  I guess that's the difference between me using only one target vs needing multiple targets. So I guess the best way would be to do separate entries.
                  Unless you want to do a static number, like 4 or 8, you could always use your initial potential entry point to estimate how many contracts you could get into and allocate the amounts accordingly. In the event that you couldn't do 4, you could even skip the 4th target.

                  Comment

                  Latest Posts

                  Collapse

                  Topics Statistics Last Post
                  Started by argusthome, 03-08-2026, 10:06 AM
                  0 responses
                  110 views
                  0 likes
                  Last Post argusthome  
                  Started by NabilKhattabi, 03-06-2026, 11:18 AM
                  0 responses
                  59 views
                  0 likes
                  Last Post NabilKhattabi  
                  Started by Deep42, 03-06-2026, 12:28 AM
                  0 responses
                  37 views
                  0 likes
                  Last Post Deep42
                  by Deep42
                   
                  Started by TheRealMorford, 03-05-2026, 06:15 PM
                  0 responses
                  41 views
                  0 likes
                  Last Post TheRealMorford  
                  Started by Mindset, 02-28-2026, 06:16 AM
                  0 responses
                  78 views
                  0 likes
                  Last Post Mindset
                  by Mindset
                   
                  Working...
                  X