Перейти к содержанию

Примеры кода индикаторов

На этой странице представлено несколько примеров кода на Python и C# для создания технических индикаторов, используемых для ручной или алгоритмической торговли.

Репозитории с примерами индикаторов

Полные примеры кода индикаторов, включая готовые к запуску шаблоны для различных типов индикаторов и инструментов технического анализа, доступны в отдельных репозиториях Python и C# на GitHub.

Простые индикаторы

Индикатор разницы между максимумом и минимумом вычисляет разницу между максимальной и минимальной ценами текущего бара и отображает ее в выходном ряду. Ряд отображается на графике в виде линии, соединяющей полученные значения в каждом баре.

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

1
2
3
4
5
6
7
from System import Action

class HighMinusLow():
    def calculate(self, index):
        # In Python you have to declare parameters and outputs in your algo C# file like C# algos
        # So Result here is already declared in C# file of this indicator and here we can use it
        api.Result[index] = api.Bars.HighPrices[index] - api.Bars.LowPrices[index]

Объявление [Indicator...] включает несколько параметров, которые определяются следующим образом:

  • IsOverlay — логическое значение, определяющее, будет ли линия наложена на график или отображена на отдельной панели пользовательского интерфейса.
  • TimeZone — поле класса TimeZones, которое указывает часовой пояс данных индикатора и времени сервера.
  • AccessRights — поле класса AccessRights, которое определяет права доступа, выделенные вашему индикатору.
  • ScalePrecision — целое число, устанавливающее точность шкалы выходных данных индикатора.

Как вы узнали ранее при редактировании кода индикатора, атрибут Output объявляется для обозначения свойства как выходных данных индикатора. Это свойство должно быть public, чтобы на него могли ссылаться другие классы.

Выходные данные индикатора всегда должны иметь тип данных IndicatorDataSeries, который представляет собой список чисел типа double, индексируемый как массив. Таким образом, значение в каждом [index] в списке Result может быть назначено в методе Calculate следующим образом.

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

Индикаторы с параметрами

В большинстве случаев выходные данные индикаторов могут варьироваться в зависимости от пользовательского ввода. Способ настройки настраиваемых параметров для индикаторов аналогичен тому, как это делается для сиБотов.

Приведенный ниже индикатор простой скользящей средней предназначен для приема источника цены и периода в качестве настраиваемых параметров. Такие параметры (Source и Periods в данном конкретном примере) должны предваряться атрибутом Parameter.

Подобно атрибутам [Indicator()] и [Output()], рассмотренным ранее, атрибут [Parameter()] может определять определенные характеристики, применяемые к пользовательскому вводу.

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

Выше мы указываем следующие характеристики:

  • Имя, отображаемое для обозначения этого параметра в пользовательском интерфейсе cTrader ("MA Periods).
  • Значение параметра по умолчанию; оно может быть изменено пользователем при настройке экземпляра (DefaultValue = 14)
  • Минимальное и максимальное значения параметра (MinValue = 1, MaxValue = 20)

В следующем фрагменте мы показываем, как настраиваемые параметры могут быть интегрированы в код индикатора.

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

1
2
3
4
5
6
7
8
class SimpleMovingAverage():
    def calculate(self, index):
        sum = 0
        # Periods, Source and Result are declared in the C# file
        for i in range(index - api.Periods + 1, index):
            sum += api.Source[i]

        api.Result[index] = sum / api.Periods

Вложенные индикаторы

Вложенные индикаторы определяются как индикаторы, значение которых зависит от результатов вычислений, выполняемых другими индикаторами. Они полезны при написании определенных типов расширений, таких как индикатор DeMark 9. Рассмотрим следующий пример кода:

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class SampleDeMarker():
    def initialize(self):
        self.deMin = api.CreateDataSeries()
        self.deMax = api.CreateDataSeries()
        # Periods is declared in the C# file
        self.deMinMA = api.Indicators.MovingAverage(self.deMin, api.Periods, MovingAverageType.Simple)
        self.deMaxMA = api.Indicators.MovingAverage(self.deMax, api.Periods, MovingAverageType.Simple)

    def calculate(self, index):
        self.deMin[index] = max(api.Bars.LowPrices[index - 1] - api.Bars.LowPrices[index], 0)
        self.deMax[index] = max(api.Bars.HighPrices[index] - api.Bars.HighPrices[index - 1], 0)

        minValue = self.deMinMA.Result[index]
        maxValue = self.deMaxMA.Result[index]

        # DMark Output is declared in the C# file
        api.DMark[index] = maxValue / (minValue + maxValue)

В приведенном выше примере deMinMA и deMaxMA — это две переменные, используемые для вычисления значения нашего индикатора DeMarker.

Вложенные индикаторы должны быть определены в методе Initialize(). Например, deMinMA определяется как простая скользящая средняя ряда deMin.

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

deMin, в свою очередь, определяется в методе Calculate() как максимум из последних двух значений минимальной цены.

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

Чтобы упростить работу с вложенными индикаторами, IntelliSense автоматически заполняет список всех встроенных индикаторов, когда вы вводите Indicators с последующей точкой в редакторе кода. Он также отобразит все связанные входные параметры после того, как вы выберете определенный индикатор из этого списка.

Image title

Отложенная загрузка

cTrader Algo использует отложенную загрузку, когда вы используете ссылочные индикаторы. Значения, предоставляемые ссылочным индикатором, не вычисляются до тех пор, пока ваш код не начнет активно их использовать.

Если вы обращаетесь к данным Outputs ссылочного индикатора, cTrader начнет загружать данные индикатора, вызывая его метод Calculate() на прошлых и будущих барах. В любом другом случае ссылочный индикатор останется неактивным и, следовательно, не будет потреблять системные ресурсы.

Это также означает, что если ваш индикатор не имеет никаких Output или если вы попытаетесь получить доступ к любому из его публичных свойств, вы получите значение по умолчанию для данного свойства. Чтобы решить эту проблему, вызовите метод Calculate() вашего пользовательского индикатора из метода Calculate() текущего индикатора.

Осцилляторы и атрибут уровней

Термин «осцилляторы» охватывает все индикаторы, которые колеблются вокруг определенной постоянной переменной.

При создании осцилляторов полезно сначала нарисовать горизонтальную линию или линию «уровня» на этом постоянном значении; затем индикатор будет колебаться вокруг этой линии. Во многих случаях постоянное значение равно нулю.

В приведенном ниже примере мы определяем осциллятор моментума. Он обычно колеблется вокруг значения 100. Мы добавляем линию уровня на этом значении, используя атрибут Levels, который объявляется перед атрибутами индикатора.

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

1
2
3
4
class MomentumOscillator():
    def calculate(self, index):
        # Result, Source, and Periods are declared in the C# file
        api.Result[index] = 100 * api.Source[index] / api.Source[index - api.Periods]

Обратите внимание, что атрибут Levels может использоваться только в том случае, если индикатор не наложен на график, то есть свойство IsOverlay не установлено в true. По умолчанию значение IsOverlay равно false. Если этот атрибут опущен в вашем коде, Levels должен работать правильно.

Если вам нужно установить несколько линий «уровня», добавьте список значений, разделенных запятыми, в скобках, как показано ниже.

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

Уровни в индикаторах Python

В Python вы объявляете уровни в файле C# вашего индикатора так же, как и в индикаторах C#.

Свойство IsLastBar

В некоторых случаях вам может понадобиться создать индикатор, который должен рассчитываться только для последнего бара на торговом графике. Для упрощения можно использовать свойство IsLastBar, чтобы проверить, является ли параметр индекса метода Calculate() индексом последнего бара.

Приведенный ниже индикатор основан на времени UTC, однако он может отображать время последнего открытия в Нью-Йорке и Токио.

 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)
    {
        var nyDateTime = Bars.OpenTimes[index].AddHours(-5);
        var tokyoDateTime = Bars.OpenTimes[index].AddHours(7);

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

        Chart.DrawStaticText("Title", "Last Bar OpenTime \n NY " + nyTime + "\n" + "Tokyo " + tokyoTime, VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class TimeInDifferentParts():
    def calculate(self, index):
        if api.IsLastBar:
            self.display_time(index)

    def display_time(self, index):
        nyDateTime = api.Bars.OpenTimes[index].AddHours(-5)
        tokyoDateTime = api.Bars.OpenTimes[index].AddHours(7)

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

        api.Chart.DrawStaticText("Title", f"Last Bar OpenTime\nNY {nyTime}\nTokyo {tokyoTime}", VerticalAlignment.Top, HorizontalAlignment.Left, Color.Lime)

Комбинирование индикаторов

cTrader позволяет комбинировать несколько индикаторов на одной панели или в пределах одного графика.

Следующий индикатор объединяет индикаторы Aroon, RSI и Directional Movement System в одном.

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

Примечание

Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Aroon_RSI_DMS():
    def initialize(self):
        # Initialize and create nested indicators
        # Periods and Source parameters are declared in the C# file
        self.aroon = api.Indicators.Aroon(Periods)
        self.rsi = api.Indicators.RelativeStrengthIndex(Source, Periods)
        self.dms = api.Indicators.DirectionalMovementSystem(Periods)

    def calculate(self, index):
        # AroonUp, AroonDn, Rsi, DmsADX, DmsDIMinus, and DmsDIPlus
        # outputs are declared in the C# file
        api.AroonUp[index] = self.aroon.Up[index]
        api.AroonDn[index] = self.aroon.Down[index]

        api.Rsi[index] = self.rsi.Result[index]

        api.DmsADX[index] = self.dms.ADX[index]
        api.DmsDIMinus[index] = self.dms.DIMinus[index]
        api.DmsDIPlus[index] = self.dms.DIPlus[index]

Несколько таймфреймов

  • Использование нескольких таймфреймов

    Следующий пример отображает индикатор Moving Averages на разных таймфреймах.

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

    Примечание

    Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

     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
    class MultiTF_MA():
        def initialize(self):
            self.bars5 = api.MarketData.GetBars(TimeFrame.Minute5)
            self.bars10 = api.MarketData.GetBars(TimeFrame.Minute10)
    
            # Period is declared as a parameter in the C# file 
            # We use indicator time frame bars for this moving average
            self.ma = Indicators.MovingAverage(api.Bars.ClosePrices, api.Period, MovingAverageType.Triangular)
    
            # We use other two time frame bars that we created previously
            # for these two moving averages
            self.ma5 = Indicators.MovingAverage(self.bars5.ClosePrices, api.Period, MovingAverageType.Triangular)
            self.ma10 = Indicators.MovingAverage(self.bars10.ClosePrices, api.Period, MovingAverageType.Triangular)
    
        def calculate(self, index):
            # MA, MA5, and MA10 are outputs we declared in the C# file
            api.MA[index] = self.ma.Result[index]
    
            index5 = self.bars5.OpenTimes.GetIndexByTime(api.Bars.OpenTimes[index])
    
            if index5 > -1:
                api.MA5[index] = self.ma5.Result[index5]
    
            index10 = self.bars10.OpenTimes.GetIndexByTime(api.Bars.OpenTimes[index])
    
            if index10 > -1:
                api.MA10[index] = self.ma10.Result[index10]
    
  • Использование нескольких таймфреймов и инструментов

    Следующий пример отображает индикатор Moving Averages на нескольких таймфреймах и инструментах.

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

    Примечание

    Индикаторы Python используют настраиваемые параметры, объявленные в их файлах .cs.

     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
    class MultiSymbolMA():
        def initialize(self):
            # Symbol2 and Symbol3 are declared as parameters in the C# file
            self.symbol2 = api.Symbols.GetSymbol(api.Symbol2)
            self.symbol3 = api.Symbols.GetSymbol(api.Symbol3)
    
            self.bars2 = api.MarketData.GetBars(api.TimeFrame, self.symbol2.Name)
            self.bars3 = api.MarketData.GetBars(api.TimeFrame, self.symbol3.Name)
    
            self.ma1 = api.Indicators.MovingAverage(api.Bars.ClosePrices, api.Period, api.MaType)
            self.ma2 = api.Indicators.MovingAverage(self.bars2.ClosePrices, api.Period, api.MaType)
            self.ma3 = api.Indicators.MovingAverage(self.bars3.ClosePrices, api.Period, api.MaType)
    
        def calculate(self, index):
            # Result1, Result2, and Result3 are declared as outputs in the C# file
            self.show_output(api.Symbol, api.Result1, self.ma1, api.Bars, index)
            self.show_output(self.symbol2, api.Result2, self.ma2, self.bars2, index)
            self.show_output(self.symbol3, api.Result3, self.ma3, self.bars3, index)
    
        def show_output(self, symbol, result, movingAverage, bars, index):
            index2 = bars.OpenTimes.GetIndexByTime(api.Bars.OpenTimes[index])
    
            result[index] = movingAverage.Result[index2]
    
            text = f"{symbol.Name} {round(result[index], symbol.Digits)}"
    
            api.Chart.DrawStaticText(symbol.Name, text, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Yellow)
    

Image title