The Command Line can change a chart’s symbol and this feature can be expanded on by cycling through a list of chart symbols. But what if you want to cycle through symbol pairs that require changing both data series?

Changing symbols in pairs with the Command Line

The .csy command changes a symbol on the chart (MultiCharts Wiki, 2014) and is executed programmatically with the ChartCommands.CommandLine() method (PowerLanguage .NET Help, n.d.).

A convenient way to cycle through pairs of symbols would be by relating the first to the second symbol. Such a thing can be done with a dictionary, which is a collection that associates a key with a value (Liberty & MacDonald, 2009), just like a regular dictionary does. These dictionary keys need to be unique while the same values can appear for any number of times (Stellman & Greene, 2010).

Creating a C# dictionary and outputting MultiCharts .NET symbol information

First, we need to make a dictionary like this:

private Dictionary<String, String> symbolPairs = new Dictionary<String, String>()
{
    { ".csy dnum=1, name=EUR/AUD, df=LMAX", 
        ".csy dnum=2, name=AUD/USD, df=LMAX" },
    { ".csy dnum=1, name=EX#, df=IQFeed", 
        ".csy dnum=2, name=@ES#, df=IQFeed" },
    { ".csy dnum=1, name=EUR/USD, df=MB Trading", 
        ".csy dnum=2, name=@EU#, df=IQFeed" },
};

protected override void StartCalc()
{
    Output.WriteLine("Symbols: #1 = {0} ({1}), #2 = {2} ({3})",
        BarsOfData(1).Info.Name,
        BarsOfData(1).Info.DataFeed,
        BarsOfData(2).Info.Name,
        BarsOfData(2).Info.DataFeed);
}

//> Symbols: #1 = EUR/USD (MB Trading), #2 = @EU# (IQFeed)
//> Symbols: #1 = EUR/AUD (LMAX), #2 = AUD/USD (LMAX)
//> Symbols: #1 = EX# (IQFeed), #2 = @ES# (IQFeed)
//> Symbols: #1 = EUR/USD (MB Trading), #2 = @EU# (IQFeed)
//> Symbols: #1 = EUR/AUD (LMAX), #2 = AUD/USD (LMAX)

Here a Dictionary<String, String> named symbolPairs is created and populated with a collection initialiser. Each of the three lines between braces { and } contain two strings, one for the key and the other for the value.

Those strings contain the .csy command to either change the first or second data series to another symbol. For understanding these commands, see changing the symbol through the Command Line.

In the StartCalc() method the symbol (Info.Name) and data feed (Info.DataFeed) are outputted to the Output Window. This type of price data information can be accessed through the BarsOfData() method for any data series (MultiCharts, 2013).

Switching to a symbol pair in MultiCharts .NET

Next is the OnMouseEvent() method for the processing of mouse clicks:

protected override void OnMouseEvent(MouseClickArgs arg)
{
    if (arg.buttons == MouseButtons.Left && arg.keys == Keys.Control)
    {
        for (int i = 0; i < symbolPairs.Keys.Count; i++)
        {
            if (symbolPairs.ElementAt(i).Key.Contains(Bars.Info.Name))
            {
                if (i == symbolPairs.Count - 1)
                {
                    ChartCommands.CommandLine(symbolPairs.ElementAt(0).Key);
                    ChartCommands.CommandLine(symbolPairs.ElementAt(0).Value);
                }
                else
                {
                    ChartCommands.CommandLine(symbolPairs.ElementAt(i+1).Key);
                    ChartCommands.CommandLine(symbolPairs.ElementAt(i+1).Value);
                }
            }
        }
    }
}

This method begins with an if statement that evaluates whether there was a left mouse click on the chart with Control pressed down. When that is the case, a for loop begins that iterates over the symbolPairs dictionary.

Inside this loop, the first if statement checks if the current dictionary key (symbolPairs.ElementAt(i).Key) contains the current chart’s primary symbol (Contains(Bars.Info.Name)). This way we can determine the location of the current chart symbol in the dictionary, which is needed to determine the next symbol.

Tip: The ElementAt() method returns the dictionary element at the specified index (Microsoft Developer Network, n.d.). But since dictionaries don’t maintain sorting when elements are added or removed (Dorman, 2010), this method gives unpredictable results when the dictionary’s contents are changed after initialisation. Since we don’t change the dictionary here after creating it, ElementAt() can be used.

Determining the MultiCharts .NET symbol pair to switch to

When that first if statement in the loop evaluates to true, an if-else statement determines whether the current element (symbolPairs.ElementAt(i)) is the last one in the dictionary or if the next should be selected.

The first case happens when the loop iterator variable (i) is equal to the dictionary length (symbolPairs.Count - 1). Because this variable is zero-based while the Count property is not, one is subtracted from the property. When that expression evaluates to true, ChartCommands.CommandLine() is executed twice, once with the Key value of the first dictionary element (symbolPairs.ElementAt(0)) and once with the accompanying Value. That executes the .csy commands stored in those dictionary values.

In the second case (the else part), the element is not the last in the dictionary. Then the Key and Value strings (that contain the .csy commands) from the next dictionary element are retrieved with ElementAt(i+1) and executed by ChartCommands.CommandLine().

Summary

The .csy Command Line command, executable through ChartCommands.CommandLine(), can change any symbol on the chart. With a dictionary we can form symbol pairs and these can be conveniently cycled through by processing mouse clicks on the chart.

Complete code of the MultiCharts .NET indicator

using System;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using System.Collections.Generic;       // For the Dictionary
using System.Windows.Forms;             // For Keys and MouseButtons enumerations

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

        private Dictionary<String, String> symbolPairs = new Dictionary<String, String>()
        {
            { ".csy dnum=1, name=EUR/AUD, df=LMAX", 
                ".csy dnum=2, name=AUD/USD, df=LMAX" },
            { ".csy dnum=1, name=EX#, df=IQFeed", 
                ".csy dnum=2, name=@ES#, df=IQFeed" },
            { ".csy dnum=1, name=EUR/USD, df=MB Trading", 
                ".csy dnum=2, name=@EU#, df=IQFeed" },
        };

        protected override void StartCalc()
        {
            Output.WriteLine("Symbols: #1 = {0} ({1}), #2 = {2} ({3})",
                BarsOfData(1).Info.Name,
                BarsOfData(1).Info.DataFeed,
                BarsOfData(2).Info.Name,
                BarsOfData(2).Info.DataFeed);
        }

        protected override void CalcBar() { }

        protected override void OnMouseEvent(MouseClickArgs arg)
        {
            if (arg.buttons == MouseButtons.Left && arg.keys == Keys.Control)
            {
                for (int i = 0; i < symbolPairs.Keys.Count; i++)
                {
                    if (symbolPairs.ElementAt(i).Key.Contains(Bars.Info.Name))
                    {
                        if (i == symbolPairs.Count - 1)
                        {
                            ChartCommands.CommandLine(symbolPairs.ElementAt(0).Key);
                            ChartCommands.CommandLine(symbolPairs.ElementAt(0).Value);
                        }
                        else
                        {
                            ChartCommands.CommandLine(symbolPairs.ElementAt(i+1).Key);
                            ChartCommands.CommandLine(symbolPairs.ElementAt(i+1).Value);
                        }
                    }
                }
            }
        }

        //> Symbols: #1 = EUR/USD (MB Trading), #2 = @EU# (IQFeed)
        //> Symbols: #1 = EUR/AUD (LMAX), #2 = AUD/USD (LMAX)
        //> Symbols: #1 = EX# (IQFeed), #2 = @ES# (IQFeed)
        //> Symbols: #1 = EUR/USD (MB Trading), #2 = @EU# (IQFeed)
        //> Symbols: #1 = EUR/AUD (LMAX), #2 = AUD/USD (LMAX)
    }
}

References

Dorman, S. (2010). Sams Teach Yourself Visual C# 2010 in 24 Hours. Indianapolis, IN (USA): 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.). Enumerable.ElementAt<TSource> Method. Retrieved on December 9, 2014, from http://msdn.microsoft.com/en-us/library/bb299233%28v=vs.110%29.aspx

MultiCharts (2013). MultiCharts .NET Programming Guide (version 1.0). Retrieved from http://www.multicharts.com/downloads/MultiCharts.NET-ProgrammingGuide-v1.0.pdf

MultiCharts Wiki (2014, August 18). MultiCharts Work Area: Understanding Command Line. Retrieved on November 18, 2014, from http://www.multicharts.com/trading-software/index.php/MultiCharts_Work_Area#Understanding_Command_Line

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.