Announcement

Collapse
No announcement yet.

Partner 728x90

Collapse

Move Stop When Close to Target

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

    Move Stop When Close to Target

    Hi,

    I want to be able to play a manual order with the default Ninjatrader ATM,
    then I want to be able to activate a strategy that will check if Close[0] is near the target by a certain amount of points,
    if this occurs, I want to move the stop that was created when I originally placed the order manually.

    Is this even possible with Ninjatrader?

    Basically I want something similar to chase when touch target, except I want it to trigger before the target is reached.
    I can't use the Ninjatrader ATM to do this because I always drag the target according to market conditions,
    so I need an automatic way to move the stop when close to the target I've moved my target to.

    Basically if Close[0] is within x points of Target orders, Stop order should move to y.

    I did this as a test, but even though the move stop code was triggered, it couldn't move the stop.

    Code:
    #region Using declarations
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Xml.Serialization;
    using NinjaTrader.Cbi;
    using NinjaTrader.Gui;
    using NinjaTrader.Gui.Chart;
    using NinjaTrader.Gui.SuperDom;
    using NinjaTrader.Gui.Tools;
    using NinjaTrader.Data;
    using NinjaTrader.NinjaScript;
    using NinjaTrader.Core.FloatingPoint;
    using NinjaTrader.NinjaScript.DrawingTools;
    using SharpDX;
    #endregion
    
    namespace NinjaTrader.NinjaScript.Strategies
    {
        public class MoveMyStopV004 : Strategy
        {
            private Account account;
    
            [NinjaScriptProperty]
            [Range(0, double.MaxValue)]
            [Display(Name = "Target Price", Description = "Fallback target price if no limit order found", Order = 1, GroupName = "Parameters")]
            public double TargetPrice { get; set; }
    
            [NinjaScriptProperty]
            [Range(1, int.MaxValue)]
            [Display(Name = "Stop Offset Ticks", Description = "Ticks below current price for stop", Order = 2, GroupName = "Parameters")]
            public int StopOffsetTicks { get; set; }
    
            [NinjaScriptProperty]
            [TypeConverter(typeof(AccountNameConverter))]
            [Display(Name = "Selected Account", Description = "Account to update stop orders", Order = 3, GroupName = "Parameters")]
            public string SelectedAccount { get; set; }
    
            protected override void OnStateChange()
            {
                if (State == State.SetDefaults)
                {
                    Description = "Updates stop market orders for the active position when price is within 10 points of the target price from limit orders.";
                    Name = "MoveMyStopV004";
                    Calculate = Calculate.OnPriceChange; // Real-time updates
                    IsUnmanaged = true; // For manual order control
    
                    TargetPrice = 19091.00; // Fallback if no limit order found
                    StopOffsetTicks = 5; // 1.25 points
                    SelectedAccount = "Playback101"; // Default for Market Replay
                }
                else if (State == State.Configure)
                {
                    try
                    {
                        lock (Account.All)
                        {
                            account = Account.All.FirstOrDefault(a => a.Name == SelectedAccount);
                            Print($"[DEBUG] Looking for account {SelectedAccount}: {(account != null ? "Found" : "Not found")}");
                        }
                        if (account == null)
                        {
                            Print($"[ERROR] Account {SelectedAccount} not found. Ensure it is connected.");
                        }
                    }
                    catch (Exception ex)
                    {
                        Print($"[ERROR] Failed to configure account: {ex.Message}");
                    }
                    Print($"[DEBUG] Fallback TargetPrice={TargetPrice:F2}, StopOffsetTicks={StopOffsetTicks}, SelectedAccount={SelectedAccount}");
                }
            }
    
            protected override void OnBarUpdate()
            {
                if (State != State.Realtime || !IsConnected() || account == null)
                {
                    Print($"[DEBUG] Skipping OnBarUpdate: State={State}, Connected={IsConnected()}, Account={(account == null ? "null" : account.Name)}");
                    return;
                }
    
                try
                {
                    // Check for active long position
                    Print($"[DEBUG] Position: MarketPosition={Position.MarketPosition}, Quantity={Position.Quantity}, AveragePrice={Position.AveragePrice:F2}");
                    if (Position.MarketPosition != MarketPosition.Long)
                    {
                        Print("[DEBUG] No active long position. Skipping stop order updates.");
                        return;
                    }
    
                    double currentPrice = Close[0];
                    double tickSize = Instrument.MasterInstrument.TickSize;
    
                    // Get orders for the active position
                    Order mar****rder = null;
                    Order limitOrder = null;
                    lock (account.Orders)
                    {
                        // Find the most recent filled market buy order
                        mar****rder = account.Orders
                            .Where(o => o.Instrument == Instrument &&
                                        o.OrderType == OrderType.Market &&
                                        o.OrderState == OrderState.Filled &&
                                        o.OrderAction == OrderAction.Buy)
                            .OrderByDescending(o => o.Time)
                            .FirstOrDefault();
    
                        // Find the limit sell order (working/accepted)
                        limitOrder = account.Orders
                            .FirstOrDefault(o => o.Instrument == Instrument &&
                                                o.OrderType == OrderType.Limit &&
                                                (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                                o.OrderAction == OrderAction.Sell);
                    }
    
                    // Get target price from limit order
                    double targetPrice = TargetPrice; // Fallback
                    if (limitOrder != null)
                    {
                        targetPrice = limitOrder.LimitPrice;
                    }
                    else
                    {
                        Print("[DEBUG] No working or accepted limit sell order found. Using fallback TargetPrice.");
                    }
    
                    double distance = Math.Abs(currentPrice - targetPrice);
    
                    Print($"[DEBUG] CurrentPrice={currentPrice:F2}, TargetPrice={targetPrice:F2}, Distance={distance:F2}, StopOffsetTicks*TickSize={StopOffsetTicks * tickSize:F2}");
    
                    lock (account.Orders)
                    {
                        // Log only active position-related orders
                        var activeOrders = account.Orders
                            .Where(o => o.OrderState != OrderState.Cancelled &&
                                        (o.OrderId == (mar****rder?.OrderId ?? "") ||
                                         (o.Instrument == Instrument &&
                                          o.OrderType == OrderType.StopMarket &&
                                          (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                          o.OrderAction == OrderAction.Sell) ||
                                         (o.Instrument == Instrument &&
                                          o.OrderType == OrderType.Limit &&
                                          (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                          o.OrderAction == OrderAction.Sell)))
                            .ToList();
    
                        Print($"[DEBUG] Total active orders in {SelectedAccount} (excluding cancelled): {activeOrders.Count}");
                        foreach (var order in activeOrders)
                        {
                            string fillPriceLog = order.OrderState == OrderState.Filled ? $", AverageFillPrice={order.AverageFillPrice:F2}" : "";
                            Print($"[DEBUG] Order {order.OrderId}: Instrument={order.Instrument?.FullName}, Type={order.OrderType}, State={order.OrderState}, Action={order.OrderAction}, StopPrice={order.StopPrice:F2}, LimitPrice={order.LimitPrice:F2}{fillPriceLog}, Quantity={order.Quantity}");
                        }
    
                        // Find stop order for debug output
                        var debugStopOrder = activeOrders
                            .FirstOrDefault(o => o.Instrument == Instrument &&
                                                o.OrderType == OrderType.StopMarket &&
                                                (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                                o.OrderAction == OrderAction.Sell);
    
                        Print($"[DEBUG] Stop Order: {(debugStopOrder != null ? $"ID={debugStopOrder.OrderId}, StopPrice={debugStopOrder.StopPrice:F2}" : "None")}");
                        Print($"[DEBUG] Target Order: {(limitOrder != null ? $"ID={limitOrder.OrderId}, LimitPrice={limitOrder.LimitPrice:F2}" : "None")}");
    
                        var stopOrders = account.Orders
                            .Where(o => o.Instrument == Instrument &&
                                       o.OrderType == OrderType.StopMarket &&
                                       (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                       o.OrderAction == OrderAction.Sell)
                            .ToList();
    
                        Print($"[DEBUG] Found {stopOrders.Count} working or accepted stop market orders for {Instrument.FullName}");
    
                        // Log why non-stop orders were excluded
                        var excludedOrders = activeOrders
                            .Where(o => !(o.Instrument == Instrument &&
                                         o.OrderType == OrderType.StopMarket &&
                                         (o.OrderState == OrderState.Working || o.OrderState == OrderState.Accepted) &&
                                         o.OrderAction == OrderAction.Sell))
                            .ToList();
                        foreach (var order in excludedOrders)
                        {
                            string reason = "";
                            if (order.Instrument != Instrument) reason = "Instrument mismatch";
                            else if (order.OrderType != OrderType.StopMarket) reason = $"Not a stop order: {order.OrderType}";
                            else if (order.OrderState != OrderState.Working && order.OrderState != OrderState.Accepted) reason = $"Invalid state: {order.OrderState}";
                            else if (order.OrderAction != OrderAction.Sell) reason = $"Invalid action: {order.OrderAction}";
                            Print($"[DEBUG] Excluded order {order.OrderId}: {reason}");
                        }
    
                        if (stopOrders.Count == 0)
                        {
                            Print("[DEBUG] No working or accepted stop market orders found to update.");
                            return;
                        }
    
                        foreach (Order stopOrder in stopOrders)
                        {
                            double newStopPrice = 0;
                            string updateReason = "";
    
                            // Rule: Within 10 points of target price
                            if (distance <= 10.00)
                            {
                                newStopPrice = currentPrice - (StopOffsetTicks * tickSize);
                                newStopPrice = Instrument.MasterInstrument.RoundToTickSize(newStopPrice);
                                updateReason = "Within 10 points of target";
                            }
                            else
                            {
                                Print($"[DEBUG] Stop order {stopOrder.OrderId}: No update, Distance={distance:F2} > 10.00");
                                continue;
                            }
    
                            if (stopOrder.StopPrice != newStopPrice)
                            {
                                try
                                {
                                    ChangeOrder(stopOrder, stopOrder.Quantity, 0, newStopPrice);
                                    Print($"[INFO] Updated stop order {stopOrder.OrderId} to {newStopPrice:F2} (previous: {stopOrder.StopPrice:F2}, reason: {updateReason})");
                                }
                                catch (Exception ex)
                                {
                                    Print($"[ERROR] Failed to update stop order {stopOrder.OrderId}: {ex.Message}");
                                }
                            }
                            else
                            {
                                Print($"[DEBUG] Stop order {stopOrder.OrderId} already at {newStopPrice:F2} (reason: {updateReason})");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Print($"[ERROR] Error in OnBarUpdate: {ex.Message}");
                }
            }
    
            private bool IsConnected()
            {
                return Account.All.Any(a => a.Name == SelectedAccount && a.Connection?.Status == ConnectionStatus.Connected);
            }
        }
    }​

    #2
    Hello davydhnz,

    Orders that are not submitted by the strategy instance or are submitted through Atm orders do not affect the strategy position or order update methods like OnOrderUpdate() or OnExecutionUpdate().

    You will need to use the Addon approach to detect an order through an Account and then modify the order with <Account>.Change().
    Join the official NinjaScript Developer Community for comprehensive resources, documentation, and community support. Build custom indicators and automated strategies for the NinjaTrader platforms with our extensive guides and APIs.

    Join the official NinjaScript Developer Community for comprehensive resources, documentation, and community support. Build custom indicators and automated strategies for the NinjaTrader platforms with our extensive guides and APIs.


    The AtmStrategyIdentifier indicator on the User App Share has a lot of useful code on detecting manually submitted atm orders.
    This indicator serves to provide labels for Atm strategies that are present on your chart. Each new Atm strategy will rotate through the colors defined in your Brush Collection. Simply add to a chart, select the data series you want to have labels added to, choose your font and add all the brushes/colors you want [&#8230;]


    The ProfitCaseStopTrailIndicatorExample on the support site demonstrates modifying an order using the Addon approach.



    Note, the stop price must be a valid price. If a stop price is changed very close to the current market price there is a possibility the market can move and cause the order to be rejected due to an invalid price.

    Comment


      #3
      NinjaTrader_ChelseaB,

      thanks for that. I got some good results with that, but the biggest issue I'm having now is how to group all working targets and stops with their respective entry.
      So far I've been using Time as the only way, but that can be problematic. There's no unifying number or something I can use to link targets/stops with their original entry?

      Because I'm making a strategy which is really a more sophisticated atm, I use account.Orders (for orders placed before turning on the atm) and account.OrderUpdate (for live orders), but any advice you can give me about how to consistently automatically look up the correct entry and best practice for that would be great.

      Thanks again.

      Comment


        #4
        Hello davydhnz,

        The exits are linked to the entry because you are using an Atm strategy.

        The AtmStrategyIdentifiier script shows how to group the orders made by the Atm in a collection by the Atm id.

        Comment


          #5
          NinjaTrader_ChelseaB,

          thank you so much for all of that. Incredibly generous and very patient considering that the second answer was in the first answer.

          Last question for now. I don't suppose there's any way to stop strategies displaying this when attempting to move stops?

          Click image for larger version

Name:	Screenshot 2025-04-25 002807.png
Views:	13
Size:	23.7 KB
ID:	1342374

          Sometimes the calculation is fine, but price moves so fast that by the time the adjustment is made, it's too late.
          It would be great to be able to turn these off.

          Thanks again!

          Comment


            #6
            Hello davydhnz,

            Unfortunately, no. If the order has an invalid price it will be rejected and a rejected order will create a pop-up to inform the user.

            This was noted in post # 2.

            "Note, the stop price must be a valid price. If a stop price is changed very close to the current market price there is a possibility the market can move and cause the order to be rejected due to an invalid price.
            https://support.ninjatrader.com/s/article/NinjaScript-Developer-Guide-Avoiding-stop-order-rejections-due-to-invalid-price"

            Comment

            Latest Posts

            Collapse

            Topics Statistics Last Post
            Started by abelsheila, 05-14-2025, 07:38 PM
            2 responses
            34 views
            0 likes
            Last Post hglover945  
            Started by nailz420, 05-14-2025, 09:14 AM
            1 response
            73 views
            0 likes
            Last Post NinjaTrader_ChristopherJ  
            Started by NinjaTrader_Brett, 05-12-2025, 03:19 PM
            0 responses
            353 views
            1 like
            Last Post NinjaTrader_Brett  
            Started by domjabs, 05-12-2025, 01:55 PM
            2 responses
            67 views
            0 likes
            Last Post domjabs
            by domjabs
             
            Started by Morning Cup Of Trades, 05-12-2025, 11:50 AM
            1 response
            87 views
            0 likes
            Last Post NinjaTrader_ChristopherJ  
            Working...
            X