Once we’ve programmatically drawn trend lines we might want to select a specific trend line instead of only selecting the most recently active line. But how can we change specific trend lines from a group of lines?

Creating MultiCharts .NET trend lines and selecting a specific line

The DrwTrendLine.Create() method draws trend lines and returns a reference to the line made (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). Storing that reference in an ITrendLineObject variable provides access to the line’s properties and methods, allowing us to work with the line after creating it.

But it’s not a problem if we don’t have the trend line variable for every line; then we just retrieve trend lines from the chart. So before we can work with a specific line when there are multiple ones drawn on the chart, we first need to get all lines to see if the one we’re looking for is actually on the chart.

The DrwTrendLine.GetTrendLineObjects() method returns a collection of trend lines that we can only go through with a foreach loop. Those loops require three parts: a collection to loop over and a variable together with its type that represents the current element inside the loop (Dorman, 2010). In such a loop we can compare the trend line that’s currently being looped over with the features of the trend line we’re searching for. To see how, let’s consider the programming example.

Example: selecting trend lines based on their price values

The example script draws 10 trend lines. Then the line with the lowest price point is coloured red while the one with the highest price point is made green. So on an E-mini Dow Jones future chart that looks like:

Changing a specific trend line in MultiCharts .NET - 1

Since the trend lines are drawn randomly, toggling the script off and on again creates different trend lines:

Changing a specific trend line in MultiCharts .NET - 2 Changing a specific trend line in MultiCharts .NET - 3

Programmatically selecting specific lines in MultiCharts .NET

The indicator’s code looks as follows:

[SameAsSymbol(true), UpdateOnEveryTick(false)]
public class Example_ChangeSpecificTrendLine : IndicatorObject
{
    public Example_ChangeSpecificTrendLine(object _ctx) : base(_ctx) { }

    private Random rnd = new Random();

    protected override void CalcBar()
    {
        if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
        {
            // Create 10 random trend lines
            for (int i = 0; i < 10; i++)
            {
                // Determine the random amount of bars back
                int randomBegin = rnd.Next(20, 40);
                int randomEnd   = rnd.Next(0, 10);

                ChartPoint lineBegin = new ChartPoint(
                    Bars.FullSymbolData.Time[randomBegin],
                    Bars.FullSymbolData.Close[randomBegin]);

                ChartPoint lineEnd = new ChartPoint(
                    Bars.FullSymbolData.Time[randomEnd],
                    Bars.FullSymbolData.Close[randomEnd]);

                ITrendLineObject trendLine = 
                    DrwTrendLine.Create(lineBegin, lineEnd);

                trendLine.Size  = 1;
                trendLine.Color = Color.CornflowerBlue;
            }

            // Make two trend lines for use inside the loop
            ITrendLineObject highestEndLine  = DrwTrendLine.Active;
            ITrendLineObject lowestBeginLine = DrwTrendLine.Active;

            // Get the line with the highest end point and the one 
            // with the lowest begin point
            foreach (ITrendLineObject line in 
                DrwTrendLine.GetTrendLineObjects(EDrawingSource.CurrentTech))
            {
                if (line.End.Price > highestEndLine.End.Price)
                {
                    highestEndLine = line;
                }
                else if (line.Begin.Price < lowestBeginLine.Begin.Price)
                {
                    lowestBeginLine = line;
                }
            }

            // Adjust the appearance of the two trend lines
            highestEndLine.Size  = 3;
            highestEndLine.Color = Color.LimeGreen;

            lowestBeginLine.Size  = 3;
            lowestBeginLine.Color = Color.Crimson;
        }
    }
}

We first define two MultiCharts .NET class attributes. SameAsSymbol set to true displays the indicator on the data series and not in a subchart. And with UpdateOnEveryTick set to false the indicator calculates on bar close only. Since the script draws trend lines on a historical bar, it doesn’t need to be evaluated on every real-time tick.

In the top of the indicator’s class we instantiate a Random class object, which generates pseudo-random numbers with a mathematical process (Liberty & MacDonald, 2009). For the purpose of our example, those numbers are random enough however.

Drawing random trend lines in MultiCharts .NET

Next up is the CalcBar() method. All of this method’s code is placed in an if statement that checks if the current bar number (returned by the Bars.FullSymbolData.Current property; PowerLanguage .NET Help, n.d.) equals (==) the last bar of the data series (Bars.FullSymbolData.Count) minus 1. Since this condition is only true on the historical bar before the last, the code inside the if statement only executes when the script processes all historical price bars. That means our trend lines are drawn once and not with every new, real-time price bar.

The if statement’s code starts with a for loop that begins at 0 and continues as long as i is less than (<) 10. After each loop cycle, this i loop variable is increased with one by the postfix increment operator (++). In this context, that operator is just a shorthand way of writing i = i + 1 (see Dorman, 2010).

In the for loop we first create two integer variables: randomBegin and randomEnd. Each is assigned a random value generated by Next(), in which we pass a minimum and maximum value to define the range of the random number (Microsoft Developer Network, n.d.).

We use those random numbers when we create the lineBegin and lineEnd ChartPoint struct variables. The price bars for these ChartPoint chart locations is retrieved with the Bars.FullSymbolData.Time and Bars.FullSymbolData.Close properties set to randomBegin and randomEnd bars back. The benefit of using Bars.FullSymbolData is that we can access any price bar of the data series (PowerLanguage .NET Help, n.d.) regardless of the indicator’s MaxBarsBack setting.

With the ChartPoint variables set to random bars, we call DrwTrendLine.Create() and pass in both variables to draw a trend line between them. The value returned by this method is assigned to the trendLine variable that we then use to change the trend line’s features by setting its Size and Color properties to different values. Once the for loop ends, the script has drawn 10 blue trend lines.

Selecting specific trend lines from the price chart

The next part of CalcBar() starts with declaring two ITrendLineObject variables: highestEndLine and lowestBeginLine. These are used in the foreach loop when a trend line matches a criterion we’re looking for: we’ll store the line with the highest end price into the highestEndLine variable while the line with the lowest begin price is assigned to lowestBeginLine.

Both variables are initialised to the recently active trend line reference that’s returned by the DrwTrendLine.Active property (see PowerLanguage .NET Help, n.d.). While we don’t use that active trend line reference, we do need to initialise both variables to any trend line reference; otherwise, the ‘use of unassigned local variable’-error is triggered.

We then go through the lines on the chart with the foreach loop that has its type set to ITrendLineObject and its variable to the name of line. The collection it loops over is returned by DrwTrendLine.GetTrendLineObjects() with the CurrentTech argument (from the EDrawingSource enumeration) passed, and so only trend lines created by the current script are included in that collection (PowerLanguage .NET Help, n.d.).

The if/else statement inside the loop compares the element that’s currently being looped over (line) with the highestEndLine and lowestBeginLine variables. When line’s end point (returned by the End.Price property) has a greater price value than the last price from highestEndLine, the line variable is assigned to highestEndLine. Similarly, when the loop’s current element in the else if part has a price lower than lowestBeginLine’s starting point (Begin.Price), lowestBeginLine is assigned the line variable. Of course, we could easily change this if/else statement to retrieve a trend line based on any another trend line property’s value.

When the loop ends, both highestEndLine and lowestBeginLine hold references to the trend lines we’re looking for: a line with the lowest starting point and the one with the highest ending point. We then highlight these lines on the chart by changing their Size and Color properties to adjust the lines’ visual appearance.

See working with a trend line’s ID for more on working with a specific trend line. For additional information about trend line collections see getting all MultiCharts .NET trend lines and removing all trend lines.

Summary

The DrwTrendLine.Create() method draws trend lines while DrwTrendLine.GetTrendLineObjects() returns a collection of trend lines from the chart. This latter method requires an EDrawingSource argument that specifies which group of trend lines are included in the returned collection. By comparing these lines against some predefined characteristic we can search for specific lines. Once found, we need to assign them to an ITrendLineObject variable so that they can be used outside of the foreach loop.

Complete MultiCharts .NET indicator example

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

namespace PowerLanguage.Indicator
{
    [SameAsSymbol(true), UpdateOnEveryTick(false)]
    public class Example_ChangeSpecificTrendLine : IndicatorObject
    {
        public Example_ChangeSpecificTrendLine(object _ctx) : base(_ctx) { }

        private Random rnd = new Random();

        protected override void CalcBar()
        {
            if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
            {
                // Create 10 random trend lines
                for (int i = 0; i < 10; i++)
                {
                    // Determine the random amount of bars back
                    int randomBegin = rnd.Next(20, 40);
                    int randomEnd   = rnd.Next(0, 10);

                    ChartPoint lineBegin = new ChartPoint(
                        Bars.FullSymbolData.Time[randomBegin],
                        Bars.FullSymbolData.Close[randomBegin]);

                    ChartPoint lineEnd = new ChartPoint(
                        Bars.FullSymbolData.Time[randomEnd],
                        Bars.FullSymbolData.Close[randomEnd]);

                    ITrendLineObject trendLine = 
                        DrwTrendLine.Create(lineBegin, lineEnd);

                    trendLine.Size  = 1;
                    trendLine.Color = Color.CornflowerBlue;
                }

                // Make two trend lines for use inside the loop
                ITrendLineObject highestEndLine  = DrwTrendLine.Active;
                ITrendLineObject lowestBeginLine = DrwTrendLine.Active;

                // Get the line with the highest end point and the one 
                // with the lowest begin point
                foreach (ITrendLineObject line in 
                    DrwTrendLine.GetTrendLineObjects(EDrawingSource.CurrentTech))
                {
                    if (line.End.Price > highestEndLine.End.Price)
                    {
                        highestEndLine = line;
                    }
                    else if (line.Begin.Price < lowestBeginLine.Begin.Price)
                    {
                        lowestBeginLine = line;
                    }
                }

                // Adjust the appearance of the two trend lines
                highestEndLine.Size  = 3;
                highestEndLine.Color = Color.LimeGreen;

                lowestBeginLine.Size  = 3;
                lowestBeginLine.Color = Color.Crimson;
            }
        }
    }
}

References

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

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.). Random.Next Method (Int32, Int32). Retrieved on May 19, 2015, from https://msdn.microsoft.com/en-us/library/2dx6wyd4%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

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