After drawing trend lines in MultiCharts .NET we’ll need to track their variables if we want to change the lines later on. While that’s doable for a few lines, it becomes a hassle when there are multiple or even groups of trend lines. How can we work efficiently with trend lines then?

Drawing and managing trend lines in MultiCharts .NET programmatically

We draw MultiCharts .NET trend lines programmatically with DrwTrendLine.Create(), a method that returns a reference to the line made (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). With that reference assigned to an ITrendLineObject interface variable, the line’s properties and methods are accessible through that variable. With that we can change the line’s appearance, its location, or retrieve the line’s price value.

However, creating individual variables for every trend line is a hassle and error prone. While we can manage trend lines with a generic list, that still requires a separate list for each group of trend lines. Luckily, with a C# dictionary we can easily keep track of multiple trend lines and classify them into different groups.

Using a C# dictionary to manage MultiCharts .NET trend lines

A Dictionary<TKey, TValue> is a collection class that associates a key with a value whereby the key is used to retrieve its value from the dictionary (Dorman, 2010; Liberty & MacDonald, 2009; Sharp, 2013). This means it works like a regular language dictionary, where a word (the key) is associated with its definition (the value).

The T in Dictionary<TKey, TValue> is a placeholder for a particular data type (Sempf, Sphar, & Davis, 2010) and is replaced with the actual type when creating a dictionary. This makes a dictionary very versatile: any kind of key can be associated with any kind of value (Liberty & MacDonald, 2009; Stellman & Greene, 2010). For instance, in the programming example below we use a Dictionary<int, ITrendLineObject> to map integers with trend lines.

Adding elements to a dictionary is done by calling its Add() method and supplying the key and value to be added (Liberty & MacDonald, 2009). While each key must be unique and different from null (Dorman, 2010; Sharp, 2013), values can be duplicated any number of times (Stellman & Greene, 2010).

We retrieve values from a dictionary by specifying the key, which can be done by placing the key inside square brackets ([ and ]) following the dictionary (Stellman & Greene, 2010). Elements in a dictionary are unordered, and so returned in an arbitrary order when looping over them (Dorman, 2010). Let’s look at the programming example to see how we can manage trend lines with a dictionary.

Example: managing and manipulating groups of trend lines in MultiCharts .NET

The example indicator draws trend lines on three data series. So when added to a chart with the S&P 500, Dow Jones, and NASDAQ-100 E-mini futures, it looks like:

MultiCharts .NET trend lines in a dictionary - 1

Each mouse click with Control held down in a data series’ chart changes the colour of all trend lines. For example, when that click occurred on the first data series the lines become red:

MultiCharts .NET trend lines in a dictionary - 2

A click with Control on the second data series leads to:

MultiCharts .NET trend lines in a dictionary - 3

And all lines are made green with a click with Control on the third data series:

MultiCharts .NET trend lines in a dictionary - 4

Another feature of the indicator is that the trend lines’ width is changed with a mouse click + Shift. A click on data 1 makes all lines thick:

MultiCharts .NET trend lines in a dictionary - 5

While a click with Shift on data 2 makes the lines thinner:

MultiCharts .NET trend lines in a dictionary - 6

And the lines are the thinnest after a Shift click on data 3:

MultiCharts .NET trend lines in a dictionary - 7

Managing MultiCharts .NET trend lines with a C# dictionary

The example’s code looks as follows:

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

    private Dictionary<int, ITrendLineObject> trendLines;

    protected override void StartCalc()
    {
        trendLines = new Dictionary<int, ITrendLineObject>();
    }

    protected override void CalcBar()
    {
        if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
        {
            // Draw the trend lines
            for (int i = 0; i < 15; i++)
            {
                // Loop through each of the data series
                for (int j = 3; j > 0; j--)
                {
                    ChartPoint beginPoint = new ChartPoint(
                        BarsOfData(j).FullSymbolData.Time[50 + (i * 3)],
                        BarsOfData(j).FullSymbolData.Close[50 + (i * 3)]);

                    ChartPoint endPoint = new ChartPoint(
                        BarsOfData(j).FullSymbolData.Time[(i * 2)],
                        BarsOfData(j).FullSymbolData.Close[(i * 2)]);

                    trendLines.Add((100 * j) + i,
                        DrwTrendLine.Create(beginPoint, endPoint, j));
                }
            }
        }
    }

    protected override void OnMouseEvent(MouseClickArgs arg)
    {
        // Only process mouse clicks with Control
        if (arg.keys == Keys.Control)
        {
            // Loop through all lines on all data series, 
            //  changing the colours of the lines
            for (int i = 0; i < 15; i++)
            {
                for (int j = 3; j > 0; j--)
                {
                    Color lineColour = 
                        (arg.data_number == 3) ? Color.LimeGreen :
                        (arg.data_number == 2) ? Color.DarkOrchid :
                        Color.Crimson;

                    trendLines[(100 * j) + i].Color = lineColour;
                }
            }
        }

        // Change the line's size with a click with Shift
        else if (arg.keys == Keys.Shift)
        {
            foreach (KeyValuePair<int, ITrendLineObject> line in trendLines)
            {
                line.Value.Size = 3 - arg.data_number;
            }
        }
    }
}

We start this example with three MultiCharts .NET class attributes: SameAsSymbol set to true displays the indicator on the data series, with UpdateOnEveryTick set to false the indicator calculates on bar close only, and MouseEvents set to true enables mouse click processing.

Creating a C# dictionary in MultiCharts .NET

Then we declare a Dictionary<int, ITrendLineObject> collection named trendLines to store our trend lines in. Since the Dictionary<TKey, TValue> class originates from the System.Collections.Generic namespace, we added that namespace with a using directive to the top of the script’s source code (see full code example further down below). That way we don’t need to precede the dictionary class each time with its namespace.

Our dictionary is instantiated with the new keyword in StartCalc(), a method that’s executed once before the script processes all historical price bars (see MultiCharts, 2014). Creating the dictionary here ensures that, each time when the script calculates again fully, it does so with an empty dictionary. So when for example the indicator is turned off and back on again, the dictionary is ‘cleaned’ from previous trend lines and no problems with duplicate keys arise.

Creating MultiCharts .NET chart coordinates in a nested loop

The CalcBar() method is the next part of the example. It contains an if statement that evaluates if the bar number that the script is currently calculated on (returned by Bars.FullSymbolData.Current; see PowerLanguage .NET Help, n.d.) equals (==) the bar preceding the last. This latter is arrived at by subtracting 1 from the number of bars on the chart, returned by the Bars.FullSymbolData.Count property (see MultiCharts, 2014).

Two nested for loops are executed when that expression evaluates to true. The first, outer for loop starts at 0 and continues as long as the i loop variable is less than (<) 15. After each loop cycle, the postfix increment operator (++) increases this variable’s value with 1. In this context, that operator is just a shorthand way to write i = i + 1 (Dorman, 2010).

We draw the trend lines in the second, inner loop. That loop begins with the j variable initialised to 3 and continues as long as this variable is greater than (>) 0. With the postfix decrement operator (--) this variable’s value is decreased with 1 after each pass through the loop. This operator is, just like the increment operator, an abbreviated way to write j = j - 1. This loop starts at 3 because, when drawing trend lines on multiple data series, the ones on the non-primary data series need to be drawn first (MultiCharts Support, personal communication, March 16, 2015). That’s why this loop begins at 3, then goes to 2, and ends with a j value of 1.

Two ChartPoint struct variables are created in the inner loop to hold the chart coordinates that we’re going the draw a line between. To set these to price bar data from the different data series, we use the BarsOfData() method with the j variable passed in.

The first ChartPoint variable, beginPoint, is set to the time and close of 50 + i * 3 bars ago. By multiplying the i outer loop variable with 3 a different starting point is generated for each of the 15 trend lines per data series. That bar’s data is accessed with the Bars.FullSymbolData property since this allows access to all price bars of the data series (see PowerLanguage .NET Help, n.d.). The second ChartPoint variable, endPoint, is created similarly except that it’s set to the time and close of i * 2 bars ago. This way each trend line is also given a different ending point.

Drawing trend lines and storing them into a dictionary

Then we call the trendLines dictionary’s Add() method inside the inner loop. This method adds a key/value pair to the dictionary and requires two arguments: the key and its associated value (Microsoft Developer Network, n.d.).

With the dictionary instantiated as Dictionary<int, ITrendLineObject>, we pass an integer and the value returned by the DrwTrendLine.Create() method into the dictionary’s Add() method. The integer key value is the result of computing (100 * j) + i so that lines on the three data series have unique key values in their 100s, 200s, and 300s. That creates three groups of trend lines in the dictionary based on their key values.

The second argument in the dictionary’s Add() method is the DrwTrendLine.Create() method with the beginPoint and endPoint variables for the line’s begin and end points together with the j loop variable to set the data series to draw the line on. Since DrwTrendLine.Create() returns a reference of type ITrendLineObject (PowerLanguage .NET Help, n.d.), it can be used here in the Add() method to specify the value associated with the key.

The code block inside this inner loop is repeated 15 times for each of the 3 data series on the chart, and so when both loops end we’ve added 45 trend lines to the dictionary with Add().

Processing mouse clicks in MultiCharts .NET to change trend lines in a collection

The example’s last part is the OnMouseEvent() method that’s executed when a mouse click is registered on the chart (see PowerLanguage .NET Help, n.d.), provided that the MouseEvents attribute is set to true. It contains an if/else statement that compares the arg.keys variable (which holds the keyboard key pressed during the click) against the Keys.Control and Keys.Shift values to see if these keys accompanied the mouse click. Both values are from the Keys enumeration located in the System.Windows.Forms namespace that we added with a using directive to the top of the indicator’s source code (see full code example below).

The first if statement triggers for the Control key, after which two for loops, one nested inside the other, execute. These loops are the same as we used earlier when adding trend lines to the dictionary, but this time we’re using them to calculate the dictionary’s key and access its associated value.

Inside the inner loop we determine the trend line’s colour with the conditional operator (?:). This operator evaluates an expression and returns the first value when the expression is true; otherwise, the second expression is returned (Albahari & Albahari, 2012; Dorman, 2010). With this operator we determine the lines’ colour based on the data series where the click occurred.

So we first evaluate whether the arg.data_number variable (which holds the clicked-on data series number; see PowerLanguage .NET Help, n.d.) equals (==) 3. When it does, the conditional operator assigns the Color.LimeGreen value to the lineColour variable; otherwise, the clicked-on data number is compared against a value of 2. When the click happened on the second data series, Color.DarkOrchid is assigned to the lineColour variable. When neither the second or third data series were clicked on, the conditional operator returns the Color.Crimson value to be assigned to the Color variable.

With the line colour determined, we access dictionary values by specifying their key in the square brackets ([ and ]) following the trendLines dictionary. To use the correct key we use the same calculation as earlier ((100 * j) + i), and then use the key’s associated value to set that line’s colour with its Color property.

Changing the size of trend lines in the dictionary

Instead of the for loop we can also loop over elements in a dictionary with the foreach loop. That’s done in the second part of OnMouseEvent() after we evaluate if the key pressed during the click is the Shift key (Keys.Shift).

We then loop over all elements in the dictionary with a foreach loop that has its type set to a KeyValuePair<TKey, TValue> struct, which represents a read-only key and its associated value used when enumerating with a foreach loop (Albahari & Albahari, 2012; Dorman, 2010; Sharp, 2013). The foreach loop’s variable is named line and the collection it loops over is the trendLines dictionary.

In the loop we use that line variable to access the element’s value (with the Value property) and then set this line’s Size property to another value to change the line’s appearance by adjusting its size (see PowerLanguage .NET Help, n.d.). Since the foreach loop iterates over all keys in the dictionary, we don’t need to specify which key to use but can suffice with using the value from the key/value pair.

We set the line’s Size property to the value of 3 minus arg.data_number (the variable that the mouse click happened on). This way trend lines are the thickest when the click occurred on the first data series (3 - 1 for a size of 2) and the thinnest with a click on the third data series (3 - 3 for the default size of 0).

Other examples of custom trend line collections are using a variable series with multiple trend lines and storing multiple lines in a generic list. We can also retrieve a collection of trend lines from the chart, which allows for things like removing all lines and changing a specific trend line.

Summary

The DrwTrendLine.Create() method draws trend lines and returns a reference to the trend line object on the chart. A convenient way to map these references to another value (like a data series number) is with a dictionary. A Dictionary<TKey, TValue> collection class associates any type of key with any type of value, a long as the key is unique and other than null. Elements from a dictionary can be retrieved in different ways, such as with the foreach loop that has its type set to a KeyValuePair<TValue, TKey> struct or with a for loop that places the key between the square brackets ([ and ]) following the dictionary to access that key’s value.

Complete MultiCharts .NET indicator example

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

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

        private Dictionary<int, ITrendLineObject> trendLines;

        protected override void StartCalc()
        {
            trendLines = new Dictionary<int, ITrendLineObject>();
        }

        protected override void CalcBar()
        {
            if (Bars.FullSymbolData.Current == (Bars.FullSymbolData.Count - 1))
            {
                // Draw the trend lines
                for (int i = 0; i < 15; i++)
                {
                    // Loop through each of the data series
                    for (int j = 3; j > 0; j--)
                    {
                        ChartPoint beginPoint = new ChartPoint(
                            BarsOfData(j).FullSymbolData.Time[50 + (i * 3)],
                            BarsOfData(j).FullSymbolData.Close[50 + (i * 3)]);

                        ChartPoint endPoint = new ChartPoint(
                            BarsOfData(j).FullSymbolData.Time[(i * 2)],
                            BarsOfData(j).FullSymbolData.Close[(i * 2)]);

                        trendLines.Add((100 * j) + i,
                            DrwTrendLine.Create(beginPoint, endPoint, j));
                    }
                }
            }
        }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            // Only process mouse clicks with Control
            if (arg.keys == Keys.Control)
            {
                // Loop through all lines on all data series, 
                //  changing the colours of the lines
                for (int i = 0; i < 15; i++)
                {
                    for (int j = 3; j > 0; j--)
                    {
                        Color lineColour = 
                            (arg.data_number == 3) ? Color.LimeGreen :
                            (arg.data_number == 2) ? Color.DarkOrchid :
                            Color.Crimson;

                        trendLines[(100 * j) + i].Color = lineColour;
                    }
                }
            }

            // Change the line's size with a click with Shift
            else if (arg.keys == Keys.Shift)
            {
                foreach (KeyValuePair<int, ITrendLineObject> line in trendLines)
                {
                    line.Value.Size = 3 - arg.data_number;
                }
            }
        }
    }
}

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.

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.). Dictionary<TKey, TValue>.Add Method. Retrieved on June 7, 2015, from https://msdn.microsoft.com/en-us/library/k7z0zy8k%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

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

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.