Skip to content

Indicators

Note

For a more detailed understanding, we also provide specific API Reference

Introduction

Technical Indicators are used to aid in manual trading as well as be included in automated strategies by indicating trends and providing signals for potential changes in trend.

There have been a number of indicators developed over time by various statisticians and traders alike. Many of the most popular indicators are included in the platform as build in indicators.

Custom indicators maybe created using the cAlgo editor.

These indicators can be viewed in the cTrader as well as the cAlgo platforms. Moreover, you may download custom indicators build by other users from cTDN Indicators.

The New Indicator Template

Image title

Click on the Indicators tab then click the new button from the side panel.

The editor will be loaded with the template for a new indicator:

 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
// These namespaces are required for a cAlgo indicator. 
// They contain basic classes of the C# language, and the cAlgo API. 
// The last namespace is required for referencing build-in 
// indicators such as SimpleMovingAverage, BollingerBands, etc.

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

// Define indicators within the cAlgo.Indicators namespace
namespace cAlgo.Indicators
{
    // The Indicator class declaration must be preceded by the indicator attribute, 
    // [Indicator()], and it should be a public  
    // Indicator derived class

    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC)] 
    public class NewIndicator : Indicator
    {
        // The parameter attribute is necessary if the program will contain user input.           
       [Parameter(DefaultValue = 0.0)]
       public double Parameter { get; set; }

       // The Output attribute is used to display the Output of 
       // an Indicator series on the chart.
       [Output("Main")]
       public IndicatorDataSeries Result { get; set; }
       // The Initialize method is called upon loading of an indicator.  
       // It is typically used to initialize variables and series such as nested indicators.
        protected override void Initialize()
        {
            // Initialize and create nested indicators
        }

        // The calculate method cannot be ommited.  
        // It is the main method of an indicator. 
        // It is called on each historical bar defined by the index and in real time on each incoming tick.
        public override void Calculate(int index)
        {
            // Calculate value at specified index
            // Result[index] = ...
        }
   }
}

Initialize

The initialize method is used to initialize variables mainly such as indicators, data series, lists, etc.

In general, any variables that need to be calculated only once on start up should be in the initialize method in order not to consume unnecessary resources.

Calculate

The calculate method is the main method of the indicator.

It is called for each historic bar starting from the beginning of the series up to the current bar and then on each incoming tick.

In the case of multi-symbol / multi-timeframe implementation, it will be called on each tick of each symbol that is used in the indicator.

It is typically used for the calculation of an indicator based on a formula. In this case, it is the difference of the High and the Low prices of each bar.

The index parameter, represents each bar, ranging from 0 up to the current (last) bar.

So, at each bar the Calculate method will be called and the Result will be assigned with the difference between the High and the Low of that bar.

A Simple Indicator

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using cAlgo.API;

namespace cAlgo.Indicators
{
    [Indicator(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] = Bars.HighPrices[index] - Bars.LowPrices[index];
        }
    }
}

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

  • the indicators name: "High Minus Low"
  • if it will be overlayed on the chart or on a separate panel: IsOverlay = false
  • the scale precision on the chart: ScalePrecision = 5

See IndicatorAttribute for a complete list.

The Indicator Attribute is required even if the properties in parenthesis are omitted, e.g.: [Indicator()].

As mentioned in the previous section the indicator must be an Indicator derived class:

1
public class MyIndicator : 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 to specify characteristics such as:

  • the name given to the output: "Main"

Image title

  • the color: LineColor = "Orange"

See OutputAttribute for a 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 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:

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

To find the total number of elements in a series use the Count property: Bars.HighPrices.Count.

Image title

As you might have noticed, Bars.HighPrices and Bars.LowPrices 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 Bars interface contains the following series:

  • OpenPrices
  • HighPrices
  • LowPrices
  • ClosePrices
  • MedianPrices
  • TypicalPrices
  • WeightedPrices
  • TickVolumes
  • OpenTimes

And SymbolCode and TimeFrame of the relevant instance.

In other words, it will retrieve the above values of whichever symbol and time frame you choose when you create a new instance.

If you type MarketSeries followed by a period (dot), Intellisense will populate this list.

Indicators with Parameters

Most of the time indicators can vary according to user input. The Simple Moving Average is designed to accept as input parameters the price source and the period for the calculation.

Input must be preceded by the Parameter attribute like shown in the code below; the “Source” and the “Periods” properties are preceded by the Parameter attribute.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public class SimpleMovingAverage : Indicator
    {
        [Parameter]
        public DataSeries Source { get; set; }

        [Parameter(DefaultValue = 14)]
        public int Periods { 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;
        }
    }

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

1
[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
  • The name that is displayed in the input panel of an instance.

Image title

  • The default value of an input which is the initial value before it can be changed by the user.
  • The minimum and maximum values in case of numbers.

See ParameterAttribute for a complete list.

Currently, the supported types for input are:

  • DataSeries
  • int
  • double
  • string
  • bool
  • MovingAverageType
  • TimeFrame
  • Enums

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

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

The default for DataSeries type cannot be set, the Close will be the default value.

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

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

Declare a bool for a yes/no type input. The value “Yes” corresponds to “true” and “No” to “false”:

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

The above will display a “Yes/No” Input option.

If the input needs to be a multi option that user can select one of them then .NET Enums can be used:

1
2
3
4
5
6
7
8
9
public enum Option
{
    First,
    Second,
    Third
}

[Parameter("Option", DefaultValue = Option.Third)]
public Option SelectedOption { get; set; }

Nested Indicators

In certain occasions it is necessary to use nested indicators. That is the value of one indicator depends on the value of other indicators. Such is the case with the SampeDeMarker indicator found in the platform.

 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
public class SampleDeMarker : Indicator
{
    [Parameter(DefaultValue = 14)]
    public int Periods { get; set; }

    [Output("DMark", LineColor = "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 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.

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

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

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

The intellisense will populate the list of all build-in indicators once you type “Indicators” followed by a period (dot).

Image title

The intellisense also shows the list of the input parameters to the referenced indicator once the indicator has been selected from the list.

Referencing Custom Indicators

A custom indicator is any indicator you create in cAlgo. The samples in the platform are custom indicators.

In order to reference custom indicator you need

to press “Manage References” button on the top left corner of the text editor.

Image title

In Indicators page of Reference Manager you need to check indicators which you want to be referenced and press Apply.

Image title

Referenced custom indicators must be defined in the Initialize method just like nested build in indicators but the syntax is slightly different.

The name of the custom indicator must 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 in order they are defined.

1
sma = Indicators.GetIndicator(Source, SmaPeriod);

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    [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];
        }
    }

Lazy Loading

When you use referenced indicators the referenced indicators are lazy loaded, it means the referenced indicator data is not calculated until you start using it's data.

If you access a referenced indicator Outputs data then cTrader starts loading the indicator data by calling it's Calculate method on past and future bars, otherwise the referenced indicator will be in idle and it will not consume any system resources.

If your indicator doesn't has any Output and you tried to access any of it's public properties you will get that property default value, to solve this issue you have to call the referenced indicator Calculate method manually from your own indicator Calculate method.

Oscillators and the Levels Attribute

Indicators that oscillate around a constant value belong to the category of 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 that value is zero.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
namespace cAlgo.Indicators
{
    [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")]
        public IndicatorDataSeries Result { get; set; }

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

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 like this: [Indicator("MyOscillator", IsOverlay = true)] 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.The following are some examples:

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

IsLastBar and IsRealTime

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 NY and Tokyo while the indicator is based on UTC time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class TimeInDifferentParts : Indicator
    {
        public override void Calculate(int index)
        {
            if (IsRealTime) 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 ", VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime);
            Chart.DrawStaticText("NY", "NY " + nyTime, VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime);
            Chart.DrawStaticText("Tokyo", "Tokyo " + tokyoTime, VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime);
        }
    }

Combining two or more indicators in one

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

Example 1 - Aroon, RSI and Directional Movement System

The following indicator combines the Aroon, RSI and Directional Movement System 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
52
53
54
55
56
57
58
59
using cAlgo.API;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
    [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 Time Frames

Example 1- Using multiple time frames

The following example displays a moving average on different time frames.

 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
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
    [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 series5;
        private Bars series10;

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

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

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

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

            var index5 = series5.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);

            if (index5 != -1) MA5[index] = ma5.Result[index5];

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

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

Example 2- Using multiple time frames 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
58
59
60
61
62
63
64
65
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC)]
    public class MultiSymbolMA : Indicator
    {
        private MovingAverage ma1, ma2, ma3;
        private Bars 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 = Symbols.GetSymbol(Symbol2);
            symbol3 = Symbols.GetSymbol(Symbol3);

            series2 = MarketData.GetBars(TimeFrame, Symbol2);
            series3 = MarketData.GetBars(TimeFrame, Symbol3);

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

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

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

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

Cloud Attribute

Most probably you have seen transparent clouds on trading charts:

Image title

You can add clouds to your indicator outputs by using the CloudAttribute, you have to add this attribute to your indicator main class, example:

 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using cAlgo.API;
using cAlgo.API.Indicators;
using System;

namespace cAlgo
{
    /// <summary>
    /// This indicator shows how to make a built-in cTrader indicator multi time frame and how to use cloud attribute
    /// </summary>
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None), Cloud("Top", "Bottom", Opacity = 0.2)]
    public class BollingerBandsMTFCloudSample : Indicator
    {
        private BollingerBands _bollingerBands;

        private Bars _baseBars;

        [Parameter("Base TimeFrame", DefaultValue = "Daily")]
        public TimeFrame BaseTimeFrame { get; set; }

        [Parameter("Source", DefaultValue = DataSeriesType.Close)]
        public DataSeriesType DataSeriesType { get; set; }

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

        [Parameter("Standard Deviation", DefaultValue = 2, MinValue = 0)]
        public double StandardDeviation { get; set; }

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

        [Output("Main", LineColor = "Yellow", PlotType = PlotType.Line, Thickness = 1)]
        public IndicatorDataSeries Main { get; set; }

        [Output("Top", LineColor = "Red", PlotType = PlotType.Line, Thickness = 1)]
        public IndicatorDataSeries Top { get; set; }

        [Output("Bottom", LineColor = "Red", PlotType = PlotType.Line, Thickness = 1)]
        public IndicatorDataSeries Bottom { get; set; }

        protected override void Initialize()
        {
            _baseBars = MarketData.GetBars(BaseTimeFrame);

            var baseSeries = GetBaseSeries();

            _bollingerBands = Indicators.BollingerBands(baseSeries, Periods, StandardDeviation, MaType);
        }

        public override void Calculate(int index)
        {
            var baseIndex = _baseBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);

            Main[index] = _bollingerBands.Main[baseIndex];
            Top[index] = _bollingerBands.Top[baseIndex];
            Bottom[index] = _bollingerBands.Bottom[baseIndex];
        }

        private DataSeries GetBaseSeries()
        {
            switch (DataSeriesType)
            {
                case DataSeriesType.Open:
                    return _baseBars.OpenPrices;

                case DataSeriesType.High:
                    return _baseBars.HighPrices;

                case DataSeriesType.Low:
                    return _baseBars.LowPrices;

                case DataSeriesType.Close:
                    return _baseBars.ClosePrices;
                default:

                    throw new ArgumentOutOfRangeException("DataSeriesType");
            }
        }
    }

    public enum DataSeriesType
    {
        Open,
        High,
        Low,
        Close
    }
}

CloudAttribute constructor takes two output line names, and then it plots the cloud in between those outputs.

You can also set the opacity of cloud by using CloudAttribute Opacity property.

To set the cloud color you can use the FirstColor property of CloudAttribute, if you don't send the color then it will use the first output line color.

Working with Colors

In Automate API there is a Color class, that you can use to set colors for chart objects, chart controls, and outputs.

The Color class contains several static properties for some of the most used colors that you can use without dealing with hexadecimal or color codes.

But to use custom colors you can use either their hexadecimal or ARGB codes.

For outputs you can set their Color by using their LineColor string property, you can use both named colors or hexadecimal color codes for LineColor property.

Let's see some code examples.

1
2
3
4
_ = Chart.DrawStaticText("NamedColor", "This is text using Color class color name properties", VerticalAlignment.Center, HorizontalAlignment.Center, Color.Red);
_ = Chart.DrawStaticText("HexadecimalColor", "This is text using Hexadecimal color", VerticalAlignment.Bottom, HorizontalAlignment.Center, Color.FromHex("#FF5733"));
_ = Chart.DrawStaticText("ARGBColor", "This is text using ARGB color", VerticalAlignment.Top, HorizontalAlignment.Center, Color.FromArgb(255, 200, 100, 60));
_ = Chart.DrawStaticText("ParsedNameColor", "This is text using color name by parsing it from string", VerticalAlignment.Center, HorizontalAlignment.Left, Color.FromName("Yellow"));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var stackPanel = new StackPanel
{
    Orientation = Orientation.Vertical,
    HorizontalAlignment = HorizontalAlignment.Center,
    VerticalAlignment = VerticalAlignment.Center
};

stackPanel.AddChild(new TextBlock { Text = "Red Color property", BackgroundColor = Color.Red });
stackPanel.AddChild(new TextBlock { Text = "Hexadecimal Color code", BackgroundColor = Color.FromHex("#FF5733") });
stackPanel.AddChild(new TextBlock { Text = "ARGB Color", BackgroundColor = Color.FromArgb(200, 100, 40, 80) });
stackPanel.AddChild(new TextBlock { Text = "Color Name", BackgroundColor = Color.FromName("Green") });

Chart.AddControl(stackPanel);
1
2
3
4
5
[Output("Hexadecimal", LineColor = "#FF5733", PlotType = PlotType.Line, Thickness = 1)]
public IndicatorDataSeries Hexadecimal { get; set; }

[Output("Name", LineColor = "Red", PlotType = PlotType.Line, Thickness = 1)]
public IndicatorDataSeries Name { get; set; }

If you get the color as input parameter from user then you have to use a string parameter as cTrader doesn't added a color parameter type yet, and then convert the user provided value to Color.

Here is an example:

 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
using cAlgo.API;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ColorSample : Indicator
    {
        // User can use both color names or hexadecimal color codes
        [Parameter("Color", DefaultValue = "Blue")]
        public string ColorString { get; set; }

        [Parameter("Color Alpha", DefaultValue = 255, MinValue = 0, MaxValue = 255)]
        public int ColorAlpha { get; set; }

        private Color _color;

        protected override void Initialize()
        {
            _color = GetColor(ColorString, ColorAlpha);

            Chart.ColorSettings.BackgroundColor = _color;
        }

        public override void Calculate(int index)
        {
        }

        private Color GetColor(string colorString, int alpha = 255)
        {
            var color = colorString[0] == '#' ? Color.FromHex(colorString) : Color.FromName(colorString);

            return Color.FromArgb(alpha, color);
        }
    }
}

Output Types

An output can show it's data in different formats like:

  • Line: A continuous connected line
  • DiscontinuousLine: A line but not continuous, you can use it for outputs that doesn't have a value always
  • Points: A point or dot for each bar, you can use it for showing buy/sell signals
  • Histogram: A series of vertical bars, when using this type you shoud set your indicator IsOverlay property to False

To set an output type use the OutputAttribute PlotType property, example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Output("Line", PlotType = PlotType.Line)]
public IndicatorDataSeries Line { get; set; }

[Output("Discontinuous Line", PlotType = PlotType.DiscontinuousLine)]
public IndicatorDataSeries DiscontinuousLine { get; set; }

[Output("Points", PlotType = PlotType.Points)]
public IndicatorDataSeries Points { get; set; }

[Output("Histogram", PlotType = PlotType.Histogram)]
public IndicatorDataSeries Histogram { get; set; }

Enum Parameters

If you want to create a parameter that have several options and user can only select one of them then you should use Enum parameters.

Let's create an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public enum Options
{
    First,
    Second,
    Third,
    Fourth
}

[Parameter("Options", DefaultValue = Options.Third)]
public Options OptionsParameter { get; set; }

IF you add the above parameter on an indicator/cBot you will see a multi option parameter that will allow you to select one of your Options Enum values.

Working with Time

Platform/Server Time

You can get the current time of the server by using Server.Time or Server.TimeInUtc:

1
2
var currentServerTimeInIndicatorTimeZone = Server.Time;
var currentServerTimeInUtc = Server.TimeInUtc;

The Server.Time returns the current time in your indicator/cBot time zone that you set via TimeZone property of IndicatorAttribute or RobotAttribute.

Bar Open Time

To get a bar open time you can use the Bars.OpenTimes collection, the time zone will be based on your indicator/cBot time zone:

1
2
3
4
public override void Calculate(int index)
{
    var barTime = Bars.OpenTimes[index];
}

Platform Time Offset

To get the user platform time zone you can use the Application.UserTimeOffset:

1
var userPlatformTimeOffset = Application.UserTimeOffset;

Application.UserTimeOffset returns a TimeSpan which is the time offset user set on his cTrader platform, it's the different in time from UTC time.

To get notified when user changes his platform time offset you can use Application.UserTimeOffsetChanged event:

1
2
3
4
5
6
7
8
9
protected override void Initialize()
{
    Application.UserTimeOffsetChanged += Application_UserTimeOffsetChanged;
}

private void Application_UserTimeOffsetChanged(UserTimeOffsetChangedEventArgs obj)
{
    var platformTimeOffset = obj.UserTimeOffset;
}

You can use the user platform time offset for converting your indicator time to user time zone.

Getting Time with a Parameter

To get a time value in parameter you have to use string, and then parse it to DateTime, DateTimeOffset, TimeSpan, DateOnly, or TimeOnly types.

Here is an example:

 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using cAlgo.API;
using System;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class Sample : Indicator
    {
        [Parameter("TimeSpan", DefaultValue = "01:40:00")]
        public string TimeSpanString { get; set; }

        [Parameter("DateTime", DefaultValue = "01/01/2000 00:00:00")]
        public string DateTimeString { get; set; }

        [Parameter("DateTimeOffset", DefaultValue = "01/01/2000 00:00:00 +00:00")]
        public string DateTimeOffsetString { get; set; }

        [Parameter("DateOnly", DefaultValue = "01/01/2000")]
        public string DateOnlyString { get; set; }

        [Parameter("TimeOnly", DefaultValue = "13:36:25")]
        public string TimeOnlyString { get; set; }

        protected override void Initialize()
        {
            if (TimeSpan.TryParse(TimeSpanString, out var timeSpan) is false)
            {
                Print("Invalid TimeSpan");
            }
            else
            {
                Print(timeSpan);
            }

            if (DateTime.TryParse(DateTimeString, out var dateTime) is false)
            {
                Print("Invalid DateTime");
            }
            else
            {
                Print(dateTime);
            }

            if (DateTimeOffset.TryParse(DateTimeOffsetString, out var dateTimeOffset) is false)
            {
                Print("Invalid DateTimeOffset");
            }
            else
            {
                Print(dateTimeOffset);
            }

            if (DateOnly.TryParse(DateOnlyString, out var dateOnly) is false)
            {
                Print("Invalid DateOnly");
            }
            else
            {
                Print(dateOnly);
            }

            if (TimeOnly.TryParse(TimeOnlyString, out var timeOnly) is false)
            {
                Print("Invalid TimeOnly");
            }
            else
            {
                Print(timeOnly);
            }
        }

        public override void Calculate(int index)
        {
        }
    }
}

Last update: July 26, 2022

Comments