Skip to content

Indicator Code Samples

This page contains code samples for indicators of varying complexity.

Samples Repository

More code samples are always available in the git@github.com:spotware/ctrader-automate-samples.git repository. To access it, click here.

Simple Indicators

The high minus low indicator calculates the difference between the high and low prices of the current bar and displays it in an output series. The series is drawn on the chart as a line connecting the resulting values in each bar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None, ScalePrecision = 5)]
    public class HighMinusLow : Indicator
    {
        [Output("Main", LineColor = "Orange")]
        public IndicatorDataSeries Result { get; set; }

        public override void Calculate(int index)
        {
            Result[index] = Bars.HighPrices[index] - Bars.LowPrices[index];
        }
    }

The [Indicator...] declaration includes several parameters which are defined as follows.

  • IsOverlay. A boolean that defines whether the line will be overlayed on the chart or displayed in a separate UI panel.
  • TimeZone. A TimeZones class field that specifies the timezone of the indicator data and the server time.
  • AccessRights. An 'AccessRights' class field that determines the access rights allocated to your indicator.
  • ScalePrecision. An int setting up the scale precision of the indicator output.

As stated previously, the Output attribute is declared to mark a property as an indicator output. This property should be public so that it can be referenced by other classes.

Indicator output should always be of the IndicatorDataSeries data type which is a list of doubles that can be indexed like an array. Therefore, the value at each [index] in the list Result can be assigned in the Calculate method as follows.

1
2
3
4
public override void Calculate(int index)
{
       Result[index] = Bars.HighPrices[index] - Bars.LowPrices[index];
}

Indicators with Parameters

In most cases, the output of indicators can vary depending on user input. The way customisable parameters are set up for indicators is similar to how this is done for cBots.

The below simple moving average indicator is designed to accept a price source and a period as customisable parameters. Such parameters (Source and Periods in this particular example) must be preceded by the Parameter attribute.

Similarly to the [Indicator()] and [Output()] attributes discussed earlier, the [Parameter()] attribute can define certain characteristics applied to the user input.

1
[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]

Above, we specify the following characteristics.

  • The name displayed to denote this parameter in the cTrader UI ("MA Periods").
  • The default value of the parameter; it can changed by the user when customizing an instance (DefaultValue = 14)
  • The minimum and maximum parameter values (MinValue = 1, MaxValue = 20)

In the following snippet, we show how customisable parameters can be integrated into indicator code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    [Indicator(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", LineColor = "Red")]
        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;
        }
    }

Nested Indicators

Nested indicators are defined as indicators the value of which depends on the results of calculations performed by other indicators. They are useful when writing certain types of extensions such as the DeMark 9 indicator. Consider the following sample code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    [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(Bars.LowPrices[index - 1] - Bars.LowPrices[index], 0);
            deMax[index] = Math.Max(Bars.HighPrices[index] - Bars.HighPrices[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 two variables used for calculating the value of our DeMarker 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.

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

deMin, in turn, is defined in the Calculate() method as the maximum of the last two low price values.

1
deMin[index] = Math.Max(Bars.LowPrices[index - 1] - Bars.LowPrices[index], 0);

To simplify working with nested indicators, IntelliSense automatically populates the list of all built-in indicators when you type Indicators followed by a dot into the code editor. It will also display all related input parameters once you select a certain indicator from this list.

Image title

Lazy Loading

cTrader Algo uses lazy loading when you use referenced indicators. The values supplied by the referenced indicator are not calculated until your code starts actively using them.

If you access the Outputs data of a referenced indicator, cTrader will start loading the indicator data by calling its Calculate() method on past and future bars. In any other case, the referenced indicator will remain idle and, therefore, will not consume any system resources.

This also means that if your indicator does not have any Output or if you have tried to access any of its public properties, you will get the default value of the property in question. To resolve this issue, call the Calculate() method of your custom indicator from the Calculate() method of the current indicator.

Oscillators and the 'Levels' Attribute

The term 'oscillators' encompasses all indicators that oscillate around a certain constant variable.

When creating oscillators, it is useful to first draw a horizontal or 'level' line at that constant value; the indicator will then oscillate around this line. In many cases, the constant value equals zero.

In the below example, we define a momentum oscillator. It typically oscillates around the value of 100. We add a level line at this value using the Levels attribute which is declared prior to the indicator attributes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    [Levels(100)]
    [Indicator()]
    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 attribute can only be used if the indicator is not overlayed on the chart, meaning that the IsOverlay property is not set to true. By default, the value of IsOverlay is false. If this attribute is omitted from your code, Levels should work properly.

If you need to establish multiple 'Level' lines, add a comma-separated list of values within the parenthesis as displayed below.

1
[Levels(0, 50, 100)] 
1
[Levels(50.5, 50.75)] 
1
[Levels(0.001, 0.002)] 

The 'IsLastBar' Property

In some cases, you may want to create an indicator that has to be calculated only for the last bar in the trading chart. Simplifying this, the IsLastBar property can be used to check whether the index parameter of the Calculate() method is that of the last bar.

The below indicator is based on UTC time; however, it can display the last open time in New York and Tokyo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    [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 = Bars.OpenTimes[index].AddHours(-5);
            DateTime tokyoDateTime = Bars.OpenTimes[index].AddHours(7);

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

            Chart.DrawStaticText("Title", "Last Bar OpenTime \n NY " + nyTime + "\n" + "Tokyo " + tokyoTime, VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime);
        }
    }

Combining Indicators

cTrader allows for combining multiple indicators in the same panel or within the same chart.

The following indicator combines the Aroon, RSI, and directional movement system indicators in one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    [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

  • Using multiple timeframes

The following example displays a moving average indicator on different timeframes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    [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 Bars bars5;
        private Bars bars10;

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

        protected override void Initialize()
        {
            bars5 = MarketData.GetBars(TimeFrame.Minute5);
            bars10 = MarketData.GetBars(TimeFrame.Minute10);

            ma = Indicators.MovingAverage(Bars.ClosePrices, Period, MovingAverageType.Triangular);
            ma5 = Indicators.MovingAverage(bars5.ClosePrices, Period, MovingAverageType.Triangular);
            ma10 = Indicators.MovingAverage(bars10.ClosePrices, Period, MovingAverageType.Triangular);
        }

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

            var index5 = bars5.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
            if (index5 != -1)
                MA5[index] = ma5.Result[index5];

            var index10 = bars10.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);

            if (index10 != -1)
                MA10[index] = ma10.Result[index10];
        }
    }
  • Using multiple timeframes and symbols

The following example displays the moving average indicator on multiple timeframes and symbols.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class MultiSymbolMA : Indicator
    {
        private MovingAverage ma1, ma2, ma3;
        private Bars bars2, bars3;
        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 = Symbols.GetSymbol(Symbol2);
            symbol3 = Symbols.GetSymbol(Symbol3);

            bars2 = MarketData.GetBars(TimeFrame, symbol2.Name);
            bars3 = MarketData.GetBars(TimeFrame, symbol3.Name);

            ma1 = Indicators.MovingAverage(Bars.ClosePrices, Period, MaType);
            ma2 = Indicators.MovingAverage(bars2.ClosePrices, Period, MaType);
            ma3 = Indicators.MovingAverage(bars3.ClosePrices, Period, MaType);
        }

        public override void Calculate(int index)
        {
            ShowOutput(Symbol, Result1, ma1, Bars, index);
            ShowOutput(symbol2, Result2, ma2, bars2, index);
            ShowOutput(symbol3, Result3, ma3, bars3, index);
        }

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

            string text = string.Format("{0} {1}", symbol.Name, Math.Round(result[index], symbol.Digits));
            Chart.DrawStaticText(symbol.Name, text, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Yellow);
        }
    }