Indicator Code Samples

Simple Indicators

The High Minus Low Indicator calculates the difference between the current bar’s High and Low prices and displays it in an output series, which is drawn on the chart as a line connecting the values at each bar.

[Indicator("High Minus Low", IsOverlay = false, ScalePrecision = 5)]
    public class HighMinusLow : Indicator
    {
        [Output("Main", LineColor = "Orange")]
        public IndicatorDataSeries Result { get; set; }

        public override void Calculate(int index)
        {
            Result[index] = MarketSeries.High[index] - MarketSeries.Low[index];
        }
    }
}

The Indicator Attribute is applied to the class to indicate certain characteristics such as:

  • the indicators name (High Minus Low in the present example);
  • if it will be overlayed on the chart or on a separate panel (IsOverlay = true/false);
  • the scale precision on the chart (ScalePrecision = 5 in the present example).

Note. Check the Indicator Attribute section for the complete list.

The IndicatorAttribute is required even if the properties in parenthesis are omitted (for example [Indicator()]). As mentioned in the previous section the indicator must be an Indicator derived class: public class HighMinusLow : Indicator

The Output Attribute is applied to mark an IndicatorDataSeries as output to the chart. The property that is marked with the output attribute should be public so that it can be referenced from other classes. As with the Indicator attribute, the Output attribute allows specifying characteristics such as:

  • the name given to the output (Main in the present example);
  • the color (LineColor = "Orange" in the present example).

Note. Check the OutputAttribute section for the complete list.

The name will be displayed in the small panel that is displayed when you add an instance. From this panel, you can change the other characteristics of the output, such as the color, thickness, and line type.

IndicatorDataSeries is a list of doubles that can be indexed like an array. Hence, the value at each [index] in the list Result is assigned in the Calculate method as follows:

public override void Calculate(int index)
{
       Result[index] = MarketSeries.High[index] - MarketSeries.Low[index];
}

To find the total number of elements in a series use the Count property as follows: MarketSeries.High.Count. If you type MarketSeries.High in the editor followed by a period (dot), the intellisense will populate the list.

As you might have noticed, MarketSeries.High[index] and MarketSeries.Low[index] are also indexed in the same fashion. They are of type DataSeries which is also a list of doubles but this type is read-only (values can be retrieved but not assigned).

The MarketSeries interface contains the following series: Open, High, Low, Close, Median, Typical and Weighted price series, as well as TickVolume, OpenTime, SymbolCode, and TimeFrame of the relevant instance. In other words, it will retrieve the above values of whichever symbol and timeframe you choose when you create a new instance. If you type MarketSeries in the editor followed by a period (dot), the intellisense will populate the list.

Indicators with Parameters

In most cases, the indicators can vary according to the user input. The Simple Moving Average is designed to accept the price source and the period for the calculation as the input parameters. Input must be preceded by the Parameter attribute as it is shown in the code below - the Source and the Periods properties are preceded by the [Parameter()] attribute.

[Indicator("Simple Moving Average", IsOverlay = false, ScalePrecision = 5)]
    public class SimpleMovingAverage : Indicator
    {
        [Parameter("Price")]
        public DataSeries Source { get; set; }

        [Parameter("MA Type", DefaultValue = MovingAverageType.Exponential)]
        public MovingAverageType MaType { get; set; }

        [Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
        public int Periods { get; set; }

        [Parameter("Message", DefaultValue = true)]
        public bool DisplayChartMessage { get; set; }

        [Output("Main", Color = Colors.Turquoise)]
        public IndicatorDataSeries Result { get; set; }

        public override void Calculate(int index)
        {
            double sum = 0;

            for (var i = index - Periods + 1; i <= index; i++)
            {
                sum += Source[i];
            }
            Result[index] = sum / Periods;
        }
    }
}

The Indicator instance panel with the parameters as set above will look as follows:

Like the Indicator and Output attributes discussed earlier, the [Parameter()] attribute can define certain characteristics to the input:

[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
  • The name that is displayed in the input panel of an instance (MA Periods).
  • The default value of an input which is the initial value before it can be changed by the user (DefaultValue = 14).
  • The minimum and maximum values in case of numbers (MinValue = 1, MaxValue = 20).

Note. Check the ParameterAttribute section for the complete list.

Currently, the supported types of input are DataSeries, int, double, string, bool, MovingAverageType, and TimeFrame.

If the input is a price source such as Open, High, Low or Close, then make the property a DataSeries type:

[Parameter("Price")]
public DataSeries Source { get; set; }

The default value for the DataSeries type is Closed and it cannot be set.

If the input needs to be a number, then int (integer) or double (decimal) can be used:

[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
public int Periods { get; set; }

Declare a bool for a yes/no type input. The values “Yes” and “No” correspond to “true” or “false” respectively:

[Parameter("Message", DefaultValue = true)]
public bool DisplayChartMessage { get; set; }

Nested Indicators

In certain occasions, it is necessary to use Nested indicators. The Nested indicator is an indicator which value depends on the value of other indicators. Such is the case with the Sample DeMarker indicator which is located in the Indicators tab by default.

[Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SampleDeMarker : Indicator
    {
        [Parameter(DefaultValue = 14)]
        public int Periods { get; set; }

        [Output("DMark", Color = Colors.Turquoise)]
        public IndicatorDataSeries DMark { get; set; }

        private IndicatorDataSeries deMin;
        private IndicatorDataSeries deMax;
        private MovingAverage deMinMA;
        private MovingAverage deMaxMA;

        protected override void Initialize()
        {
            deMin = CreateDataSeries();
            deMax = CreateDataSeries();
            deMinMA = Indicators.MovingAverage(deMin, Periods, MovingAverageType.Simple);
            deMaxMA = Indicators.MovingAverage(deMax, Periods, MovingAverageType.Simple);
        }

        public override void Calculate(int index)
        {
            deMin[index] = Math.Max(MarketSeries.Low[index - 1] - MarketSeries.Low[index], 0);
            deMax[index] = Math.Max(MarketSeries.High[index] - MarketSeries.High[index - 1], 0);

            var min = deMinMA.Result[index];
            var max = deMaxMA.Result[index];

            DMark[index] = max / (min + max);
        }
    }

In the example above deMinMA and deMaxMA are indicators which are used for the calculation of the DeMark indicator. Nested indicators need to be defined in the Initialize() method. For example, deMinMA is defined as the simple moving average of the series deMin.

deMinMA = Indicators.MovingAverage(deMin, Periods, MovingAverageType.Simple);

deMin in its turn is defined in the Calculate method as the max of the last two Low price values.

deMin[index] = Math.Max(MarketSeries.Low[index - 1] - MarketSeries.Low[index], 0);

The IntelliSense will populate the list of all built-in indicators once you type Indicators. (followed by a period (dot) in the editor. The IntelliSense also shows the list of the input parameters to the referenced indicator once the indicator has been selected from the list.

Custom Indicators

A custom indicator is any indicator you create in cTrader Automate. The Sample Indicators provided in the Indicators tab by default are custom indicators.

Follow these steps to reference a custom indicator:

  1. Click Manage References to the upper left of the code editor.

  1. In Indicators section of the Reference Manager pop-up check the required indicators and click Apply. Alternatively, use the advanced search to find the required indicator.

Referenced Custom Indicators are defined in the Initialize() method just like the nested built-in indicators, but the syntax is slightly different. The name of the custom indicator should be enclosed in angle brackets following the GetIndicator directive. The parameters of the referenced indicator follow enclosed in parenthesis just like with built-in indicators, as follows:

sma = Indicators.GetIndicator(Source, SmaPeriod);

Example:

[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class SampleReferenceSMA : Indicator
    {
        [Parameter("Source")]
        public DataSeries Source { get; set; }

        [Parameter(DefaultValue = 14)]
        public int SmaPeriod { get; set; }

        [Output("Referenced SMA Output")]
        public IndicatorDataSeries refSMA { get; set; }

        private SampleSMA sma;

        protected override void Initialize()
        {
            sma = Indicators.GetIndicator(Source, SmaPeriod);
        }

        public override void Calculate(int index)
        {
            refSMA[index] = sma.Result[index];
        }
    }

Oscillators and the Levels Attribute

The Indicators that oscillate around a constant value belong to the category of the Oscillators. When creating Oscillators it is useful to draw a horizontal or “level” line at that constant value around which the indicator oscillates. In many cases, this value is zero.

The momentum oscillator typically oscillates around 100, so we will add a level line at that value using the Levels attribute.

[Levels(100)]
    [Indicator("Momentum Oscillator")]
    public class MomentumOscillator : Indicator
    {
        [Parameter()]
        public DataSeries Source { get; set; }

        [Parameter(DefaultValue = 14, MinValue = 1)]
        public int Periods { get; set; }

        [Output("Main", LineColor = "Green")]
        public IndicatorDataSeries Result { get; set; }

        public override void Calculate(int index)
        {
            Result[index] = 100 * Source[index] / Source[index - Periods];
        }
    }
}

Note that the levels can only be used when the indicator is not overlayed on the chart. That is, the property IsOverlay is not set to true in the Indicator attribute as follows: [Indicator("Momentum Oscillator", IsOverlay = false)]. This will not work. IsOverlay is false by default so if it is omitted like in the following example, the indicator will be displayed below the chart and the Levels will be displayed properly.

If multiple lines are necessary, then add a comma-separated list of the values within the parenthesis. For example:

[Levels(0, 50, 100)]

or:

[Levels(50.5, 50.75)]

or: [Levels(0.001, 0.002)]

IsLastBar Property

In certain cases, we may need an indicator that requires to be calculated at the last bar. Then, the IsLastBar property can be checked, to determine if as the name suggests, the index parameter of the Calculate method, is that of the last bar.

The following indicator displays the last open time in New York and Tokyo while the indicator is based on UTC time.

[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class TimeInDifferentParts : Indicator
    {
        public override void Calculate(int index)
        {
            if (IsLastBar)
                DisplayTime(index);
        }

        protected void DisplayTime(int index)
        {
            DateTime nyDateTime = MarketSeries.OpenTime[index].AddHours(-5);
            DateTime tokyoDateTime = MarketSeries.OpenTime[index].AddHours(7);

            string nyTime = nyDateTime.ToShortTimeString();
            string tokyoTime = tokyoDateTime.ToShortTimeString();

            ChartObjects.DrawText("Title", "Last Bar OpenTime ", StaticPosition.TopLeft, Colors.Lime);
            ChartObjects.DrawText("NY", "NY " + nyTime, StaticPosition.TopLeft, Colors.Lime);
            ChartObjects.DrawText("Tokyo", "Tokyo " + tokyoTime, StaticPosition.TopLeft, Colors.Lime);
        }
    }
}

Combining Indicators

In case you want to have many indicators displayed on the same panel that are not overlayed on the chart you may combine them in one custom indicator.

The following indicator combines the Aroon, RSI and Directional Movement System Indicators in one.

[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, ScalePrecision = 5)]
    public class Aroon_RSI_DMS : Indicator
    {
        private Aroon aroon;
        private RelativeStrengthIndex rsi;
        private DirectionalMovementSystem dms;

        [Parameter()]
        public DataSeries Source { get; set; }

        [Parameter(DefaultValue = 14)]
        public int Periods { get; set; }

        [Output("Aroon Up", LineColor = "LightSkyBlue")]
        public IndicatorDataSeries AroonUp { get; set; }

        [Output("Aroon Down", LineColor = "Red")]
        public IndicatorDataSeries AroonDn { get; set; }

        [Output("Rsi", LineColor = "Green")]
        public IndicatorDataSeries Rsi { get; set; }

        [Output("DI Plus", LineColor = "DarkGreen")]
        public IndicatorDataSeries DmsDIPlus { get; set; }

        [Output("DI Minus", LineColor = "DarkRed")]
        public IndicatorDataSeries DmsDIMinus { get; set; }

        [Output("ADX", LineColor = "Blue")]
        public IndicatorDataSeries DmsADX { get; set; }

        protected override void Initialize()
        {
            // Initialize and create nested indicators
            aroon = Indicators.Aroon(Periods);
            rsi = Indicators.RelativeStrengthIndex(Source, Periods);
            dms = Indicators.DirectionalMovementSystem(Periods);
        }

        public override void Calculate(int index)
        {
            AroonUp[index] = aroon.Up[index];
            AroonDn[index] = aroon.Down[index];

            Rsi[index] = rsi.Result[index];

            DmsADX[index] = dms.ADX[index];
            DmsDIMinus[index] = dms.DIMinus[index];
            DmsDIPlus[index] = dms.DIPlus[index];
        }
    }

Multiple Timeframes

Example 1: Using multiple timeframes.

The following example displays a moving average on different timeframes.

[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class MultiTF_MA : Indicator
    {
        [Parameter(DefaultValue = 50)]
        public int Period { get; set; }

        [Output("MA", LineColor = "Yellow")]
        public IndicatorDataSeries MA { get; set; }

        [Output("MA5", LineColor = "Orange")]
        public IndicatorDataSeries MA5 { get; set; }

        [Output("MA10", LineColor = "Red")]
        public IndicatorDataSeries MA10 { get; set; }

        private MarketSeries series5;
        private MarketSeries series10;

        private MovingAverage ma;
        private MovingAverage ma5;
        private MovingAverage ma10;

        protected override void Initialize()
        {
            series5 = MarketData.GetSeries(TimeFrame.Minute5);
            series10 = MarketData.GetSeries(TimeFrame.Minute10);

            ma = Indicators.MovingAverage(MarketSeries.Close, Period, MovingAverageType.Triangular);
            ma5 = Indicators.MovingAverage(series5.Close, Period, MovingAverageType.Triangular);
            ma10 = Indicators.MovingAverage(series10.Close, Period, MovingAverageType.Triangular);
        }

        public override void Calculate(int index)
        {
            MA[index] = ma.Result[index];

            var index5 = series5.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);
            if (index5 != -1)
                MA5[index] = ma5.Result[index5];

            var index10 = series10.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);

            if (index10 != -1)
                MA10[index] = ma10.Result[index10];
        }
    }

Example 2: Using Multiple timeframes and Symbols.

[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class MultiSymbolMA : Indicator
    {
        private MovingAverage ma1, ma2, ma3;
        private MarketSeries series2, series3;
        private Symbol symbol2, symbol3;

        [Parameter(DefaultValue = "EURCHF")]
        public string Symbol2 { get; set; }

        [Parameter(DefaultValue = "EURCAD")]
        public string Symbol3 { get; set; }

        [Parameter(DefaultValue = 14)]
        public int Period { get; set; }

        [Parameter(DefaultValue = MovingAverageType.Simple)]
        public MovingAverageType MaType { get; set; }

        [Output("MA Symbol 1", LineColor = "Magenta")]
        public IndicatorDataSeries Result1 { get; set; }

        [Output("MA Symbol 2", LineColor = "Magenta")]
        public IndicatorDataSeries Result2 { get; set; }

        [Output("MA Symbol 3", LineColor = "Magenta")]
        public IndicatorDataSeries Result3 { get; set; }

        protected override void Initialize()
        {
            symbol2 = MarketData.GetSymbol(Symbol2);
            symbol3 = MarketData.GetSymbol(Symbol3);

            series2 = MarketData.GetSeries(symbol2, TimeFrame);
            series3 = MarketData.GetSeries(symbol3, TimeFrame);

            ma1 = Indicators.MovingAverage(MarketSeries.Close, Period, MaType);
            ma2 = Indicators.MovingAverage(series2.Close, Period, MaType);
            ma3 = Indicators.MovingAverage(series3.Close, Period, MaType);
        }

        public override void Calculate(int index)
        {
            ShowOutput(Symbol, Result1, ma1, MarketSeries, index);
            ShowOutput(symbol2, Result2, ma2, series2, index);
            ShowOutput(symbol3, Result3, ma3, series3, index);
        }

        private void ShowOutput(Symbol symbol, IndicatorDataSeries result, MovingAverage movingAverage, MarketSeries series, int index)
        {
            var index2 = series.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);
            result[index] = movingAverage.Result[index2];

            string text = string.Format("{0} {1}", symbol.Code, Math.Round(result[index], symbol.Digits));
            ChartObjects.DrawText(symbol.Code, text, index, result[index], VerticalAlignment.Top, HorizontalAlignment.Right, Colors.Yellow);
        }
    }