Skip to content

Advanced Indicator Operations

Introduction

This article complements our extensive list of indicator code samples. We elaborate on some concepts mentioned in these code samples and discuss several advanced features you can implement when creating new indicators.

The 'Cloud' Attribute

You have probably seen transparent 'clouds' on trading charts.

Image title

You can add 'clouds' to your indicator outputs by using the CloudAttribute class attribute as shown in the below 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
    }
}

The CloudAttribute constructor takes two output line names. Afterward, it automatically plots the 'cloud' to match the space between these two outputs.

You can also set the opacity of the 'cloud' by using the Opacity property. To set up the 'cloud' color, use the FirstColor property. By default, the color of the 'cloud' will match the color of the first line in the indicator output.

Working with Colors

The Automate API includes the Color enum that can be used to configure the colors for Chart objects, chart controls, and outputs.

The Color class contains several constants representing commonly used colors. You can, therefore, use these colors without having to deal with hexadecimal or ARGB color codes.

When customizing outputs, you can set their colors by using the LineColor string property. As shown below, it accepts both named colors and hexadecimal color codes.

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; }

Note that colors can also be treated as customizable parameters. To allow users to select custom colors (e.g., the colors of the lines drawn by an indicator), declare a parameter of the Color type.

1
2
[Parameter("Drawing Color", DefaultValue = "#f54242")]
public Color DrawingColor { get; set; }

In the below example, we create a simple 'high minus low' indicator that, upon being initialized, displays text on the trading chart to which it is attached.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Collections;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class HighMinusLowColor: Indicator
    {
        [Parameter("Text Color", DefaultValue="#f54242")]
        public Color TextColor { get; set; }

        public override void Initialize()
        {
            var staticText = Chart.DrawStaticText("static", "This text shows how color parameters work", VerticalAlignment.Center, HorizontalAlignment.Center, TextColor);
        }
    }
}

Output Types

There are several different types of output available for custom indicators.

  • Line. A continuous line.
  • DiscontinuousLine. A non-continuous line that is useful for cases in which your indicator does not always have a calculation value.
  • Points. A point or a dot for each bar.
  • Histogram. A series of vertical bars. When using this type, please, set the IsOverlay property of your indicator to false.

To set an output type, use the PlotType property as demonstrated below.

 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

The enum type is needed to create parameters that have several predefined options from which users could choose. We use the enum type in the following 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 this parameter to an indicator/cBot, you will see a multi-option interactable menu allowing for choosing one of the specified enum values.

Working with Time

Platform/Server Time

You can get the current server time by accessing either the Server.Time or Server.TimeInUts properties.

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

The Server.Time property represents the current time in your indicator/cBot time zone established via the TimeZone property.

Bar Open Time

Use the Bars.OpenTime collection to get the bar open times. The time zone will be based on the time zone you have specified in the TimeZone class attribute.

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

Platform Time Offset

Use the Application.UserTimeOffset property to get the time zone of the user's platform. This is primarily used for converting your indicator time to the user's time zone.

1
var userPlatformTimeOffset = Application.UserTimeOffset;

The Application.UserTimeOffset property returns a TimeSpan object representing the time offset set by the user in their cTrader platform compared to UTC time.

You can also choose to get notified when a user changes their platform time offset. To do so, use the 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;
}

Getting Time with a Parameter

To get a certain time value via a customizable parameter, you have to use the string type and then parse this string to one of the following types.

  • DateTime
  • DataTimeOffset
  • TimeSpan
  • DateOnly
  • TimeOnly

This is just one example of how this can be done.

 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: January 30, 2023