Once we’ve drawn a trend line in MultiCharts .NET we can submit market orders with it. We can also draw a line manually and use it as a stop-loss. But how can we use trend lines to visually highlight a trading strategy’s trailing stop-loss orders?

Drawing trend lines and trading programmatically in MultiCharts .NET

DrwTrendLine.Create() is a method that draws trend lines and returns a reference to the line made (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). When assigned to an ITrendLineObject variable, that reference allows accessing the line’s properties and methods. That way a line can be extended, relocated, or traded with market orders.

Highlighting a stop-loss with a trend line requires two steps. First, we need to draw a trend line when the stop-loss value changes (or is set for the first time). Then, when the stop-loss price remains the same, the existing trend line needs to be extended. This creates a ‘staircase’ with increasingly higher trend lines for long positions and gradually lower lines for shorts.

For that we need to access the previous trend line and compare the current bar’s stop-loss value with the preceding bar’s value. One way to implement that programmatically is with a VariableSeries<T> class that creates a series of variable that, just like the chart’s data series, provide access to values held on the close of previous bars (Henry MultiCharts, 2013; MultiCharts, 2014). These variable series are type-safe and generic: their T is replaceable with any type, but once the type is set, it can only hold values of that type (Sempf, Sphar, & Davis, 2010). For example, a VariableSeries<int> is a variable series holding integers, one for each bar of the data series. In this context, also see tracking trend lines with a variable series.

To generate the actual stop-loss orders themselves three things are required (MultiCharts, 2014): declaring an IOrderPriced variable, creating the stop-loss order with the OrderCreator.Stop() method, and submitting the order itself by calling its Send() method. Let’s see how the programming example implements all of this.

Example: display trailing stop orders with MultiCharts .NET’s trend lines

When the example strategy is added to an E-mini S&P 500 future chart, it draws small trend lines that display the trailing stop-loss orders’ price level:

Trend lines that show a stop-loss in MultiCharts .NET - 1 Trend lines that show a stop-loss in MultiCharts .NET - 2

And on a 5-minute EUR/USD chart the strategy looks as follows:

Trend lines that show a stop-loss in MultiCharts .NET - 3

Programmatic highlighting trailing stop-loss orders in MultiCharts .NET

The example’s code looks as follows:

[IOGMode(IOGMode.Disabled)]
public class Example_TrendLinesTrailingStop : SignalObject
{
    public Example_TrendLinesTrailingStop(object _ctx) : base(_ctx) { }

    private IOrderMarket enterLong, enterShort;
    private IOrderPriced exitLong, exitShort;

    private VariableSeries<double> longStop, shortStop;
    private VariableSeries<ITrendLineObject> longLines, shortLines;

    protected override void Create()
    {
        // Create the orders
        enterLong = OrderCreator.MarketNextBar(new 
            SOrderParameters(Contracts.Default, EOrderAction.Buy));

        enterShort = OrderCreator.MarketNextBar(new 
            SOrderParameters(Contracts.Default, EOrderAction.SellShort));

        exitLong = OrderCreator.Stop(new 
            SOrderParameters(Contracts.Default, EOrderAction.Sell));

        exitShort = OrderCreator.Stop(new 
            SOrderParameters(Contracts.Default, EOrderAction.BuyToCover));

        // Create the data series
        longStop  = new VariableSeries<double>(this);
        shortStop = new VariableSeries<double>(this);

        longLines  = new VariableSeries<ITrendLineObject>(this);
        shortLines = new VariableSeries<ITrendLineObject>(this);
    }

    protected override void CalcBar()
    {
        // Only execute the remainder of CalcBar() when needed
        if (Environment.ApplicationCode != EApplicationCode.Charting ||
            Environment.Optimizing == true)
            return;

        // Enter a position when the strategy is flat
        if (StrategyInfo.MarketPosition == 0)
        {
            if (Bars.CurrentBar % 5 == 0)
            {
                enterLong.Send();
                longStop.Value = 0;
            }
            else if (Bars.CurrentBar % 9 == 0)
            {
                enterShort.Send();
                shortStop.Value = Double.MaxValue;
            }
        }

        // Manage long position
        if (StrategyInfo.MarketPosition > 0)
        {
            // Update stop-loss
            longStop.Value = Math.Max(longStop[1], Bars.Low[0] - Bars.Range());

            exitLong.Send(longStop[0]);

            // Draw or update trend line
            if (longStop[0] != longStop[1])
            {
                ChartPoint beginPoint = new ChartPoint(
                    Bars.Time[1], longStop[0]);
                ChartPoint endPoint   = new ChartPoint(
                    Bars.Time[0], longStop[0]);

                longLines.Value = DrwTrendLine.Create(beginPoint, endPoint);

                // Change line appearance
                longLines[0].Size  = 2;
                longLines[0].Color = Color.Red;
            }
            else
            {
                ChartPoint endPoint = new ChartPoint(Bars.Time[0], longStop[0]);

                longLines[1].End = endPoint;
            }
        }
        // Manage short position
        else if (StrategyInfo.MarketPosition < 0)
        {
            // Update stop loss
            shortStop.Value = Math.Min(shortStop[1], 
                Bars.High[0] + Bars.Range());

            exitShort.Send(shortStop[0]);

            if (shortStop[0] != shortStop[1])
            {
                ChartPoint beginPoint = new ChartPoint(
                    Bars.Time[1], shortStop[0]);
                ChartPoint endPoint   = new ChartPoint(
                    Bars.Time[0], shortStop[0]);

                shortLines.Value = DrwTrendLine.Create(beginPoint, endPoint);

                // Change line appearance
                shortLines[0].Size  = 2;
                shortLines[0].Color = Color.LimeGreen;
            }
            else
            {
                ChartPoint endPoint = new ChartPoint(Bars.Time[0], shortStop[0]);

                shortLines[1].End = endPoint;
            }
        }
    }
}

We first set the IOGMode attribute to disabled to turn off intra-bar order generation so that the strategy is calculated on bar close only. That prevents trend lines from being updated on every tick, which gives a distracting odd visual effect.

The two IOrderMarket interface variables declared next (enterLong and enterShort) are used later to open a position, while the IOrderPriced variables (exitLong and exitShort) are our trailing stop-losses. Then two double variable series (VariableSeries<double>) are declared (longStop and shortStop) that we’ll put the stop-loss values in. The two ITrendLineObject variable series (VariableSeries<ITrendLineObject>) are named longLines and shortLines and hold the references to trend lines highlighting trailing stops of long and short positions, respectively.

Creating market and stop-loss orders in MultiCharts .NET

In the Create() method, which is executed immediately after an instance of the script is made (MultiCharts, 2014), we first make the two market orders with OrderCreator.MarketNextBar(). This method creates a market order that’s submitted at the open of the bar after the one it was generated on (MultiCharts, 2014).

We specify the order settings with a SOrderParameters struct that has two arguments: Contract.Default sets the order’s position size to the value set in the ‘Strategy Properties’ screen while an EOrderAction enumerated value specifies the order type (MultiCharts, 2014). We set the enterLong order to EOrderAction.Buy and mark the enterShort order EOrderAction.SellShort.

Then we create the two stop orders with the OrderCreator.Stop() method. These are submitted at the open of the next bar, following the bar on which they’re generated on (MultiCharts, 2014). A SOrderParameters struct sets them to the default order quantity (Contracts.Default) and marks the exit long stop as EOrderAction.Sell while the exit short stop is set to EOrderAction.BuyToCover (MultiCharts, 2014).

After that we instantiate the four VariableSeries<T> classes with C#’s new keyword so that these four variable series are created.

Open trading positions with market orders

Next is the CalcBar() method that begins with an if statement that evaluates two expressions. The first is whether the script’s environment (returned by Environment.ApplicationCode; PowerLanguage .NET Help, n.d.) equals (==) EApplicationCode.Charting, which it does when the script is applied to a chart. The other expression evaluates if Environment.Optimizing equals true, which is the case when the strategy is being optimised (MultiCharts, 2014). Since these Boolean expressions are combined with the conditional or operator (||), only one of them needs to be true before the entire condition is true (Dorman, 2010). When that happens, we execute the return keyword to exit the current method immediately (see Dorman, 2010).

The reasoning behind this if statement is that, since trend lines cannot be drawn in the Portfolio Trader or Real-Time Market Scanner, we only proceed with the strategy’s code when the script is applied to a chart. Furthermore, since trend lines cannot be optimised (MultiCharts, 2014), we also skip CalcBar()’s code when the strategy is being optimised.

The next if statement checks if StrategyInfo.MarketPosition equals (==), which it does when the strategy is flat (see MultiCharts, 2014). We then enter a long or short position in an arbitrary manner. For that a nested if/else statement evaluates if the current bar number (Bars.CurrentBar) divides evenly into 5 or 9. We test this with the modulus operator (%) which returns the arithmetic remainder of dividing the first value by the second (Liberty & MacDonald, 2009).

When the bar number is an even multiple of 5, this operator returns a value equal to 0 and the if statement is executed. In it we submit the enterLong order by calling its Send() method. We then, in order to calculate the trailing stop correctly later on, set the longStop variable series’ current value to 0 with its Value property. That property sets or retrieves the current bar’s value of a VariableSeries<T> class instance (PowerLanguage .NET Help, n.d.).

And when Bars.CurrentBar evenly divides into 9, the else if portion executes the enterShort order’s Send() method. Then the shortStop variable series’ current value is set to Double.MaxValue, a constant that returns the largest possible value of a double (Microsoft Developer Network, n.d.). We do so in order to calculate the stop correctly in the next part of the code.

Calculate and submit a trailing stop-loss in MultiCharts .NET

The next segment is an if/else statement that first checks if the strategy is long, which is the case when StrategyInfo.MarketPosition returns a value greater than (>) 0 (MultiCharts, 2014). We then update the trailing long stop-loss by assigning longStop.Value the value returned by Math.Max(), a method that accepts two arguments and returns the largest of these two (Albahari & Albahari, 2012).

The values we pass into that method are longStop[1] (the variable series’ value on the previous bar) and the current bar’s low (Bars.Low[0]) minus its range (Bars.Range()). And so Math.Max() either returns the previous stop-loss value or a new, higher value. We then pass the current bar’s long stop value (longStop[0]) into the exitLong order’s Send() method to generate the trailing stop-loss order itself.

Drawing or updating a trend line based on the trailing stop-loss value

A nested if/else statement first evaluates whether the current stop-loss value (longStop[0]) is unequal to (!=) the stop-loss of the previous bar (longStop[1]). When that happens, a new trend line for this new trailing stop-loss value is needed.

So we first define two ChartPoint struct variables to hold the coordinates for the new line. The first, beginPoint, is set to the previous bar’s time (Bars.Time[1]) and the current, updated stop-loss value (longStop[0]). The second, endPoint, has its DateTime value set to that of the current bar (Bars.Time[0]) and the current stop-loss value (longStop[0]).

We then draw the trend line between those two chart points with DrwTrendLine.Create() and pass in the beginPoint and endPoint variables. This method returns an ITrendLineObject reference (PowerLanguage .NET Help, n.d.), which we assign to the current value of the longLines variable series with its Value property. This way we can access this trend line on the next price bars through the variable series. After that we change the trend line’s visual appearance by accessing the trend line with longLines[0] and setting its Size and Color properties to new values.

The else part of the if/else statement executes when the current value of the longStop variable series equals that of the previous bar. In that situation we need to extend the existing trend line with one bar for the horizontal trend line. To do so we first create a ChartPoint struct variable named endPoint and set it to the current bar’s time (Bars.Time[0]) and stop value (longStop[0]).

We update the end point of the previous bar’s trend line (longLines[1].End) with the trend line’s variables series (longLines[1]) and the End property, which returns or sets the ChartPoint value of the line’s end point (PowerLanguage .NET Help, n.d.). Since a VariableSeries<T> simply repeats the previous bar value when no new value is assigned to it, we don’t need to draw a trend line on the previous bar before longLines[1] returns a trend line reference. See managing trend lines with a VariableSeries<T> class for more.

Updating stop-loss values for shorts and manipulating the trend line

The else if component of the if/else statement in CalcBar() then evaluates if StrategyInfo.MarketPosition returns a value less than (<) 0, which indicates that the strategy is short (see MultiCharts, 2014). The code block below this statement is very similar to the code with which we managed long trailing stop-losses.

We first update the variable series that hold the short stop prices (shortStop.Value) with the value provided by Math.Min(), a method that returns the lowest value of two arguments (Albahari & Albahari, 2012). In it, we pass the previous bar’s stop-loss value (shortStop[1]) and the newly calculated stop (Bars.High[0] + Bars.Range()). This computes a stop-loss with a price that is equal to or less than the previous bar’s stop value. The shortStop.Value property was reset earlier to Double.MaxValue so that a correct stop price is calculated here when a new short position is opened; resetting shortStop.Value to zero doesn’t work due to Math.Min().

Then we submit the exitShort stop-loss order by calling its Send() method and passing in the current stop-loss value (shortStop[0]). An if/else statement checks if the current bar’s stop value differs from the previous bar, in which case we draw (just like with the long stop) a new trend line with a price value set to shortStop[0] and a Color.LimeGreen colour. In the else code block we generate a new ChartPoint struct variable based on the current bar’s time and short stop value (shortStop[0]). This variable is subsequently assigned to the previous bar’s trend line (shortLines[1]) with that line’s End property.

Tip: We submit the stop-loss orders as long as there’s an open strategy position because open orders are only kept active when resubmitted each time the strategy calculates (e.g., see MultiCharts Wiki, 2012). So when an unfilled order’s Send() method is not executed when the script calculates again, the open order is cancelled.

Other examples of trend lines and trading strategies are submitting market orders based on a trend line and using a manually drawn trend line as a stop-loss.

Summary

The DrwTrendLine.Create() method draws trend lines and returns an ITrendLineObject reference to the line made. When that reference is put into a VariableSeries<T> class instance, trend lines drawn on previous bars are accessible. And with that we can extend the trend line associated with the previous bar to create horizontal lines that highlight trailing stop prices. The trailing stop-losses themselves require declaring an IOrderPriced variable, creating the order with OrderCreator.Stop(), and finally submitting it with Send(). When the prices of trailing stops are stored in a VariableSeries<T>, stop values from previous bars are retrievable. Math.Min() and Math.Max() can be used to make stop-losses trailing by setting the stop’s price to either the previous value or one that’s more in favour of the open position.

Complete MultiCharts .NET trading strategy example

using System;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using ATCenterProxy.interop;

namespace PowerLanguage.Strategy
{
    [IOGMode(IOGMode.Disabled)]
    public class Example_TrendLinesTrailingStop : SignalObject
    {
        public Example_TrendLinesTrailingStop(object _ctx) : base(_ctx) { }

        private IOrderMarket enterLong, enterShort;
        private IOrderPriced exitLong, exitShort;

        private VariableSeries<double> longStop, shortStop;
        private VariableSeries<ITrendLineObject> longLines, shortLines;

        protected override void Create()
        {
            // Create the orders
            enterLong = OrderCreator.MarketNextBar(new 
                SOrderParameters(Contracts.Default, EOrderAction.Buy));

            enterShort = OrderCreator.MarketNextBar(new 
                SOrderParameters(Contracts.Default, EOrderAction.SellShort));

            exitLong = OrderCreator.Stop(new 
                SOrderParameters(Contracts.Default, EOrderAction.Sell));

            exitShort = OrderCreator.Stop(new 
                SOrderParameters(Contracts.Default, EOrderAction.BuyToCover));

            // Create the data series
            longStop  = new VariableSeries<double>(this);
            shortStop = new VariableSeries<double>(this);

            longLines  = new VariableSeries<ITrendLineObject>(this);
            shortLines = new VariableSeries<ITrendLineObject>(this);
        }

        protected override void CalcBar()
        {
            // Only execute the remainder of CalcBar() when needed
            if (Environment.ApplicationCode != EApplicationCode.Charting ||
                Environment.Optimizing == true)
                return;

            // Enter a position when the strategy is flat
            if (StrategyInfo.MarketPosition == 0)
            {
                if (Bars.CurrentBar % 5 == 0)
                {
                    enterLong.Send();
                    longStop.Value = 0;
                }
                else if (Bars.CurrentBar % 9 == 0)
                {
                    enterShort.Send();
                    shortStop.Value = Double.MaxValue;
                }
            }

            // Manage long position
            if (StrategyInfo.MarketPosition > 0)
            {
                // Update stop-loss
                longStop.Value = Math.Max(longStop[1], Bars.Low[0] - Bars.Range());

                exitLong.Send(longStop[0]);

                // Draw or update trend line
                if (longStop[0] != longStop[1])
                {
                    ChartPoint beginPoint = new ChartPoint(Bars.Time[1], longStop[0]);
                    ChartPoint endPoint   = new ChartPoint(Bars.Time[0], longStop[0]);

                    longLines.Value = DrwTrendLine.Create(beginPoint, endPoint);

                    // Change line appearance
                    longLines[0].Size  = 2;
                    longLines[0].Color = Color.Red;
                }
                else
                {
                    ChartPoint endPoint = new ChartPoint(Bars.Time[0], longStop[0]);

                    longLines[1].End = endPoint;
                }
            }
            // Manage short position
            else if (StrategyInfo.MarketPosition < 0)
            {
                // Update stop loss
                shortStop.Value = Math.Min(shortStop[1], Bars.High[0] + Bars.Range());

                exitShort.Send(shortStop[0]);

                if (shortStop[0] != shortStop[1])
                {
                    ChartPoint beginPoint = new ChartPoint(Bars.Time[1], shortStop[0]);
                    ChartPoint endPoint   = new ChartPoint(Bars.Time[0], shortStop[0]);

                    shortLines.Value = DrwTrendLine.Create(beginPoint, endPoint);

                    // Change line appearance
                    shortLines[0].Size  = 2;
                    shortLines[0].Color = Color.LimeGreen;
                }
                else
                {
                    ChartPoint endPoint = new ChartPoint(Bars.Time[0], shortStop[0]);

                    shortLines[1].End = endPoint;
                }
            }
        }
    }
}

References

Albahari, J. & Albahari, B. (2012). C# 5.0 in a Nutshell: The Definitive Reference (5th edition). Sebastopol, CA: O’Reilly Media.

Dorman, S. (2010). Sams Teach Yourself Visual C# 2010 in 24 Hours. Indianapolis, IN: Sams/Pearson Education.

Henry MultiCharts (2013, May 31). VariableObject – why use it? – Forum discussion. Retrieved on May 29, 2015, from http://www.multicharts.com/discussion/viewtopic.php?f=19&p=70279#p70248

Liberty, J. & MacDonald, B. (2009). Learning C# 3.0: Master the Fundamentals of C# 3.0. Sebastopol, CA: O’Reilly Media.

Microsoft Developer Network (n.d.). Double.MaxValue Field. Retrieved on June 18, 2015, from https://msdn.microsoft.com/en-us/library/system.double.maxvalue%28v=vs.110%29.aspx

MultiCharts (2014). MultiCharts .NET Programming Guide (version 1.1). Retrieved from http://www.multicharts.com/downloads/MultiCharts.NET-ProgrammingGuide-v1.1.pdf

MultiCharts Wiki (2012, February 19). Buy. Retrieved on June 15, 2015, from http://www.multicharts.com/trading-software/index.php/Buy

PowerLanguage .NET Help (n.d.). Retrieved on November 18, 2014, from http://www.multicharts.com/downloads/PowerLanguage.NET.chm

Sempf, B., Sphar, C., & Davis, S.R. (2010). C# 2010 All-In-One for Dummies. Hoboken, NJ: John Wiley & Sons.