In MultiCharts .NET we can programmatically work with manually drawn trend lines by first retrieving the chart’s active trend line and then, for example, trigger alerts based on the manual trend line. But how do we work with a list of trend lines that were drawn by hand?

Working with manual trend lines programmatically in MultiCharts .NET

By programmatically accessing a MultiCharts .NET trend line we can do things like relocating, deleting, or extending a line. All of those operations require a trend line reference, which is for example returned by the DrwTrendLine.Create() method after drawing a trend line (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). With the reference stored in an ITrendLineObject variable, we can access the trend line object’s properties and methods through the variable.

But manually drawn trend lines are not accessible in the script by default. And while the DrwTrendLine.Active property returns the reference of the active trend line (MultiCharts, 2014), whether that line is drawn manually or not, that still only provides access to a single line. So if we want to change or remove manual trend lines programmatically, how to access them in the first place?

The DrwTrendLine.GetTrendLineObjects() method, together with the Manual value (from the EDrawingSource enumeration) as an argument, returns a collection with all references to the chart’s manual trend lines (see PowerLanguage .NET Help, n.d.). Technically, the collection returned by this method implements the IEnumerable<T> interface (see PowerLanguage .NET Help, n.d.), which is a type-safe collection interface that can conveniently be enumerated (that is, looped over) with a foreach loop (Liberty & MacDonald, 2009).

But while convenient, foreach loops cannot remove items from the collection they loop over (Dorman, 2010). The for loop, however, can (Sharp, 2013). An easy way to remove manual trend lines is by transforming their collection (returned by DrwTrendLine.GetTrendLineObjects()) to the List<T> collection type, which is a generic list that can hold any type like double, int, or, in our case, ITrendLineObject (Stellman & Greene, 2010). We’ll then go through that List<T> collection with the for loop, removing each trend line in turn. Let’s look at the programming example to see how.

Example: changing manual trend lines or removing them

After adding the indicator to, for example, a Russell 2000 Index mini future chart, we first need to draw a few trend lines by hand:

Manual trend line collection in MultiCharts .NET - 1

Then when a mouse click with Control is registered on the chart, the lines’ appearance is changed to the indicator’s input settings:

Manual trend line collection in MultiCharts .NET - 2

And a mouse click with Shift removes all manual trend lines from the chart:

Manual trend line collection in MultiCharts .NET - 3

Adjusting manual trend lines with programming code in MultiCharts .NET

The example indicator looks as follows:

[SameAsSymbol(true), MouseEvents(true)]
public class Example_ManualTrendLines : IndicatorObject
{
    [Input]
    public Color LineColour { get; set; }

    [Input]
    public int LineSize { get; set; }

    public Example_ManualTrendLines(object _ctx) : base(_ctx) 
    {
        // Set the inputs' default values
        LineColour = Color.LimeGreen;
        LineSize   = 2;
    }

    protected override void CalcBar() { }

    protected override void OnMouseEvent(MouseClickArgs arg)
    {
        // Remove all manual trend lines with a click + Shift
        if (arg.keys == Keys.Shift)
        {
            // Get all manual trend lines
            IEnumerable<ITrendLineObject> manualTrendLines =
                DrwTrendLine.GetTrendLineObjects(EDrawingSource.Manual);

            // Transform that collection into a List
            List<ITrendLineObject> trendLineList = manualTrendLines.ToList();

            // Loop through the List and remove everything
            for (int i = 0; i < trendLineList.Count; i++)
            {
                trendLineList[i].Delete();
            }
        }
        // Change the manual trend lines' appearance with a click + Control
        else if (arg.keys == Keys.Control)
        {
            foreach (ITrendLineObject line in 
                DrwTrendLine.GetTrendLineObjects(EDrawingSource.Manual))
            {
                line.Size  = LineSize;
                line.Color = LineColour;
            }
        }
    }
}

The code begins by defining two MultiCharts .NET class attributes. With SameAsSymbol set to true the indicator is displayed on the data series while MouseEvents set to true enables mouse click processing.

We then add two inputs to the script: an automatic property of type Color that’s named LineColour and an integer one termed LineSize. Both are set in the indicator’s constructor to default values of Color.LimeGreen and 2, respectively. We’ll use both inputs later when changing the appearance of the trend lines.

Removing all manual trend lines with a mouse click

Next in the example is OnMouseEvent(), a method that’s executed with each mouse click on the chart (see PowerLanguage .NET Help, n.d.) when the MouseEvents attribute is set to true.

This method consists out of an if/else statement. The if part evaluates if the key pressed during the click (stored in the arg.keys variable) equals (==) the Keys.Shift value. This Keys enumeration comes from the Windows.System.Forms namespace that we added to the top of the indicator’s code with a using directive (see full code example below).

When a click with Shift happened, the if statement’s code block first retrieves all manual trend lines from the chart. For this it calls DrwTrendLine.GetTrendLineObjects() with the Manual value from the EDrawingSource enumeration passed in. This lets that method return a collection of manual trend lines (see PowerLanguage .NET Help, n.d.).

That collection is then assigned to the IEnumerable<ITrendLineObject> collection named manualTrendLines. With the System.Collections.Generic namespace added to the top of the indicator’s code, we don’t need to qualify this IEnumerable<T> interface nor the List<T> interface used later on with their namespace.

While the for loop can go through that collection, it can’t remove all items from it. So we first transform manualTrendLines into a collection that can have items added and/or removed. For that we declare a List<ITrendLineObject> collection named trendLineList and assign it the manualTrendLines collection converted to a List<T>. This latter is done with its ToList() method, which creates a List<T> from an IEnumerable<T> collection (Microsoft Developer Network, n.d.).

Then we create a for loop that starts at 0 and continues as long as the i loop variable is less than (<) the number of items in the list, which is returned by the list’s Count property (Liberty & MacDonald, 2009). The value of i is increased with 1 by the postfix increment operator (++) at the end of each loop cycle. This operator is a shorthand way to add 1 to a value and so i++ is simply the equivalent of i = i + 1 (Dorman, 2010). Inside the loop we use the i variable inside square brackets ([ and ]) to access a single item from the collection, which allows us to access all of that trend line’s properties and methods. Then, by calling the line’s Delete() method, we remove the trend line.

Changing the look of manual trend lines in MultiCharts .NET

The last part of OnMouseEvent() is the if/else statement’s else if portion that checks if the Control key was pressed during the mouse click.

When it was, a foreach loop is executed. This loop has its type set to ITrendLineObject and its variable, which represents the current element inside the loop (Liberty & MacDonald, 2009), is named line. The collection it loops over is returned by the DrwTrendLine.GetTrendLineObjects() method with the Manual value (from the EDrawingSource enumeration) passed. This means it returns a collection of manually-drawn trend lines (see PowerLanguage .NET Help, n.d.).

In the loop we change the trend line’s visual appearance by setting its Size and Color properties to the LineSize and LineColour inputs, respectively. And so when foreach has looped over all manual trend lines, each has the look specified in the indicator’s inputs. Since OnMouseEvent() is executed with every click, new manual trend lines on the chart also have their appearance changed when a click with Control happens again.

Other examples of working with manual trend lines are generating alerts based on a manually drawn trend line and submitting stop-loss orders with a manual trend line. More on working with a collection of trend lines is discussed in removing all trend lines from the chart, changing a specific trend line, and removing the first trend line.

Summary

Programmatically accessing trend line objects requires that their references are assigned to an ITrendLineObject variable. The DrwTrendLine.GetTrendLineObjects() method with the Manual argument (from the EDrawingSource enumeration) fetches a list of manually-drawn trend lines. This method returns a collection that implements the IEnumerable<T> interface, from which all trend line objects cannot be removed by looping. However, converting the trend line collection into a List<T> and looping through that generic list allows removing each of the trend line objects from the chart with their Delete() method.

Complete MultiCharts .NET indicator example

using System;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using System.Windows.Forms;         // For the Keys enumeration
using System.Collections.Generic;   // For the generic List and IEnumerable collections

namespace PowerLanguage.Indicator
{
    [SameAsSymbol(true), MouseEvents(true)]
    public class Example_ManualTrendLines : IndicatorObject
    {
        [Input]
        public Color LineColour { get; set; }

        [Input]
        public int LineSize { get; set; }

        public Example_ManualTrendLines(object _ctx) : base(_ctx) 
        {
            // Set the inputs' default values
            LineColour = Color.LimeGreen;
            LineSize   = 2;
        }

        protected override void CalcBar() { }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            // Remove all manual trend lines with a click + Shift
            if (arg.keys == Keys.Shift)
            {
                // Get all manual trend lines
                IEnumerable<ITrendLineObject> manualTrendLines =
                    DrwTrendLine.GetTrendLineObjects(EDrawingSource.Manual);

                // Transform that collection into a List
                List<ITrendLineObject> trendLineList = manualTrendLines.ToList();

                // Loop through the List and remove everything
                for (int i = 0; i < trendLineList.Count; i++)
                {
                    trendLineList[i].Delete();
                }
            }
            // Change the manual trend lines' appearance with a click + Control
            else if (arg.keys == Keys.Control)
            {
                foreach (ITrendLineObject line in 
                    DrwTrendLine.GetTrendLineObjects(EDrawingSource.Manual))
                {
                    line.Size  = LineSize;
                    line.Color = LineColour;
                }
            }
        }
    }
}

References

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.). Enumerable.ToList<TSource> Method. Retrieved on May 22, 2015, from https://msdn.microsoft.com/en-us/library/vstudio/bb342261%28v=vs.100%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

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.