With trend lines we can highlight an opening range, connect pivot swing highs and lows, and draw a triangle. But how can we use trend lines to draw rectangles around price bars?

Drawing a rectangle with MultiCharts .NET trend lines

Drawing trend lines is done with the DrwTrendLine.Create() method, which requires at least two ChartPoint structs that specify the line’s begin and end point (see PowerLanguage .NET Help, n.d.). This method returns a reference (MultiCharts, 2014) that, when saved to an ITrendLineObject variable, provides access to the trend line’s properties and methods. With such a variable we can relocate or remove a line, change its appearance, retrieve trend line price values, and so on.

Just like with drawing a triangle, creating a rectangle programmatically in MultiCharts .NET requires multiple trend lines, one for each of the box’s four angles. To position these lines, we also need four ChartPoint chart coordinates: one for the rectangle’s upper right, lower right, lower left, and upper left. To see how to do this programmatically, let’s examine the programming example.

Example: colouring boxes around prices in MultiCharts .NET

Before our rectangles are drawn, the Russell 2000 (mini) future chart looks like:

Before drawing boxes with MultiCharts .NET trend lines

A rectangle is drawn between a first and second click with Control on the chart. These rectangles are anchored to the highest high and lowest low of that range, and are either coloured green (when the price closed higher inside the box) or red (when the price dropped or remained unchanged):

After drawing boxes with MultiCharts .NET trend lines

These boxes can also be drawn on top of each other:

MultiCharts .NET boxes drawn on top of each other

Programmatically drawing boxes around price action in MultiCharts .NET

The code for creating these boxes with mouse clicks is the following:

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

    private ChartPoint firstClickLocation, secondClickLocation;

    protected override void CalcBar() { }

    protected override void OnMouseEvent(MouseClickArgs arg)
    {
        if (arg.keys != Keys.Control)
            return;

        int barsAgo = Bars.FullSymbolData.Current - arg.bar_number;

        // Determine the first and second mouse click chart coordinate
        if (firstClickLocation.Price == 0)
        {
            firstClickLocation = new ChartPoint(
                arg.bar_number,
                Bars.FullSymbolData.Close[barsAgo]
                );
        }
        else if (secondClickLocation.Price == 0)
        {
            secondClickLocation = new ChartPoint(
                arg.bar_number,
                Bars.FullSymbolData.Close[barsAgo]
                );

            // Call DrawBox() to create the box
            DrawBox(firstClickLocation, secondClickLocation);

            // Reset the chart locations for the next box to draw
            secondClickLocation = new ChartPoint();
            firstClickLocation  = new ChartPoint();
        }
    }

    private void DrawBox(ChartPoint locationOne, ChartPoint locationTwo)
    {
        ITrendLineObject leftSideLine, rightSideLine, highLine, lowLine;

        // Return if the second click happened before the first click
        if (locationOne.BarNumber > locationTwo.BarNumber)
            return;

        // Determine highest high and low in the box
        double highestHigh = 0;
        double lowestLow   = 99999;

        for (int i = 0; i <= (locationTwo.BarNumber - locationOne.BarNumber); 
            i++)
        {
            int barsBack = (Bars.FullSymbolData.Current - 
                (int)locationOne.BarNumber) - i;

            highestHigh = Math.Max(highestHigh, 
                Bars.FullSymbolData.High[barsBack]);
            lowestLow   = Math.Min(lowestLow, 
                Bars.FullSymbolData.Low[barsBack]);
        }

        // Determine the DateTime of the box
        DateTime leftSideBox = Bars.FullSymbolData.Time[
            Bars.FullSymbolData.Current - (int)locationOne.BarNumber];

        DateTime rightSideBox = Bars.FullSymbolData.Time[
            Bars.FullSymbolData.Current - (int)locationTwo.BarNumber];

        // Draw the vertical trend lines
        leftSideLine = DrwTrendLine.Create(
            new ChartPoint(leftSideBox, highestHigh),
            new ChartPoint(leftSideBox, lowestLow)
            );

        rightSideLine = DrwTrendLine.Create(
            new ChartPoint(rightSideBox, highestHigh),
            new ChartPoint(rightSideBox, lowestLow)
            );

        // Draw the horizontal trend lines
        highLine = DrwTrendLine.Create(
            new ChartPoint(leftSideBox, highestHigh),
            new ChartPoint(rightSideBox, highestHigh)
            );

        lowLine = DrwTrendLine.Create(
            new ChartPoint(leftSideBox, lowestLow),
            new ChartPoint(rightSideBox, lowestLow)
            );

        // Adjust the trend lines
        leftSideLine.Size  = 2;
        rightSideLine.Size = 2;
        highLine.Size      = 2;
        lowLine.Size       = 2;

        leftSideLine.Style  = ETLStyle.ToolDashed;
        rightSideLine.Style = ETLStyle.ToolDashed;
        highLine.Style      = ETLStyle.ToolDashed;
        lowLine.Style       = ETLStyle.ToolDashed;

        // Determine the trend line's colour
        Color trendLinesColour = (locationOne.Price < locationTwo.Price) ?
            Color.LimeGreen : Color.Red;

        leftSideLine.Color  = trendLinesColour;
        rightSideLine.Color = trendLinesColour;
        highLine.Color      = trendLinesColour;
        lowLine.Color       = trendLinesColour;
    }
}

We first define three MultiCharts .NET class attributes. With SameAsSymbol set to true the indicator is displayed on the data series as opposed to a separate subchart. By setting RecoverDrawings to false intra-bar generated drawings aren’t removed automatically (MultiCharts, 2014). And MouseEvents set to true enables mouse click processing.

We then declare two ChartPoint variables: firstClickLocation and secondClickLocation. These will hold the mouse click location later on.

Processing mouse clicks with Control in MultiCharts .NET

The programming example consists out of two methods: OnMouseEvent() (which processes the mouse clicks on the chart; see PowerLanguage .NET Help, n.d.) and the custom DrawBox() method.

OnMouseEvent() begins with an if statement that checks whether the keyboard key pressed down during the click (returned by the arg.keys variable) is unequal to (!=) the Control key. For this latter we use the Keys.Control value from the Keys enumeration, which originates from the System.Windows.Form namespace. With a using statement we included that namespace in the top of the indicator’s source code (see full code below).

When that if statement’s condition evaluates to true, the return keyword is executed. What this keyword does is immediately exit the current method (Stellman & Greene, 2010), which in this case is the OnMouseEvent() method. This means the remainder of this method is only executed when a mouse click with Control happens; otherwise, return is executed.

Storing the mouse click’s location in ChartPoint variables

Then in OnMouseEvent() we create the barsAgo variable set to the difference between the current bar number (Bars.FullSymbolData.Current) and the one from the mouse click’s location (arg.bar_number). By computing this difference we know how many bars ago the click happened, which we then use to access that bar’s price data.

But first we use an if/else statement to determine if the current mouse click that’s processed in OnMouseEvent() should be stored in the firstClickLocation or secondClickLocation variable. For this we compare the Price property of these variables to see whether it equals (==) zero. When it does, we know that the variable hasn’t been assigned a chart coordinate yet (because Price would be anything but zero then).

So when firstClickLocation.Price equals zero, this variable is assigned a new ChartPoint struct set to the bar number of the region where the click happened (arg.bar_number) and that bar’s closing price. We retrieve this last value with Bars.FullSymbolData.Close and the barsAgo variable passed in between the square brackets ([ and ]). The benefit of using the Bars.FullSymbolData property is that any bar of the data series can be accessed (see PowerLanguage .NET Help, n.d.).

In the else if portion, that tests whether secondClickLocation.Price equals zero, we assign the secondClickLocation variable a new ChartPoint in the same way as with firstClickLocation. Because of the if/else statement, this second part is only executed when the first isn’t (so only when firstClickLocation already holds a valid chart coordinate).

Now that both variables hold a chart coordinate, we call the DrawBox() method to make the box and pass in firstClickLocation and secondClickLocation as arguments. Then we reset both variables to an empty ChartPoint so that they’re ready for the next box to draw.

Drawing a box with chart locations and their bar numbers

Next is the DrawBox() method that has two parameters of type ChartPoint: locationOne and locationTwo. When we called this method in the OnMouseEvent() method, we passed in the two ChartPoint variables that held the mouse click location (the click’s bar number and accompanying closing price). So in DrawBox(), locationOne is the first mouse click’s location and locationTwo the second.

We start DrawBox() with declaring four ITrendLineObject variables: leftSideLine, rightSideLine, highLine, and lowLine. We’ll store the trend line references in these variables later on so that the trend lines can be accessed through them.

An if statement then checks if the bar number of the first location (locationOne.BarNumber) is greater than the second location’s bar number (locationTwo.BarNumber). That happens when the box is drawn from right to left (instead of the regular left to right), which affects the calculations in DrawBox(). Instead of expanding the example with more code to address this, we just execute return to prematurely exit DrawBox() and let the user try again.

Determining the highest and lowest price inside the box

We then declare two double variables (highestHigh and lowestLow) that are initialised to 0 and 99999, respectively. These variables are used in the subsequent for loop to keep track of the highest and lowest price occurring inside the box.

That for loop begins at 0 and continues as long as the i loop variable is less than or equal to (<=) the difference between the bar numbers of both chart locations (locationTwo.BarNumber minus locationOne.BarNumber). After each loop cycle, the i variable is incremented with one by the postfix increment operator (++). That operator is the shorthand equivalent of i = i + 1 (Dorman, 2010).

Inside the loop, we declare the barsBack integer variable to hold, for each bar in the box, the number of bars back relative to the current bar that the script is calculated on. This variable’s value is calculated by subtracting the bar number of the box’s starting point (locationOne.BarNumber) from the current bar (Bars.FullSymbolData.Current), followed by subtracting the i loop variable. This latter ensures the correct number of bars back for each bar inside the box. In this statement we need to explicitly cast locationOne.BarNumber to an integer ((int)) since this variable is nullable while Bars.FullSymbolData.Current is not.

So with this calculation the barsBack variable starts with the number of bars back for the left side of the rectangle and continues down to the amount of bars back of the rectangle’s right side.

We also determine the highest high and lowest low of the bars that are looped over. For this the highestHigh and lowestLow variables are used together with the Math.Max() and Math.Min() methods, that both take two arguments and return the maximum and minimum value respectively (Albahari & Albahari, 2012). Both methods use the variable’s current value (highestHigh or lowestLow) and the high or low of the bar currently examined in the loop (Bars.FullSymbolData.High[barsBack] and Bars.FullSymbolData.Low[barsBack]). This way they keep a ‘running’ high and low of the prices inside the box.

Drawing the four trend lines to creating a box

After the for loop ends we need to determine the date and time values for the left and right side of the box. We do that by declaring two DateTime variables (leftSideBox and rightSideBox) and use the Bars.FullSymbolData.Time property to access the bar’s time of the box’s left or right side. For the left side, we subtract the bar number of the first location (locationOne.BarNumber) from the current bar number (Bars.FullSymbolData.Current). And for the box’s right side we subtract the second location’s bar number (locationTwo.BarNumber) from the current bar. This way we programmatically retrieve the DateTime values of both sides of the box.

At that point we have the four values needed to draw the box: highestHigh and lowestLow with the box’s price values while leftSideBox and rightSideBox contain the box’s DateTime coordinates.

We first draw the box’s left side by calling the DrwTrendLine.Create() method and passing in two ChartPoint structs: one set to the leftSideBox date and time value and the highestHigh price, the other set to the same DateTime value and the lowestLow price. The reference to this vertical line is then assigned to the leftSideLine variable. The box’s right side is made similarly, although here the date and time is set to rightSideBox and the value returned by DrwTrendLine.Create() is stored in the rightSideLine variable.

Then the rectangle’s top and bottom lines are drawn. The first horizontal line, highLine, has its first ChartPoint coordinate set to the leftSideBox DateTime value and the highestHigh price. Its second ChartPoint is set to the date and time of the box’s right side (rightSideBox) and the highestHigh price. The other horizontal line (lowLine) is set to the same date and time values, except that this line is set to the lowestLow price.

Adjusting the rectangle’s trend lines in MultiCharts .NET

Since each trend line is assigned to its own ITrendLineObject variable, we can access the trend line’s properties and methods through those variables. And so we change the lines’ visual appearance by setting their Size properties to a new value that makes them thicker and the Style property to make the lines dashed.

Then we determine the lines’ colour. We first declare a Color variable named trendLinesColour and assign it a value with the conditional operator (?:). This operator evaluates a condition and returns the first value when true or the second when false (Dorman, 2010).

In this case, the operator evaluates whether the first click’s price value (locationOne.Price) is less than (<) that of the second click (locationTwo.Price). That condition evaluates to true when the box’s left side has a lower price than its right side, signalling that prices have moved higher inside the box. And so then we assign a green colour (Color.LimeGreen) to the trendLineColour variable; otherwise, this variable is given the Color.Red colour. After determining the value of the trendLinesColour variable, the Color property of each line is set to this variable.

This article’s example can be expanded by locking the trend lines so that the rectangles cannot be accidentally moved. Other examples of using trend lines to highlight price action are drawing an opening range with trend lines, connecting swing points with trend lines, and drawing a triangle scaled to a price.

Summary

Drawing trend lines is done with DrwTrendLine.Create(). That method returns a reference that, when stored in an ITrendLineObject variable, can be used to access a line’s properties and methods. A rectangle is drawn by combining four trend lines based on as many coordinates. Each of those four chart locations is defined with a ChartPoint struct that contains that location’s price and DateTime value.

Complete MultiCharts .NET indicator example

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

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

        private ChartPoint firstClickLocation, secondClickLocation;

        protected override void CalcBar() { }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            if (arg.keys != Keys.Control)
                return;

            int barsAgo = Bars.FullSymbolData.Current - arg.bar_number;

            // Determine the first and second mouse click chart coordinate
            if (firstClickLocation.Price == 0)
            {
                firstClickLocation = new ChartPoint(
                    arg.bar_number,
                    Bars.FullSymbolData.Close[barsAgo]
                    );
            }
            else if (secondClickLocation.Price == 0)
            {
                secondClickLocation = new ChartPoint(
                    arg.bar_number,
                    Bars.FullSymbolData.Close[barsAgo]
                    );

                // Call DrawBox() to create the box
                DrawBox(firstClickLocation, secondClickLocation);

                // Reset the chart locations for the next box to draw
                secondClickLocation = new ChartPoint();
                firstClickLocation  = new ChartPoint();
            }
        }

        private void DrawBox(ChartPoint locationOne, ChartPoint locationTwo)
        {
            ITrendLineObject leftSideLine, rightSideLine, highLine, lowLine;

            // Return if the second click happened before the first click
            if (locationOne.BarNumber > locationTwo.BarNumber)
                return;

            // Determine highest high and low in the box
            double highestHigh = 0;
            double lowestLow   = 99999;

            for (int i = 0; i <= (locationTwo.BarNumber - locationOne.BarNumber); 
                i++)
            {
                int barsBack = (Bars.FullSymbolData.Current - 
                    (int)locationOne.BarNumber) - i;

                highestHigh = Math.Max(highestHigh, 
                    Bars.FullSymbolData.High[barsBack]);
                lowestLow   = Math.Min(lowestLow, 
                    Bars.FullSymbolData.Low[barsBack]);
            }

            // Determine the DateTime of the box
            DateTime leftSideBox = Bars.FullSymbolData.Time[
                Bars.FullSymbolData.Current - (int)locationOne.BarNumber];

            DateTime rightSideBox = Bars.FullSymbolData.Time[
                Bars.FullSymbolData.Current - (int)locationTwo.BarNumber];

            // Draw the vertical trend lines
            leftSideLine = DrwTrendLine.Create(
                new ChartPoint(leftSideBox, highestHigh),
                new ChartPoint(leftSideBox, lowestLow)
                );

            rightSideLine = DrwTrendLine.Create(
                new ChartPoint(rightSideBox, highestHigh),
                new ChartPoint(rightSideBox, lowestLow)
                );

            // Draw the horizontal trend lines
            highLine = DrwTrendLine.Create(
                new ChartPoint(leftSideBox, highestHigh),
                new ChartPoint(rightSideBox, highestHigh)
                );

            lowLine = DrwTrendLine.Create(
                new ChartPoint(leftSideBox, lowestLow),
                new ChartPoint(rightSideBox, lowestLow)
                );

            // Adjust the trend lines
            leftSideLine.Size  = 2;
            rightSideLine.Size = 2;
            highLine.Size      = 2;
            lowLine.Size       = 2;

            leftSideLine.Style  = ETLStyle.ToolDashed;
            rightSideLine.Style = ETLStyle.ToolDashed;
            highLine.Style      = ETLStyle.ToolDashed;
            lowLine.Style       = ETLStyle.ToolDashed;

            // Determine the trend line's colour
            Color trendLinesColour = (locationOne.Price < locationTwo.Price) ?
                Color.LimeGreen : Color.Red;

            leftSideLine.Color  = trendLinesColour;
            rightSideLine.Color = trendLinesColour;
            highLine.Color      = trendLinesColour;
            lowLine.Color       = trendLinesColour;
        }
    }
}

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.

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

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