We can draw trend lines on the primary data series, on another data series, or even on indicator values. But how to copy a MultiCharts .NET trend line to another data series programmatically?

Drawing MultiCharts .NET trend lines and copying them

Trend lines are drawn with the DrwTrendLine.Create() method that returns a reference to the line created (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). When stored in an ITrendLineObject variable, that reference provides access to the trend line’s properties and methods. Those can then be used to do things like relocating, removing, or extending a trend line.

A MultiCharts .NET trend line, however, doesn’t have a method for copying itself to another data series. But what we can do is use a line’s begin and end coordinates (which are accessible through its Begin and End properties; see PowerLanguage .NET Help, n.d.) to determine the coordinates for the copied trend line drawn on another data series. To see how, let’s consider the programming example.

Example: copy a trend line to another data series

When the example script is added to the chart below, it first draws a trend line on the E-mini S&P 500 future:

Copy a MultiCharts .NET trend line - before

Then, when a mouse click with Control held down on the chart occurs, that line is replicated to the E-mini NASDAQ-100 data series:

Copy a MultiCharts .NET trend line - after

Programmatically replicating a MultiCharts .NET trend line

The example indicator looks as follows:

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

    private ITrendLineObject originalLine, copiedLine;

    protected override void CalcBar()
    {
        if (Bars.LastBarOnChart && originalLine == null)
        {
            // Define the original line's points
            ChartPoint begin = new ChartPoint(
                Bars.FullSymbolData.Time[45],
                Bars.FullSymbolData.Close[45]);

            ChartPoint end = new ChartPoint(
                Bars.FullSymbolData.Time[0],
                Bars.FullSymbolData.Close[0]);

            // Line on data 2 needs to be drawn first
            copiedLine = DrwTrendLine.Create(
                new ChartPoint(), new ChartPoint(), 2);

            // Draw and adjust the trend line
            originalLine = DrwTrendLine.Create(begin, end);

            originalLine.Color = Color.LimeGreen;
            originalLine.Size  = 2;
        }
    }

    protected override void OnMouseEvent(MouseClickArgs arg)
    {
        if (arg.keys == Keys.Control && originalLine != null &&
            copiedLine != null)
        {
            // Correct for data series with an unequal number of bars
            int barsDataSeries = Math.Min(Bars.FullSymbolData.Current,
                BarsOfData(2).FullSymbolData.Current);

            int barsBackStart = barsDataSeries - 
                (int)originalLine.Begin.BarNumber;

            int barsBackEnd = barsDataSeries -
                (int)originalLine.End.BarNumber;

            // Base the coordinates for the copied line on the original
            ChartPoint startingPoint = new ChartPoint(
                BarsOfData(2).FullSymbolData.Time[barsBackStart],
                BarsOfData(2).FullSymbolData.Close[barsBackStart]);

            ChartPoint endingPoint = new ChartPoint(
                BarsOfData(2).FullSymbolData.Time[barsBackEnd],
                BarsOfData(2).FullSymbolData.Close[barsBackEnd]);

            copiedLine.End   = endingPoint;
            copiedLine.Begin = startingPoint;

            // Copy the visual appearance
            copiedLine.Size  = originalLine.Size;
            copiedLine.Color = originalLine.Color;
        }
    }
}

We define three MultiCharts .NET class attributes first. SameAsSymbol set to true displays the indicator on the data series (and not in a subchart). The RecoverDrawings attribute set to false prevents automatic removal of intra-bar generated drawings (MultiCharts, 2014). And MouseEvents set to true enables mouse click processing.

In the indicator’s class we then declare two ITrendLineObject variables (originalLine and copiedLine). We’ll use these to hold our trend line references so we can access their properties and methods.

Drawing the original trend line on the MultiCharts .NET chart

Next is the CalcBar() method with an if statement that evaluates two conditions. The first is whether the current bar of the primary data series is the last, in which case Bars.LastBarOnChart returns true (see PowerLanguage .NET Help, n.d.). The second condition evaluates if the originalLine variable equals (==) null. The default value for unassigned reference variables is null (Sempf, Sphar, & Davis, 2010), which means they point to no object yet (Albahari & Albahari, 2012). Checking if originalLine equals null ensures we only draw a trend line when one hasn’t been associated with this variable yet.

The code inside the if statement code begins with creating two ChartPoint struct variables. The first (begin) is set to an arbitrary 45 bars ago while the second (end) is set to the current bar. To access the time and price of these bars, we use the Bars property (which provides access to the first data series; see MultiCharts, 2014) coupled with the FullSymbolData property. With this latter any bar of the data series can be accessed (PowerLanguage .NET Help, n.d.), allowing us to work independently from an indicator’s MaxBarsBack value.

After defining both chart coordinates, we don’t draw a trend line between them yet but first create a line on the second data series. That seems odd since our goal is to draw a trend line on the first data series, and then replicate it to the second data series. However, with two data series on the chart, the trend line on the second data series needs to be created before drawing the line on the first (MultiCharts Support, personal communication, March 16, 2015).

So we call DrwTrendLine.Create() to draw a trend line on a non-primary data series by passing in two ChartPoint structs together with an integer value of 2 that causes the line to be drawn on the second data series. Since we don’t care about its coordinates yet, we initialise two empty ChartPoint structs for the line’s begin and end point. The value returned by DrwTrendLine.Create() is assigned to the copiedLine variable so that we can relocate this trend line at a later point.

Then we call DrwTrendLine.Create() again, but this time with the begin and end ChartPoint variables as arguments to draw a trend line between them. Since this method draws on the first data series by default (see essentials of drawing trend lines for more), we don’t need to specify that. We assign the reference returned by DrwTrendLine.Create() to the originalLine variable, after which we use that variable to change the line’s visual appearance by setting its Color and Size properties to new values.

Placing the copied line based on the original trend line

Next in the example is the OnMouseEvent() method which processes mouse clicks on the chart (see PowerLanguage .NET Help, n.d.). This method consists out of an if statement that evaluates three conditions.

The first condition is whether the keyboard key held down during the click (accessible through arg.keys) equals the Control key. We check this latter with Keys.Control, a value from the Keys enumeration that’s located in the System.Windows.Forms namespace. With a using directive in the top of the code (see full code example below) we included that namespace in the script. The second and third conditions are whether the originalLine and copiedLine trend line variables are unequal to (!=) null. Should they be null, using them to access the line’s properties or methods triggers a NullReferenceException error (see Stellman & Greene, 2010), which is why we check them beforehand.

With all three conditions evaluating to true, the if statement’s code starts with making a correction for the different data series’ length. As it turns out, in MultiCharts .NET 9.0 the bar number returned by originalLine.BarNumber is based on the data series with the lowest amount of bars (in this case, the second data series). In order to determine how many bars ago this line was drawn (for replicating it on the second data series), we need to subtract the number of bars in the shortest data series from the trend line’s bar numbers.

So we declare an integer variable named barsDataSeries and assign it the minimum value of the number of bars of both data series. For that we use the Math.Min() method, which returns the minimum value of its two arguments (Albahari & Albahari, 2012). The values passed into this method are FullSymbolData.Current, which returns the current bar number (see PowerLanguage .NET Help, n.d.), preceded with either Bars (for accessing the primary data series) or BarsOfData(2) (for accessing the second data series) (MultiCharts, 2014).

We then determine how many bars ago the original line started and ended. To compute the bars back of the line’s begin point (barsBackStart) we subtract the line’s Begin.BarNumber property from the barsDataSeries variables. Since that property returns a nullable value, it needs to be explicitly cast to an integer ((int)) before we can subtract with it. To determine how many bars ago the original line ended, we subtract its End.BarNumber property from barsDataSeries and assign this computed value to the barsBackEnd variable.

Copying the existing trend line to another data series

Once we’ve determined how many bars ago the existing trend line started and ended, we use those values to determine the coordinates for the copied line. We first create a ChartPoint struct variable named startingPoint and set it to the time and close of the previously calculated barsBackStart number of bars ago. We use BarsOfData(2) here to access the second data series and FullSymbolData to access any bar from that data series (MultiCharts, 2014; PowerLanguage .NET Help, n.d.). We create the endingPoint ChartPoint variable in the same way, except that here we use barsBackEnd to retrieve bar data belonging to the end point of the original trend line.

With the original trend line’s coordinates translated to prices of the second data series, we set the copied line’s End and Begin properties to the endingPoint and startingPoint chart location variables. Since the copiedLine trend line is already drawn (in the CalcBar() method), we can suffice here with updating its location through those properties.

Then we set the copied line’s visual appearance to that of the original trend line. That’s done by setting the Size and Color properties of the copiedLine to those of the originalLine trend line. And so we’ve not only copied the original line’s location but also replicated its look.

To learn more, see plotting trend lines on different data series, relocating trend lines, and extending a line to its past and future values.

Summary

Trend lines are drawn with the DrwTrendLine.Create() method, which by default draws on the primary data series. But, with an additional integer argument this method can also draw on other data series. Trend lines on non-primary data series need to be drawn before the line on the first data series is created. We can replicate a trend line by retrieving the line’s begin and end point and translating those to the data series that we want to copy the line to.

Complete MultiCharts .NET indicator example

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

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

        private ITrendLineObject originalLine, copiedLine;

        protected override void CalcBar()
        {
            if (Bars.LastBarOnChart && originalLine == null)
            {
                // Define the original line's points
                ChartPoint begin = new ChartPoint(
                    Bars.FullSymbolData.Time[45],
                    Bars.FullSymbolData.Close[45]);

                ChartPoint end = new ChartPoint(
                    Bars.FullSymbolData.Time[0],
                    Bars.FullSymbolData.Close[0]);

                // Line on data 2 needs to be drawn first
                copiedLine = DrwTrendLine.Create(
                    new ChartPoint(), new ChartPoint(), 2);

                // Draw and adjust the trend line
                originalLine = DrwTrendLine.Create(begin, end);

                originalLine.Color = Color.LimeGreen;
                originalLine.Size  = 2;
            }
        }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            if (arg.keys == Keys.Control && originalLine != null &&
                copiedLine != null)
            {
                // Correct for data series with an unequal number of bars
                int barsDataSeries = Math.Min(Bars.FullSymbolData.Current,
                    BarsOfData(2).FullSymbolData.Current);

                int barsBackStart = barsDataSeries - 
                    (int)originalLine.Begin.BarNumber;

                int barsBackEnd = barsDataSeries -
                    (int)originalLine.End.BarNumber;

                // Base the coordinates for the copied line on the original
                ChartPoint startingPoint = new ChartPoint(
                    BarsOfData(2).FullSymbolData.Time[barsBackStart],
                    BarsOfData(2).FullSymbolData.Close[barsBackStart]);

                ChartPoint endingPoint = new ChartPoint(
                    BarsOfData(2).FullSymbolData.Time[barsBackEnd],
                    BarsOfData(2).FullSymbolData.Close[barsBackEnd]);

                copiedLine.End   = endingPoint;
                copiedLine.Begin = startingPoint;

                // Copy the visual appearance
                copiedLine.Size  = originalLine.Size;
                copiedLine.Color = originalLine.Color;
            }
        }
    }
}

References

Albahari, J. & Albahari, B. (2012). C# 5.0 in a Nutshell: The Definitive Reference (5th edition). 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

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.

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