Начальная Глава

Пошаговая инструкция создания пользовательского индикатора

1.1 Создаём первоначальный шаблон

- Запускаем редактор mCode любым способом: в меню справа есть соответствующая кнопка,

 а в меню Индикаторы - соответствующий пункт.

- В открывшемся окне редактора нажимаем кнопку Создать Индикатор.

- В открывшемся окне диалога задаём имя файла, которое станет названием индикатора в списке пользовательских индикаторов торгового терминала.

Соответственно, имя индикатору даётся по-русски - если мы хотим видеть его в списке по-русски, и наоборот. Пробелы в имени файла редактор допускает, но их лучше избегать по соображениям совместимости операционных систем; вместо пробела лучше использовать символ подчеркивания - после перезапуска терминала эти символы в имени индикатора в списке пользовательских индикаторов будут заменены на пробелы и читаемость не пострадает.

Ввели имя файла (расширение указывать не нужно) - нажимаем кнопку Добавить. Имя файла появится в списке слева.

Слева от имени файла будет присутствовать символ - точка, пока серого цвета; он станет зеленым, когда мы активируем индикатор, но до этого созданный для нас шаблон нужно наполнить содержанием,  чтобы он стал действующими индикатором.

Стандартный шаблон, предлагаемый редактором mCode на момент написания данного текста, выглядит так:

export class Main extends Indicator {
    constructor() {
        super();
        this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
        this.buffer = this.addBuffer();
    }
    onInit() {       
    }
    async onUpdate() {
      }
}

Этот код пока не делает ничего полезного с пользовательской точки зрения. Нам предстоит сделать из него полезную программу.

1.2 Простейший индикатор

Начнём с простейшего варианта индикатора: нарисуем линию, соединяющую точки Close каждого бара, при этом не будем задавать пользователю никаких вопросов.

export class Main extends Indicator {
    constructor() {
        super();   
		this.buffers = {
            BufferForLine1 : this.addBuffer(),
        };
    } // конец метода constructor
	onInit() {     
        this.buffers.BufferForLine1
        .setShape(Shape.Line)
            .setColor(Color.OrangeRed)
        ;
    } // конец функции onInit

	async onUpdate() { 
	
	  const PriceType = 0,
		{BufferForLine1} = this.buffers,
			{
          Close,
         } = await Bar.load([
                Bar.Mode.Close,
            ], PriceType),
         barSize = Close.length;
		for (let i = 0; i < barSize; i++) {
            BufferForLine1.set(i, Close[i]);
        } //конец цикла
    } // конец функции onUpdate
} // конец кода индикатора

Для программирования индикаторов используется язык JavaScript. Поясним значение стандартного набора функций  и методов.

Минимальная конструкция индикатора состоит из трёх методов: constructor, onInit и onUpdate. Метод constructor - создаёт объекты, необходимые для работы индикатора; onInit - производит инициализацию индикатора, исполняется немедленно после загрузки клиентским терминалом; onUpdate - исполняется каждый раз при поступлении новых данных.

Рассмотрим каждый из них чуть подробнее.

    constructor() {
        super();   
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
        };
    }

В конструкторе ключевое слово super используется как функция, вызывающая родительский конструктор. Её необходимо вызвать до первого обращения к ключевому слову this в теле конструктора.

Затем мы создаем необходимые для работы буферы; в данном случае буфер всего один, мы даём ему имя BufferForLine1.

onInit() {     
        this.buffers.BufferForLine1
          .setShape(Shape.Line)
          .setColor(Color.OrangeRed)
        ;
    }

onInit  - Процедура Инициализации индикатора, исполняется немедленно после загрузки клиентским терминалом. В ней мы определяем свойства созданного нами буфера:

.setShape(Shape.Line) - задаём вид графика: линия

.setColor(Color.OrangeRed) - задаём цвет линии по умолчанию; в диалоговом окне можно будет выбрать другой.

async onUpdate() { 	
	  const PriceType = 0,
	{BufferForLine1} = this.buffers,
        {
          Close,
         } = await Bar.load([
                Bar.Mode.Close,
            ], PriceType),
         barSize = Close.length;
 
      for (let i = 0; i <= barSize; i++) {
            BufferForLine1.set(i, Close[i]);
        } 
    }

onUpdate - стандартная функция, исполняемая каждый раз при поступлении новых данных. async - эта функция асинхронная, в ней мы используем выражение await: исполнение программы приостанавливается до получения всех данных.

PriceType = 0: тип цены (bid или ask) мы пока зададим явно; в следующих версиях нашего индикатора мы запросим нужный вариант у пользователя.

{BufferForLine1} = this.buffers : передадим в метод данные наших буферов.

{
   Close,
  } = await Bar.load([
            Bar.Mode.Close,
         ], PriceType)

 - запросим данные Close для всех баров для соответствующего типа цены PriceType  и дождёмся заполнения соответствующего массива.

barSize = Close.length; - установим значение константы barSize равным количеству элементов массива Close.

И, наконец, цикл по i с шагом в один бар от последнего (текущего, нулевого) бара до barSize:

for (let i = 0; i < barSize; i++) {
            BufferForLine1.set(i, Close[i]);
 } 

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

Создав в редакторе mCode код программы, нажимаем переключатель <Добавить в "Мои индикаторы"> (он станет зеленым ) и нажимаем <Сохранить> - зеленой станет и точка слева от имени индикатора.

Переходим в торговый терминал, выбираем график нужной валютной пары, и добавляем в него наш индикатор через меню Индикаторы - Пользовательский - Имя_Вашего_Индикатора. В возникающем при этом диалоговом меню можно изменить цвет линии, её толщину и тип – по своему вкусу.

Если всё сделано правильно - результат работы индикатора отобразится в окне графика в виде линии заданного нами цвета и вида, соединяющей точки Close каждого бара:

1.3 Немного усложним наш индикатор: добавим вторую линию, соединяющую точки Open каждого бара

export class Main extends Indicator {
    constructor() {
        super();   
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2  : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {        
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
        ;
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
        ;		
    } // конец функции onInit

    async onUpdate() {
      const PriceType = 0,
	{BufferForLine1, BufferForLine2} = this.buffers,
        {
          Close,
          Open,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
            ], PriceType),
         barSize = Close.length; 
      
        for (let i = barSize - 1; i >= 0; i--) {        			
            BufferForLine1.set(i, Close[i]);
	BufferForLine2.set(i, Open[i]);
        } //конец цикла
    } // конец функции onUpdate
} // конец кода индикатора

Что мы сделали для добавления второй линии?

- в constructor добавили создание еще одного буфера BufferForLine2 ;

- в onInit добавили задание свойств этого нового буфера;

- в onUpdate добавили запрос данных для него;

- там же в тело цикла добавили заполнение BufferForLine2 значениями массива Open.

Несложно, не так ли? Сохранив улучшенную версию индикатора, мы получим на графике искомые две линии:

Заметим также, что мы изменили порядок перебора значений переменной i, заменив      for (let i = 0; i < barSize; i++) на  for (let i = barSize - 1; i >= 0; i--) . Такая замена не приводит к визуальным изменениям на графике, но теперь входные данные мы анализируем в более естественном порядке занесения их в историю. Кроме того, индикаторы рисуются слева направо (mtrader7.com/ru/docs/Terminal/mScript/JavaScript/v1/Buffer/setDrawBegin)– и именно в таком порядке мы теперь поставляем данные в буфер.

1.4 Добавим немного интерактивности

export class Main extends Indicator {
    constructor() {
   super();   		
   this.addInput("PriceType", InputType.PriceType, PriceType.Bid);		
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {        
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
        ;		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
        ;		
    } // конец функции onInit

    async onUpdate() {
		  const {PriceType} = this.getInputs(),
		{BufferForLine1, BufferForLine2} = this.buffers,
        {
          Close,
	      Open,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
            ], PriceType),
         barSize = Close.length; 
      
        for (let i = barSize - 1; i >=0; i--) {        			
            BufferForLine1.set(i, Close[i]);
	BufferForLine2.set(i, Open[i]);
        } //конец цикла
    } // конец функции onUpdate
} // конец кода индикатора

Настало время спросить у пользователя, по точкам какого типа цены он хочет построить линии: ask или bid.

Для этого в constructor мы добавляем строчку

this.addInput("PriceType", InputType.PriceType, PriceType.Bid);

(mtrader7.com/ru/docs/Terminal/mScript/JavaScript/v1/mScript/addInput)

а в onUpdate PriceType = 0 заменяем на {PriceType} = this.getInputs() – таким образом сообщая в метод onUpdate запрошенные у пользователя данные (mtrader7.com/ru/docs/Terminal/mScript/JavaScript/v1/mScript/getInputs).

В результате таких изменений - при установке индикатора на график - пользователю будет предложено уже расширенное меню:

1.5 Добавим еще немного интерактивности, функциональности и дружелюбности

Пользователь, возможно, захочет провести эти две линии не по Close и Open, а по другим точкам. Предоставим ему такую возможность.

export class Main extends Indicator {
    constructor() {
        super();   		
		this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
		this.addInput("Line1Price", InputType.AppliedPrice, 0);
		this.addInput("Line2Price", InputType.AppliedPrice, 1);		
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {        

        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
           .setLabel("Цвет, толщина и стиль линии 1")
        ;
		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
	 .setLabel("Цвет, толщина и стиль линии 2")
        ;		
    } // конец функции onInit

    async onUpdate() {
        const {PriceType, Line1Price, Line2Price} = this.getInputs(),
                 {BufferForLine1, BufferForLine2} = this.buffers,
        {
          Close,
          Open,
          High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType),
         barSize = Close.length; 
		
        for (let i = barSize - 1; i >=0; i--) {   
	let Price1, Price2;

	switch (Line1Price) {
              case 0:
                Price1 = Close[i];
                break;
              case 1:
                Price1 = Open[i];
                break;
              case 2:
                Price1 = High[i];
                break;
              case 3:
                Price1 = Low[i];
                break;
              default:
                Price1 = Close[i];
            } // конец switch Line1Price
			
	switch (Line2Price) {
              case 0:
                Price2 = Close[i];
                break;
              case 1:
                Price2 = Open[i];
                break;
              case 2:
                Price2 = High[i];
                break;
              case 3:
                Price2 = Low[i];
                break;
              default:
                Price2 = Close[i];
            } // конец switch Line2Price
			
            BufferForLine1.set(i, Price1);
	BufferForLine2.set(i, Price2);
        } //конец цикла по i
    } // конец функции onUpdate
} // конец кода индикатора

Мы добавили в constructor два запроса о том, какие значения использовать для построения линий:

this.addInput("Line1Price", InputType.AppliedPrice, 0);
		this.addInput("Line2Price", InputType.AppliedPrice, 1);

Соответственно, расширили в onUpdate запросы на получение данных

{PriceType, Line1Price, Line2Price } = this.getInputs()

и

        {
          Close,
          Open,
          High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType)

и  переработали код цикла, добавив пару промежуточных переменных

 let Price1, Price2;

и пару инструкций  switch для выбора соответствующих значений для этих переменных

(developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/switch)

Этими значениями мы и заполняем теперь в цикле массивы буферов:

BufferForLine1.set(i, Price1);
BufferForLine2.set(i, Price2);

Кроме этих функциональных изменений, добавим и немного «дружелюбности» к пользователю на этапе запроса данных – у нас есть возможность задать в onInit описание  рисуемых  индикаторов, которой мы и воспользуемся:

           .setLabel("Цвет, толщина и стиль линии 1")

Теперь окно запроса данных выглядит чуть симпатичнее:

А линии индикатора могут быть проведены, например, через точки High и Low:

1.6 Добавим возможность рисовать линии со смещением на несколько баров вперед или назад

export class Main extends Indicator {
    constructor() {
        super();   		
		this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
		this.addInput("Line1Price", InputType.AppliedPrice, 0);
		this.addInput("Line2Price", InputType.AppliedPrice, 1);
		this.addInput("BarsToShift", InputType.int, 0);		
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {                
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
           .setLabel("Цвет, толщина и стиль линии 1")
        ;
		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
	 .setLabel("Цвет, толщина и стиль линии 2")
        ;
		
    } // конец функции onInit

    async onUpdate() {
		  const {PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs(),
		{BufferForLine1, BufferForLine2} = this.buffers,
        {
          Close,
	      Open,
		  High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType),
         barSize = Close.length;       
        for (let i = barSize - 1; i >=0; i--) {   
	let Price1, Price2;
	switch (Line1Price) {
              case 0:
                Price1 = Close[i+BarsToShift];
                break;
              case 1:
                Price1 = Open[i+BarsToShift];
                break;
              case 2:
                Price1 = High[i+BarsToShift];
                break;
              case 3:
                Price1 = Low[i+BarsToShift];
                break;
              default:
                Price1 = Close[i+BarsToShift];
            } // конец switch Line1Price
			
	switch (Line2Price) {
              case 0:
                Price2 = Close[i+BarsToShift];
                break;
              case 1:
                Price2 = Open[i+BarsToShift];
                break;
              case 2:
                Price2 = High[i+BarsToShift];
                break;
  
            case 3:
                Price2 = Low[i+BarsToShift];
                break;
              default:
                Price2 = Close[i+BarsToShift];
            } // конец switch Line2Price		
            BufferForLine1.set(i, Price1);
	BufferForLine2.set(i, Price2);
        } //конец цикла по i
    } // конец функции onUpdate
} // конец кода индикатора

Мы добавили в constructor еще один запрос – количество баров сдвига:

this.addInput("BarsToShift", InputType.int, 0);

а в onUpdate – расширили запрос на получение входных данных:

{PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs()

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

Price1 = Close[i]; на Price1 = Close[i+BarsToShift]; 

Теперь окно запроса предпочтений пользователя снова изменилось:

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

так и назад.

по сравнению с оригинальным расположением линий без смещения:

1.7 Добавим стрелочки на график

Для иллюстрации возможностей рисования стрелок ограничимся пока простой задачей: нарисовать стрелочки на уровне High каждого бара.

export class Main extends Indicator {
    constructor() {
        super();   		
		this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
		this.addInput("Line1Price", InputType.AppliedPrice, 0);
		this.addInput("Line2Price", InputType.AppliedPrice, 1);
		this.addInput("BarsToShift", InputType.int, 0);		
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
            BufferForArrows1 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {                
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
		    .setLabel("Цвет, толщина и стиль линии 1")
        ;
		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
		    .setLabel("Цвет, толщина и стиль линии 2")
        ;
		
        this.buffers.BufferForArrows1
            .setShape(Shape.Arrow)
            .setArrow(ArrowSymbols.ArrowDoubleUpWards)
            .setColor(Color.Red)
            .setLabel("Цвет и размер стрелочек")
      ;

		
    } // конец функции onInit
    async onUpdate() {
		  const {PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs(),
		{BufferForLine1, BufferForLine2, BufferForArrows1} = this.buffers,
        {
         Close,
         Open,
         High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType),
         barSize = Close.length;       

        for (let i = barSize - 1; i >=0; i--) {   
			let Price1, Price2;

	switch (Line1Price) {
              case 0:
                Price1 = Close[i+BarsToShift];
                break;
              case 1:
                Price1 = Open[i+BarsToShift];
                break;
              case 2:
                Price1 = High[i+BarsToShift];
                break;
              case 3:
                Price1 = Low[i+BarsToShift];
                break;
              default:
                Price1 = Close[i+BarsToShift];
            } // конец switch Line1Price
			


	switch (Line2Price) {
              case 0:
                Price2 = Close[i+BarsToShift];
                break;
              case 1:
                Price2 = Open[i+BarsToShift];
                break;
              case 2:
                Price2 = High[i+BarsToShift];
                break;
              case 3:
                Price2 = Low[i+BarsToShift];
                break;
              default:
                Price2 = Close[i+BarsToShift];
            } // конец switch Line2Price
			
            BufferForLine1.set(i, Price1);
			BufferForLine2.set(i, Price2);
			BufferForArrows1.set(i, High[i] + 2 * Current.Point);
        } //конец цикла по i
	BufferForArrows1.clear(0,1);
    } // конец функции onUpdate
} // конец кода индикатора

Изменений в коде нашего индикатора не слишком много:

- мы добавили в constructor  еще один буфер 

 BufferForArrows1 : this.addBuffer()

- в onInit добавили инициализацию этого нового буфера:

  this.buffers.BufferForArrows1
   .setShape(Shape.Arrow)
     .setArrow(ArrowSymbols.ArrowDoubleUpWards)
     .setColor(Color.Red)
      .setLabel("Цвет и размер стрелочек")
      ;

-  в onUpdate –  добавили новый буфер в список запроса данных

{BufferForLine1, BufferForLine2, BufferForArrows1} = this.buffers

и в теле цикла организовали заполнение нового буфера данными:

BufferForArrows1.set(i, High[i] + 2 * Current.Point);

Мы добавляем небольшое смещение позиции стрелочек вверх (High[i] + 2 * Current.Point), поскольку по умолчанию центр стрелочек приходится на точку установленного значения буфера и выглядит на графике это не вполне удобочитаемо.

Кроме того, уже по завершении цикла мы удаляем стрелочку с последнего бара:

BufferForArrows1.clear(0,1);

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

В результате всех изменений окно запроса и отображение индикатора снова изменились:

1.8 Придадим стрелочкам некий смысл

Будем рисовать стрелочки не для каждого бара подряд, а только для тех, где значение High больше, чем у предыдущего бара.

export class Main extends Indicator {
    constructor() {
        super();   		
	this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
	this.addInput("Line1Price", InputType.AppliedPrice, 0);
	this.addInput("Line2Price", InputType.AppliedPrice, 1);
	this.addInput("BarsToShift", InputType.int, 0);	
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
            BufferForArrows1 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {                
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
		    .setLabel("Цвет, толщина и стиль линии 1")
        ;
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
		    .setLabel("Цвет, толщина и стиль линии 2")
        ;		
        this.buffers.BufferForArrows1
            .setShape(Shape.Arrow)
            .setArrow(ArrowSymbols.ArrowDoubleUpWards)
            .setColor(Color.Red)
            .setLabel("Цвет и размер стрелочек")
      ;		
    } // конец функции onInit
    async onUpdate() {
		  const {PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs(),
		{BufferForLine1, BufferForLine2, BufferForArrows1} = this.buffers,
        {
          Close,
          Open,
          High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType),
         barSize = Close.length;       
        for (let i = barSize - 1; i >=0; i--) {   
           let Price1, Price2;
	switch (Line1Price) {
              case 0:
                Price1 = Close[i+BarsToShift];
                break;
              case 1:
                Price1 = Open[i+BarsToShift];
                break;
              case 2:
                Price1 = High[i+BarsToShift];
                break;
              case 3:
                Price1 = Low[i+BarsToShift];
                break;
              default:
                Price1 = Close[i+BarsToShift];
            } // конец switch Line1Price
			
	switch (Line2Price) {
              case 0:
                Price2 = Close[i+BarsToShift];
                break;
              case 1:
                Price2 = Open[i+BarsToShift];
                break;

              case 2:
                Price2 = High[i+BarsToShift];
                break;
              case 3:
                Price2 = Low[i+BarsToShift];
                break;
              default:
                Price2 = Close[i+BarsToShift];
            } // конец switch Line2Price
			
            BufferForLine1.set(i, Price1);
	BufferForLine2.set(i, Price2);
	if ( i < barSize - 1 &&  High[i] > High[i+1] ) {
              BufferForArrows1.set(i, High[i] + 2 * Current.Point);
	} else {
              BufferForArrows1.clear(i,i+1);
	}
        } //конец цикла по i
       BufferForArrows1.clear(0,1);
    } // конец функции onUpdate
} // конец кода индикатора

Изменений в коде совсем немного: мы лишь заменили в теле цикла безусловное рисование стрелочек условным оператором (developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/if...else):

if ( i < barSize - 1 &&  High[i] > High[i+1] ) {
              BufferForArrows1.set(i, High[i] + 2 * Current.Point);
} else {
             BufferForArrows1.clear(i,i+1);
}

Это и позволяет добиться искомого результата:

1.9 Добавим еще одну линию

Соединим те точки High, где нарисованы стрелочки, ещё одной линией.

export class Main extends Indicator {
    constructor() {
        super();   
        this.setProperty(Property.ShortName, "Пользовательский Индикатор - Пример");		
	this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
	this.addInput("Line1Price", InputType.AppliedPrice, 0);
	this.addInput("Line2Price", InputType.AppliedPrice, 1);
	this.addInput("BarsToShift", InputType.int, 0);
		
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
            BufferForArrows1 : this.addBuffer(),
	BufferForLine3 : this.addBuffer(),
        };
    } // конец метода constructor

    onInit() {                
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
           .setLabel("Цвет, толщина и стиль линии 1")
        ;
		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
	.setLabel("Цвет, толщина и стиль линии 2")
        ;		
        this.buffers.BufferForArrows1
            .setShape(Shape.Arrow)
            .setArrow(ArrowSymbols.ArrowDoubleUpWards)
            .setColor(Color.Red)
	 .setLabel("Цвет и размер стрелочек")
       ;
       this.buffers.BufferForLine3
            .setShape(Shape.Line)
            .setColor(Color.White)           
	 .setLabel("Цвет, толщина и стиль линии 3")
        ;		
    } // конец функции onInit

    async onUpdate() {
	const {PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs(),
	{BufferForLine1, BufferForLine2, BufferForArrows1,BufferForLine3} = this.buffers,
        {
          Close,
          Open,
          High,
          Low,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
		     ], PriceType),
         barSize = Close.length; 
      
        var CurrentHighest;
		
        for (let i = barSize - 1; i >=0; i--) {   
	let Price1, Price2;
	switch (Line1Price) {
              case 0:
                Price1 = Close[i+BarsToShift];
                break;
              case 1:
                Price1 = Open[i+BarsToShift];
                break;
              case 2:
                Price1 = High[i+BarsToShift];
                break;
              case 3:
                Price1 = Low[i+BarsToShift];
                break;
              default:
                Price1 = Close[i+BarsToShift];
            } // конец switch Line1Price
			
	switch (Line2Price) {
              case 0:
                Price2 = Close[i+BarsToShift];
                break;
              case 1:
                Price2 = Open[i+BarsToShift];
                break;
              case 2:
                Price2 = High[i+BarsToShift];
                break;
              case 3:
                Price2 = Low[i+BarsToShift];
                break;
              default:
                Price2 = Close[i+BarsToShift];
            } // конец switch Line2Price
			
            BufferForLine1.set(i, Price1);
	BufferForLine2.set(i, Price2);
	if ( i < barSize - 1 &&  High[i] > High[i+1] ) {
              BufferForArrows1.set(i, High[i] + 2 * Current.Point);
	  BufferForLine3.set(i, High[i]);
	  CurrentHighest = High[i];
	} else {
              BufferForArrows1.clear(i,i+1);
			  BufferForLine3.set(i, CurrentHighest);
	} // конец if
        } //конец цикла по i
	BufferForArrows1.clear(0,1);
	BufferForLine3.clear(0,1);		
    } // конец функции onUpdate
} // конец кода индикатора

Как уже нетрудно догадаться внимательному читателю, мы:

- добавили в constructor еще один буфер:

BufferForLine3 : this.addBuffer()

- добавили в onInit его инициализацию:

       this.buffers.BufferForLine3
            .setShape(Shape.Line)
            .setColor(Color.White)           
	.setLabel("Цвет, толщина и стиль линии 3")
        ;

- в onUpdate добавили этот новый буфер в запрос данных

{BufferForLine1, BufferForLine2, BufferForArrows1,BufferForLine3} = this.buffers	

а в тело цикла – заполнение нового буфера  данными:

if ( i < barSize - 1 &&  High[i] > High[i+1] ) {
              BufferForArrows1.set(i, High[i] + 2 * Current.Point);
	  BufferForLine3.set(i, High[i]);
	  CurrentHighest = High[i];
} else {
              BufferForArrows1.clear(i,i+1);
	  BufferForLine3.set(i, CurrentHighest);
} // конец if

Обратите внимание на то, что мы, наконец, дали имя нашему индикатору, добавив в constructor объявление одного из свойств индикатора:

this.setProperty(Property.ShortName, "Пользовательский Индикатор - Пример");

(mtrader7.com/ru/docs/Terminal/mScript/JavaScript/v1/mScript/setProperty)

(mtrader7.com/ru/docs/Terminal/mScript/JavaScript/v1/Property)

1.10 Графические элементы: Текст (Text) и Прямоугольник (Rectangle)

Рассмотрим пару примеров работы с графическими объектами.

export class Main extends Indicator {
    constructor() {
        super();   
        this.setProperty(Property.ShortName, "Пользовательский Индикатор - Пример");
	this.addInput("PriceType", InputType.PriceType, PriceType.Bid);
	this.addInput("Line1Price", InputType.AppliedPrice, 0);
	this.addInput("Line2Price", InputType.AppliedPrice, 1);
	this.addInput("BarsToShift", InputType.int, 0);	
        this.buffers = {
            BufferForLine1 : this.addBuffer(),
            BufferForLine2 : this.addBuffer(),
            BufferForArrows1 : this.addBuffer(),
	BufferForLine3 : this.addBuffer(),
        };
    } // конец метода constructor
    onInit() {                
        this.buffers.BufferForLine1
            .setShape(Shape.Line)
            .setColor(Color.OrangeRed)           
           .setLabel("Цвет, толщина и стиль линии 1")
        ;


		
        this.buffers.BufferForLine2
            .setShape(Shape.Line)
            .setColor(Color.Khaki)           
	.setLabel("Цвет, толщина и стиль линии 2")
        ;
		
        this.buffers.BufferForArrows1
            .setShape(Shape.Arrow)
            .setArrow(ArrowSymbols.ArrowDoubleUpWards)
            .setColor(Color.Red)
	 .setLabel("Цвет и размер стрелочек")
       ;
       this.buffers.BufferForLine3
            .setShape(Shape.Line)
            .setColor(Color.White)           
	.setLabel("Цвет, толщина и стиль линии 3")
        ;		
    } // конец функции onInit

    async onUpdate() {
      const {PriceType, Line1Price, Line2Price, BarsToShift} = this.getInputs(),
     {BufferForLine1, BufferForLine2, BufferForArrows1,BufferForLine3} = this.buffers,     
   {
          Close,
          Open,
          High,
          Low,
          Time,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
	    Bar.Mode.Time,
	     ], PriceType),
         barSize = Close.length; 
// Объект "Текст"		 
let TextObjectName = "MyTextObject";
let OurTextObject = this.getObject(TextObjectName);	
if (!OurTextObject) {
OurTextObject = this.createObject(GObject.Type.Text, TextObjectName)
	.setTime1(Time[12])
	.setPrice1(Low[12] - 1 * Current.Point)
	.setColor(Color.Aquamarine)
	.setText("Очень хороший индикатор")
	.setFontSize("22")
}
// Конец Объекта "Text"		 		
// Объект "Rectangle"		 
let RectangleObjectName = "MyRectangleObject";
let OurRectangleObject = this.getObject(RectangleObjectName);
if (!OurRectangleObject) {
OurRectangleObject = this.createObject(GObject.Type.Rectangle, RectangleObjectName)
  .setTime1(Time[7])
  .setPrice1(Low[7] -2 * Current.Point)
  .setTime2(Time[3])
  .setPrice2(Low[3] - 4 * Current.Point)
  .setColor(Color.MediumVioletRed)
}
// Конец Объекта "Rectangle"		 				
        var CurrentHighest;
        for (let i = barSize - 1; i >=0; i--) {   
	let Price1, Price2;
	switch (Line1Price) {
              case 0:
                Price1 = Close[i+BarsToShift];
                break;
              case 1:
                Price1 = Open[i+BarsToShift];
                break;
              case 2:
                Price1 = High[i+BarsToShift];
                break;
              case 3:
                Price1 = Low[i+BarsToShift];
                break;
              default:
                Price1 = Close[i+BarsToShift];
            } // конец switch Line1Price

	switch (Line2Price) {
              case 0:
                Price2 = Close[i+BarsToShift];
                break;
              case 1:
                Price2 = Open[i+BarsToShift];
                break;
              case 2:
                Price2 = High[i+BarsToShift];
                break;
              case 3:
                Price2 = Low[i+BarsToShift];
                break;
              default:
                Price2 = Close[i+BarsToShift];
            } // конец switch Line2Price			
            BufferForLine1.set(i, Price1);
	BufferForLine2.set(i, Price2);
	if ( i < barSize - 1 &&  High[i] > High[i+1] ) {
              BufferForArrows1.set(i, High[i] + 2 * Current.Point);
			  BufferForLine3.set(i, High[i]);
			  CurrentHighest = High[i];
	} else {
              BufferForArrows1.clear(i,i+1);
			  BufferForLine3.set(i, CurrentHighest);
	} // конец if
        } //конец цикла по i
       BufferForArrows1.clear(0,1);
       BufferForLine3.clear(0,1);		
    } // конец функции onUpdate
} // конец кода индикатора

Нам понадобятся дополнительные данные – массив Time, задающий время каждого бара, поэтому мы усовершенствуем запрос данных:

   {
          Close,
          Open,
          High,
          Low,
          Time,
         } = await Bar.load([
                Bar.Mode.Close,
                Bar.Mode.Open,
                Bar.Mode.High,
                Bar.Mode.Low,
	    Bar.Mode.Time,
	     ], PriceType)

За создание объекта типа Текст отвечает следующий фрагмент кода:

// Объект "Текст"		 
let TextObjectName = "MyTextObject";
let OurTextObject = this.getObject(TextObjectName);	
if (!OurTextObject) {
OurTextObject = this.createObject(GObject.Type.Text, TextObjectName)
.setTime1(Time[12])
	.setPrice1(Low[12] - 1 * Current.Point)
	.setColor(Color.Aquamarine)
	.setText("Очень хороший индикатор")
	.setFontSize("22")
}
// Конец Объекта "Text"	

Последовательность действий достаточно проста:

- объявляем переменную с именем TextObjectName и присваиваем ей значение – имя нашего текстового объекта:

let TextObjectName = "MyTextObject";

- проверяем, не существует ли уже такой объект:

let OurTextObject = this.getObject(TextObjectName);

- если нет, то создаём новый объект:

OurTextObject = this.createObject(GObject.Type.Text, TextObjectName)

и задаём его характеристики

- начальную точку привязки текста:

.setPrice1(Low[12] - 1 * Current.Point)

- цвет:

.setColor(Color.Aquamarine)

- текст по умолчанию:

.setText("Очень хороший индикатор")

- размер шрифта:

.setFontSize("22")

Рисование прямоугольника производит следующий фрагмент кода:

// Объект "Rectangle"		 
let RectangleObjectName = "MyRectangleObject";
let OurRectangleObject = this.getObject(RectangleObjectName);
if (!OurRectangleObject) {
OurRectangleObject = this.createObject(GObject.Type.Rectangle, RectangleObjectName)
.setTime1(Time[7])
	.setPrice1(Low[7] -2 * Current.Point)
	.setTime2(Time[3])
	.setPrice2(Low[3] - 4 * Current.Point)
	.setColor(Color.MediumVioletRed)
}
// Конец Объекта "Rectangle"

В данном случае все аналогично: задаём имя объекта и проверяем, существует ли он уже.  Если нет – создаём новый объект и задаём его параметры. Для прямоугольника это помимо цвета еще две точки, координаты двух углов прямоугольника:

.setTime1(Time[7])
	.setPrice1(Low[7] -2 * Current.Point)

и

.setTime2(Time[3])
	.setPrice2(Low[3] - 4 * Current.Point)

На этом мы завершаем первую часть нашего повествования о создании пользовательских индикаторов.