Tutorial: Moving Average

In this tutorial, we will look at an example implementation of the MovingAverage indicator.

To get acquainted with the implementation example, download the archive with the indicator example and open it in IDE of your choice, we suggest you use Cursor IDE.

For more details on how to open it in Cursor IDE, you can refer to this section.

You can download the archive of Moving Average from here

Indicator structure

A custom indicator is built by extending the IndicatorImplementation class, provided by the forex-tester-custom-indicator-api library. You can find this implementation in the source code of the Moving Average example described in the Setup and Installation section.

import { IndicatorImplementation } from "forex-tester-custom-indicator-api";

export default class MovingAverage extends IndicatorImplementation {
  // indicator logic
}

Indicator parameters

These parameters can be of different types, they are determined by the TOptValue class, and they will be displayed in the indicator addition/editing window

export default class MovingAverage extends IndicatorImplementation {
    // Declaring class-level fields
    public Period!: TOptValue_number;
    public Shift!: TOptValue_number;
    public MAtype!: TOptValue_number;
    public ApplyToPrice!: TOptValue_number;
    public VShift!: TOptValue_number;

    Init(): void {
        // Create parameters using factory method
        this.Period = this.api.createTOptValue_number(8);
        this.Shift = this.api.createTOptValue_number(0);
        this.MAtype = this.api.createTOptValue_number(E_MAType.SMA);
        this.ApplyToPrice = this.api.createTOptValue_number(TPriceType.CLOSE);
        this.VShift = this.api.createTOptValue_number(0);
    ...existing code...
    }

For this, they need to be registered in the Init function.


public Init(): void {
  ...existing code...
  // Register parameter this.Period so it's shown in the indicator settings
  this.api.RegOption(
    'Period',
    TOptionType.INTEGER,
    this.Period
  );
  // Setting the maximum avalable range that can be used for Period value
  this.api.SetOptionRange(
    'Period',
    1,
    Number.MAX_SAFE_INTEGER
  );
  // Register parameter this.Shift so it's shown in the indicator settings
  this.api.RegOption(
    'Shift',
    TOptionType.INTEGER,
    this.Shift
  );
  // Register parameter this.VShift so it's its shown in the indicator settings
  this.api.RegOption(
    'VShift',
    TOptionType.INTEGER,
    this.VShift
  );
  // Register the MA type so it has a drowdown in the indicator settings
  this.api.RegMATypeOption(
    this.MAtype,
    'MAtype'
  );
  // Register the price type so it has a dropdown in the indicator settings.
  this.api.RegApplyToPriceOption(
    this.ApplyToPrice,
    'ApplyToPrice'
  );
...existing code...
}

You can see the methods for registering parameters in the external parameters definition

Buffers setup

Buffers are used to store and display indicator values on the chart.

public SSMA!: TIndexBuffer
private SMA!: TIndexBuffer

They need to be declared with all class fields and initialized in the Init function

this.SMA = this.api.CreateIndexBuffer();
this.SSMA = this.api.CreateIndexBuffer();

After their creation, you need to tell how many buffers will be displayed on the chart and bind them by index, starting from 0 (the indices must be unique) In this case, there is one buffer

this.api.IndicatorBuffers(1);
this.api.SetIndexBuffer(0, this.SSMA);

Each registered buffer can be configured

this.api.SetIndexLabel(0, "MA");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000");
this.api.SetIndexDrawBegin(0, this.Period.value - 1 + this.Shift.value);

Other settings

Also, other methods for configuring the indicator are used in the Init function. To ensure the indicator recalculates on each tick, use the function RecalculateMeAlways. If this setting is not used, each buffer index will be calculated only once to save resources, but some indicators may be calculated inaccurately. If the calculations do not heavily load the processor, we recommend always using it.

Set the indicator name, which will be displayed in the indicator settings window and in the context menu: IndicatorShortName.

We want the Moving Average indicator to be displayed on the main chart, so we use SetOutputWindow.

Using this call, specify that values equal to 0 will not be drawn on the chart: SetEmptyValue.

Indicator's main function

The main function of the indicator is the Calculate function, where the indicator values are calculated on each tick.

public Calculate(index: number): void {
    // check if the index is in the valid range
    if (index + this.Period.value >= this.api.Bars()) {
        return
    }

    // calculate the SMA value
    const calculatedSMA = this.api.GetMA(
        index,
        0,
        this.Period.value,
        this.MAtype.value,
        this.ApplyToPrice.value,
        // here we get the value of the previous bar
        this.SMA.getValue(index + 1)
    )

    this.SMA.setValue(index, calculatedSMA)
    // set the value which is going to be displayed on the chart
    this.SSMA.setValue(index, calculatedSMA + this.VShift.value * this.api.Point())
}

Changing parameters

To add custom logic after changing the indicator parameters, we use the OnParamsChange method. In Moving average, it is applied to the horizontal shift of the indicator. This method is often used to work with custom objects or to shift the indicator.

public OnParamsChange(): void {
    this.api.SetBufferShift(0, this.Shift.value)
}

Last updated