Skip to content

Creating an Indicator

Action Map

The following diagram showcases the action flow of creating and implementing a custom indicator.

graph TB
  A([Add a New Indicator]) ==> B([Edit the Indicator Code]);
  B ==> C([Save and Build Your Indicator]);
  C ==> D([Create and Customize an Indicator Instance]);

Add a New Indicator

Click on the 'New' button to the right of the search bar. The following window should open.

Type the indicator name and select between two options.

  • 'Blank'. If this option is chosen, the new indicator will contain only a basic template.
  • 'From the list'. If this option is chosen and an indicator is selected from the list below, the new indicator will contain the entire code of the chosen algo. The pre-made indicators in the list cover a wide range of technical analysis patterns and visual outputs.

Samples Repository

The code samples for the 'From the list' option are taken from the git@github.com:spotware/ctrader-automate-samples.git repository. To access it, click here.

Click on 'Create' to finish setting up the indicator.

Note

If you would like to create a similar algo but with a different name, you can always use the duplicate functionality.

Edit the Sample Code

Click on your new indicator to open the code editor window. If you have chosen 'Blank' during the previous step, it will contain the following 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
34
35
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(AccessRights = AccessRights.None)]
    public class NewIndicator : Indicator
    {
        [Parameter(DefaultValue = "Hello world!")]
        public string Message { get; set; }

        [Output("Main")]
        public IndicatorDataSeries Result { get; set; }

        protected override void Initialize()
        {
            // To learn more about cTrader Algo, visit our Help Center:
            // https://help.ctrader.com/ctrader-automate

            Print(Message);
        }

        public override void Calculate(int index)
        {
            // Calculate value at specified index
            // Result[index] = 
        }
    }
}

Note

If you have chosen 'From the list' during the previous step, your indicator will be ready for action immediately. It will contain output calculation logic and custom parameters.

As we have discussed previously, the indicator attribute (Indicator) along with its optional properties such as TimeZone and AccessRights, precedes the indicator class (NewIndicator) declaration.

The [Output("OutputName")] declaration defines what your indicator is supposed to display. After using it, make sure that the property you are declaring next is of the IndicatorDataSeries type. You can customiыe the default appearance of your indicator output by adding one more parameter to the [Output("OutputName")] declaration.

1
2
3
/* "Main" is the output name while "Orange"
is chosen as the line color. */
[Output("Main", LineColor = "Orange")]

The Initialize() method is called on when an indicator is launched. It is typically used to initialise variables and series such as nested indicators. In general, this method should contain all variables that have to be calculated on indicator start-up to avoid consuming unnecessary resources.

In turn, the Calculate() method is used to calculate a specific value for the index you would like to see displayed. It is automatically invoked on each historical bar; it is also invoked in real-time on each incoming tick. In the case of multi-symbol/multi-timeframe implementation, it will be invoked on each tick of each symbol that your indicator is tasked with analysing. Think of the Calculate() method as the place in your code where you have to implement the actual indicator formula. For example, when using the half trend indicator formula, you would use the Calculate() method to define moving average intersections.

These two methods form the 'bread and butter' of every indicator and cannot be omitted. Consult our references library to learn more about the classes, methods, and properties you can use when coding indicators. In addition, you can always choose to write custom methods as shown in our introduction to C# and .NET.

Given what you have just learned and the examples provided in other parts of this documentation, edit the code of your indicator to suit your requirements.

Image title

In the example above, the Calculate() method determines the difference between the high and low price of each bar. The bars themselves are accessed by using their index in the Bars collection.

Copy and paste the below code just below the namespace declaration to create the same indicator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
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];
  }
}

Save and Build Your Indicator

Save your code by clicking on the 'Save' button located to the upper-left of the code editor window. Alternatively, press Ctrl+S. You can also right-click anywhere in the code editor and choose 'Save' from the newly opened menu.

Image title

Similarly to cBots, indicators need to be built before being deployed. To build an indicator, click the 'Build' icon at the top of the code editor or in the cBot menu. The keyboard shortcut for this action is Ctrl+B.

If the build succeeds, you will be notified by a new message in the 'Build Result' viewer. If the build fails, this window will, instead, display a summary of all errors encountered during the build process. Clicking on an error description in the 'Build Result' viewer will show you the exact place in your code where this error occurs.

If there are changes to your code made since the last build, you should see a red '' sign next to the '**Build*' icon. In this case, build the indicator again before running it.

Create and Customise an Indicator Instance

If you have successfully completed all of the above actions, your indicator should be ready for deployment. To start using it, create a new instance by performing one of the following actions.

  • Click on the '+' icon to the right of the extension name. In the menu that appears, select a symbol that you would like to trade.

Image title

  • Click on the 'three dots' icon to the right of the cBot name and select 'Add Instance'. cTrader will automatically create a new instance trading the EURUSD symbol using h1 as the timeframe.

You should see a new instance located directly below your indicator.

Image title

Note

The 'Lines' parameter is universal to all indicators, even the ones that do not explicitly declare it in the indicator code.

NaN Arithmetics

Indicators perform calculations to output certain visuals. As a result, it is essential that your code accurately handles fringe cases and boundary conditions for output calculations.

Consider the following code excerpt taken from an indicator outputting a detrended price oscillator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private MovingAverage movingAverage;

protected override void Initialize()
{
    movingAverage = Indicators.SimpleMovingAverage(Source, Periods);
}

public override void Calculate(int index)
{
    Result[index] = Source[index] - movingAverage.Result[index - Periods / 2 - 1];
}

The above code contains some general rules for calculation but it notably omits any boundary conditions.

For example, if Periods has the value of 10 and index has its value in the 0-9 range, our calculations will result in NaN (Not a Number). Our indicator simply cannot calculate the required value for a bar chart with a negative index. To address this problem, we could always add the following condition.

1
2
3
4
5
6
7
public override void Calculate(int index)
{
    if (index >= Periods + Periods / 2 + 1)
    {
        Result[index] = Source[index] - movingAverage.Result[index - Periods / 2 - 1];
    }
}

The code of the simple moving average also has to check that index >= Periods. Moreover, if we use a nested indicator (e.g., another type of moving average), this condition must be different to account for all new calculations.

While you can specify boundary conditions and explore fringe cases, C# provides an easy-to-use means of avoiding this problem entirely.

Specifically, the double data type fully supports NaN (Not a Number) arithmetics. We can demonstrate this using the example below.

1
double.NaN + 1.4 == double.NaN

In other words, when you perform calculations in which at least one variable is of the NaN type, the result of these calculations will always be NaN.

Fortunately, all custom cTrader indicators operate on data of the DataSeries type. When you request a value of an element of a â„–DataSeries list with a negative index, you will always receive NaN, and the value itself will simply not be drawn on the trading chart.

The indicator will continue operating as normal without throwing an exception. This means that, in most cases, you can safely ignore boundary conditions and simply implement the general calculation rules.

Recursive Indicators

While NaN arithmetics simplify work with boundary conditions, there are still situations in which NaN values have to be handled explicitly. Recursive indicators constitute one such case.

When calculating a value, a recursive indicator bases its calculations on the value calculated for the previous period. Consider the following example of an exponential moving average calculation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public override void Calculate(int index)
{
    var previousValue = Result[index - 1];

    if (double.IsNaN(previousValue))
    {
        Result[index] = Source[index];
    }
    else
    {
        Result[index] = Source[index] * _exp + previousValue * (1 - _exp);
    }
}

If our indicator calculates a value for the first period, we have to set it explicitly to a non-NaN type. Otherwise, all our calculations for all future periods will result in NaN, and the indicator will be useless.

In the code above, this is achieved by using the double.IsNaN(previousValue) method. Please, note that we cannot use the == operator (previousValue == double.IsNan) due to the fact that a NaN == x expression will always return false even if x is NaN.