Each MultiCharts .NET trend line is accessible through its own variable, which is doable when there are a couple of lines. But how to efficiently work with multiple trend lines?

Using code to draw trend lines in MultiCharts .NET

The DrwTrendLine.Create() method draws trend lines and returns a reference to the line made (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). Once we’ve assigned that reference to an ITrendLineObject interface variable, a line’s properties and methods are accessible through that variable. That way we can extend, remove, or relocate a trend line.

But with multiple trend lines it’s burdensome to keep track of each line’s variable. And while we can retrieve all trend lines with the DrwTrendLine.GetTrendLineObjects() method (PowerLanguage .NET Help, n.d.), that puts all lines into one collection. That’s not helpful if, for example, we have two groups of trend lines, such as lines between pivot highs and low.

Fortunately, we can create our own trend line collections to group similar lines together. Changing all lines from a certain group is then as simple as looping over their collection.

Using a List to keep track of (groups of) trend lines

One C# collection type is the easy-to-use List<T> class that automatically resizes itself when new elements are added (Sharp, 2013). The <T> indicates it’s generic: this T is replaced with whatever type the collection should hold (Stellman & Greene, 2010). That means a list with integers will be declared as List<int> while we’ll put trend lines in a List<ITrendLineObject> collection.

Adding an element to a List<T> is done by calling the list’s Add() method and supplying the element to be added (Liberty & MacDonald, 2009; Sharp, 2013). Since List<T> is a numerically indexed collection of items (Dorman, 2010), we access existing elements from this collection type by providing their zero-based index number between square brackets ([ and ]) (Sharp, 2013). To see how List<T> can be used to create and work with custom trend line collections, let’s examine the programming example.

Example: working with different groups of MultiCharts .NET trend lines

The example indicator draws several trend lines, such as on this VSTOXX mini future chart:

Working with a custom trend line collection in MultiCharts .NET - 1

When we click on the chart while Control is held down, all upward sloping lines are coloured green while those with a downward slope are made red:

Working with a custom trend line collection in MultiCharts .NET - 2

A mouse click with Shift changes the lines’ size and style of both groups:

Working with a custom trend line collection in MultiCharts .NET - 3

Finally, a click with both Control and Shift removes all lines:

Working with a custom trend line collection in MultiCharts .NET - 4

The type of lines – upward or downward sloping – depend on the recent price movement. For example, when the price has trended upward, like in the EUR/USD chart below, all lines have a rising slope:

Working with a custom trend line collection in MultiCharts .NET - 5

And so a mouse click with Control will make them all green:

Working with a custom trend line collection in MultiCharts .NET - 6

While a click with Shift makes them dashed and thicker:

Working with a custom trend line collection in MultiCharts .NET - 7

And a click with Control and Shift removes all lines from the chart:

Working with a custom trend line collection in MultiCharts .NET - 8

Programmatically working with custom MultiCharts .NET trend line collections

The example indicator’s code is the following:

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

    private List<ITrendLineObject> risingLines, fallingLines;

    protected override void StartCalc()
    {
        risingLines  = new List<ITrendLineObject>();
        fallingLines = new List<ITrendLineObject>();
    }

    protected override void CalcBar()
    {
        if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
        {
            for (int i = 0; i < 14; i++)
            {
                // Create the line's coordinates
                ChartPoint beginPoint = new ChartPoint(
                    Bars.FullSymbolData.Time[i + 40],
                    Bars.FullSymbolData.Close[i + 40]);

                ChartPoint endPoint = new ChartPoint(
                    Bars.FullSymbolData.Time[i],
                    Bars.FullSymbolData.Close[i]);

                // Add a trend line to one of the collections
                if (beginPoint.Price < endPoint.Price)
                {
                    risingLines.Add(DrwTrendLine.Create(beginPoint, endPoint));
                }
                else
                {
                    fallingLines.Add(DrwTrendLine.Create(beginPoint, endPoint));
                }
            }
        }
    }

    protected override void OnMouseEvent(MouseClickArgs arg)
    {
        // Click + Control: change the lines' colour
        if (arg.keys == Keys.Control)
        {
            for (int i = 0; i < risingLines.Count; i++)
            {
                risingLines[i].Color = Color.LimeGreen;
            }

            for (int i = 0; i < fallingLines.Count; i++)
            {
                fallingLines[i].Color = Color.Red;
            }
        }
        // Click + Shift: change the lines' size and style
        else if (arg.keys == Keys.Shift)
        {
            for (int i = 0; i < risingLines.Count; i++)
            {
                risingLines[i].Size  = 3;
                risingLines[i].Style = ETLStyle.ToolDashed2;
            }

            for (int i = 0; i < fallingLines.Count; i++)
            {
                fallingLines[i].Size = 2;
            }
        }
        // Click + Shift & Control: remove all trend lines
        else if (arg.keys == (Keys.Shift | Keys.Control))
        {
            for (int i = 0; i < risingLines.Count; i++)
            {
                if (risingLines[i].Exist)
                    risingLines[i].Delete();
            }

            for (int i = 0; i < fallingLines.Count; i++)
            {
                if (fallingLines[i].Exist)
                    fallingLines[i].Delete();
            }
        }
    }
}

The example begins with defining three MultiCharts .NET class attributes. The first, UpdateOnEveryTick set to false, makes the indicator calculate on bar close only. Since the lines are only drawn once, the script doesn’t need to be calculated on every real-time tick. The second attribute, SameAsSymbol, set to true displays the indicator on the price chart and not in a subchart. And with the MouseEvents attribute set to true we enable mouse click processing.

Then we declare two List<ITrendLineObject> collections: risingLines and fallingLines. Both are an instance of the List<T> class that comes from the System.Collections.Generic namespace, which we added to the top of the indicator’s source code with a using directive (see full code example below).

The risingLines and fallingLines collections are initialised in the StartCalc() method. This method is executed before the script begins processing all price bars (MultiCharts, 2014). Initialising the collections here ensures we have new, empty collections at the start of each full script calculation.

Drawing multiple MultiCharts .NET trend lines with a for loop

Next in the example is CalcBar(). This method contains an if statement that checks if the current bar number (returned by Bars.FullSymbolData.Current; see PowerLanguage .NET Help, n.d.) equals (==) the last bar number (Bars.FullSymbolDate.Count) minus 1. Since this is only true on the historical bar preceding the last bar, the lines drawn inside this if statement’s code block are only made once.

Then a for loop below this if statement begins at 0 and continues as long as the i loop variable is less than (<) 14. After each loop cycle, the postfix increment operator (++) increases the value of i with one. This operator is, in this context, just a shorthand way to write i = i + 1 (Dorman, 2010).

The for loop’s code block, executed 15 times, first creates two ChartPoint struct variables. The first, beginPoint, is set to the time and close of 40 plus i bars ago, while the second (endPoint) is set to the time and close of i bars ago. We retrieve price bar data for these ChartPoint chart locations with the Bars.FullSymbolData property that allows access to any bar of the data series (PowerLanguage .NET Help, n.d.), regardless of the indicator’s MaxBarsBack setting.

Next an if/else statement checks if we can expect a rising or falling trend line with these ChartPoint variables. For this the Price properties, that return a chart location’s price coordinate (PowerLanguage .NET Help, n.d.), from both ChartPoint variables are compared against each other.

When the price of the first chart coordinate is below that of the second, the line will have a rising slope. To add a line between those points to the risingLines collection we first call the Add() method. Inside this method we pass the DrwTrendLine.Create() method with the beginPoint and endPoint variables as arguments to draw a trend line. This way the reference returned by DrwTrendLine.Create() is added to the risingLines collection.

The else part of the if/else statement, executed when the intended line has a zero or negative slope, adds the reference of the trend line drawn between beginPoint and endPoint to the fallingLines collection. This way, after both for loops end, all trend lines drawn on the chart are added to either one of these collections.

Changing the appearance of trend lines in custom collections

Another part of the example is the OnMouseEvent() method, executed with each mouse click on the chart (see PowerLanguage .NET Help, n.d.) when the MouseEvents attribute is set to true. It contains an if/else statement that repeatedly evaluates the arg.keys variable, which holds the keyboard key pressed during the click.

The first condition is whether arg.keys equals (==) the Keys.Control value. This Keys enumeration originates from the System.Windows.Forms namespace, which we added to the top of the indicator’s code with a using keyword (see full code example below). With Control pressed during the click, this if statement executes two for loops.

The first for loop iterates over the risingLines list, starting at 0 and continuing as long as the i loop variable is less than (<) the number of elements in the collection, which is returned by the collection’s Count property (Stellman & Greene, 2010). Inside the loop, we use the i variable to access individual elements by placing this variable between square brackets ([ and ]) after the collection’s name. To change the colour of trend lines in the risingLines list to green, each element (that is, individual trend line reference) has its Color property set to lime green.

The second for loop below this if statement is highly similar, except that this one goes through the fallingLines collection and set each trend lines’ colour to red. While it seems unnecessary to have two nearly identical for loops, we do need two of them since the risingLines and fallingLines lists are likely to have a different number of elements.

The second if statement evaluates whether the arg.keys variable is the Keys.Shift enumerated value. When it is, we use two for loops to iterate over all elements in both lists to change the lines’ appearance. In the risingLines list we set the size of each line to 3 and its Style property to a dashed line, while lines in the fallingLines list have their Size property set to 2.

Removing lines from custom collections in MultiCharts .NET

The third part of the OnMouseEvent() method’s if/else statement checks whether the arg.keys variable equals the Shift and Control key combination, which we evaluate by placing the logical OR operator (|) between the Keys.Shift and Keys.Control enumerated values.

When this condition is true, two for loops are used to remove trend lines from either collection. In both loops, an if statement uses the line’s Exist property to check if the trend line is on the chart (PowerLanguage .NET Help, n.d.). When that property returns true, it means the line is on the chart and we then call the line’s Delete() method to remove the trend line. This way we don’t work on trend lines that are already removed.

For more on working with a custom collection of trend lines, see managing lines with a variable series and tracking lines with a dictionary. Examples of a regular trend line collection are retrieving all trend lines on the chart, removing all trend lines, and changing a specific trend line. There’s also an introduction to working with trend line collections.

Summary

The DrwTrendLine.Create() method draws trend lines and returns a reference to the trend line object. A convenient way to work with a group of trend lines is putting them into one or more collections that store each trend line’s reference in an ITrendLineObject variable. A generic List<T> is an easy-to-use collection for this: it keeps track of its own internal bookkeeping, new items are added with its Add() method, and the list’s elements can be accessed with a zero-based index number between square brackets ([ and ]) after the collection’s instance.

Full MultiCharts .NET indicator example

using System;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using System.Collections.Generic;       // Added for List
using System.Windows.Forms;             // Added for the Keys enumeration

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

        private List<ITrendLineObject> risingLines, fallingLines;

        protected override void StartCalc()
        {
            risingLines  = new List<ITrendLineObject>();
            fallingLines = new List<ITrendLineObject>();
        }

        protected override void CalcBar()
        {
            if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
            {
                for (int i = 0; i < 14; i++)
                {
                    // Create the line's coordinates
                    ChartPoint beginPoint = new ChartPoint(
                        Bars.FullSymbolData.Time[i + 40],
                        Bars.FullSymbolData.Close[i + 40]);

                    ChartPoint endPoint = new ChartPoint(
                        Bars.FullSymbolData.Time[i],
                        Bars.FullSymbolData.Close[i]);

                    // Add a trend line to one of the collections
                    if (beginPoint.Price < endPoint.Price)
                    {
                        risingLines.Add(DrwTrendLine.Create(beginPoint, endPoint));
                    }
                    else
                    {
                        fallingLines.Add(DrwTrendLine.Create(beginPoint, endPoint));
                    }
                }
            }
        }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            // Click + Control: change the lines' colour
            if (arg.keys == Keys.Control)
            {
                for (int i = 0; i < risingLines.Count; i++)
                {
                    risingLines[i].Color = Color.LimeGreen;
                }

                for (int i = 0; i < fallingLines.Count; i++)
                {
                    fallingLines[i].Color = Color.Red;
                }
            }
            // Click + Shift: change the lines' size and style
            else if (arg.keys == Keys.Shift)
            {
                for (int i = 0; i < risingLines.Count; i++)
                {
                    risingLines[i].Size  = 3;
                    risingLines[i].Style = ETLStyle.ToolDashed2;
                }

                for (int i = 0; i < fallingLines.Count; i++)
                {
                    fallingLines[i].Size = 2;
                }
            }
            // Click + Shift & Control: remove all trend lines
            else if (arg.keys == (Keys.Shift | Keys.Control))
            {
                for (int i = 0; i < risingLines.Count; i++)
                {
                    if (risingLines[i].Exist)
                        risingLines[i].Delete();
                }

                for (int i = 0; i < fallingLines.Count; i++)
                {
                    if (fallingLines[i].Exist)
                        fallingLines[i].Delete();
                }
            }
        }
    }
}

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.

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

Sharp, J. (2013). Microsoft Visual C# 2013 Step by Step. Microsoft Press.

Stellman, A. & Greene, J. (2010). Head First C#: A Brain-Friendly Guide (2nd edition). Sebastopol, CA: O’Reilly Media.

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