Español Português
preview
От начального к среднему уровню: Файл шаблона графика (III)

От начального к среднему уровню: Файл шаблона графика (III)

MetaTrader 5 — Примеры |
60 0
CODE X
CODE X

Введение

В предыдущей статье «От начального к среднему уровню: Файл шаблона графика (II)» мы рассмотрели, как можно добавить индикатор на график, содержащийся в объекте OBJ_CHART. Если вы ещё не читали ту статью, советую вам прервать чтение и сначала ознакомиться с предыдущей статьёй. Понимание того, что там объясняется, крайне важно для того, чтобы вам было проще понять то, о чём пойдёт речь здесь.

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

Так что приготовьтесь, потому что здесь мы углубимся в то, что было объяснено в предыдущей статье, и подробнее разберём ряд вопросов. Настало время отвлечься от всего постороннего и сосредоточиться исключительно на том, что будет объясняться здесь. Потому что теперь начинается самое сложное.


Файл шаблона (III)

То, что мы увидели в предыдущей статье, — лишь небольшой намёк на то, что мы действительно можем сделать. Хотя многие новички и даже программисты с некоторым опытом полагают, что некоторые вещи сделать совершенно невозможно, на самом деле далеко не всего можно добиться, используя исключительно MQL5. В целом MQL5 отлично подходит для того, чтобы указывать MetaTrader 5, как выполнять те или иные действия. Тем не менее понимание того, как работает платформа, позволит вам гораздо шире, полнее и глубже контролировать то, как ваши приложения будут реально работать внутри MetaTrader 5.

То, что было показано в предыдущей статье, вовсе не так очевидно, как может показаться. Настолько, что, когда здесь были опубликованы мои первые статьи, многие так и не поняли, как на самом деле работали те приложения. Это произошло именно потому, что они ожидали найти в коде то, чего там на самом деле не было. Секрет приложений, показанных в то время, заключался именно в том, что не всё достижимо, используя только MQL5 или чистое программирование.

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

Я уже видел многих, кто ломал голову, пытаясь что-то реализовать, хотя, знай они само приложение или даже операционную систему, им не пришлось бы биться над тем, чтобы запрограммировать всё с нуля. Вместо этого они просто воспользовались бы ресурсами или механизмами, доступными в самой операционной системе или даже в приложении — в нашем случае это MetaTrader 5, — чтобы обойти проблему и создать решение.

Именно такие вещи я и рассматривал в своих первых статьях. Однако объяснение того, почему это работало, оказалось недостаточно ясным. Это произошло потому, что я ошибочно полагал, что целевая аудитория была иной. В результате многие из тех, кто пытался воспроизвести показанные результаты, столкнулись с пробелом в знаниях.

Именно из-за этого недостатка в объяснении было решено вернуться к истокам. Так что я больше не буду давать объяснения наполовину. Я хочу, чтобы вы поняли до мельчайших деталей, как создаётся то, что мы собираемся показать.

Для начала есть несколько предварительных условий, чтобы вы могли полностью понять всё, что мы будем делать. Среди них — понимание того, что мы делали в предыдущей статье, а также того, что было объяснено в следующих статьях:

От начального к среднему уровню: Объекты и подокна (III)

От начального к среднему уровню: Подокна (IV)

От начального к среднему уровню: События в объектах (IV)

Итак, теперь мы можем перейти к той части, где реализуем нечто несколько безумное. Я говорю «нелепое», потому что то, что мы сейчас увидим, обычно хорошенько запутывает многих начинающих программистов, а более опытных заставляет насторожиться. Это связано с тем, что программировать мы будем совсем немного, но получим довольно интересные результаты, которые выходят далеко за рамки того, что, на первый взгляд, обещает код.

Для начала мы создадим простой и почти совсем безликий код. Он показан ниже.

01. #property copyright "Daniel Jose"
02. #property description "DEMO Indicator"
03. #property indicator_separate_window
04. #property indicator_height          100
05. #property indicator_plots           0
06. //+------------------------------------------------------------------+
07. class C_MultiChart
08. {
09. private:
10.    string            m_szObject[];
11. 
12.    void              NewObj_CHART(void)
13.      {
14.       int            nObjects = ArraySize(m_szObject) + 1;
15.       string         sz0 = "Demo_Chart #" + (string)ObjectsTotal(0, -1, -1);
16. 
17.       ArrayResize(m_szObject, nObjects);
18.       m_szObject[nObjects - 1] = sz0;
19. 
20.       ObjectCreate(0, sz0, OBJ_CHART, ChartWindowFind(), 0, 0);
21.       ObjectSetString(0, sz0, OBJPROP_SYMBOL, _Symbol);
22.       ObjectSetInteger(0, sz0, OBJPROP_YDISTANCE, 0);
23.       ObjectSetInteger(0, sz0, OBJPROP_PERIOD, PERIOD_M15);
24.       ObjectSetInteger(0, sz0, OBJPROP_DATE_SCALE, false);
25.       ObjectSetInteger(0, sz0, OBJPROP_PRICE_SCALE, false);
26.       ObjectSetInteger(0, sz0, OBJPROP_YSIZE, indicator_height);
27.      }
28. 
29. public:
30.                      C_MultiChart(void)
31.      {
32.       for(int c = 0; c < 2; c++)
33.          NewObj_CHART();
34.      }
35. 
36.    void              Resize(void)
37.      {
38.       const int      nObjects = ArraySize(m_szObject);
39.       const int      step = (int)(ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) /
40.                                   (nObjects > 0 ? nObjects : 1));
41. 
42.       for(int c = 0, x = 0; c < nObjects; c++, x += step)
43.         {
44.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XDISTANCE, x);
45.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XSIZE, step);
46.         }
47.       ChartRedraw();
48.      }
49. };
50. //+------------------------------------------------------------------+
51. C_MultiChart *gl_Multi = NULL;
52. //+------------------------------------------------------------------+
53. int OnInit(void)
54.   {
55.    gl_Multi = new C_MultiChart();
56. 
57.    return(INIT_SUCCEEDED);
58.   }
59. //+------------------------------------------------------------------+
60. int OnCalculate(const int rates_total,
61.                 const int prev_calculated,
62.                 const int begin,
63.                 const double &price[])
64.   {
65.    return(rates_total);
66.   }
67. //+------------------------------------------------------------------+
68. void OnChartEvent(const int id,
69.                   const long &lparam,
70.                   const double &dparam,
71.                   const string &sparam)
72.   {
73.    switch(id)
74.      {
75.       case CHARTEVENT_CHART_CHANGE:
76.          if(gl_Multi != NULL)
77.             gl_Multi.Resize();
78.          break;
79.      }
80.   }
81. //+------------------------------------------------------------------+
82. void OnDeinit(const int reason)
83.   {
84.    delete gl_Multi;
85.   }
86. //+------------------------------------------------------------------+

Код 01

Хотя он и не слишком интересен, этот код 01 позволяет нам создать то, что показано на следующем изображении.


Изображение 01

Возможно, у вас возник вопрос по поводу чего-то, что есть в этом коде 01. И для тех, кто изучает и применяет на практике то, чему мы учили в этой серии, этот вопрос в некотором смысле вполне уместен и должен быть полностью прояснён. Речь идёт о следующем: где находится процедура или функция, отвечающая за удаление с графика объектов типа OBJ_CHART? Дело в том, что в этом коде 01 нам не нужно беспокоиться об удалении этих объектов. Это связано с тем, что в шестой строке мы указываем, что будем использовать подокно. Далее, в строке двадцать три, мы создадим объекты.

Мы указываем MetaTrader 5 создать объекты непосредственно в подокне, в котором будет размещён индикатор. Теперь обратите особое внимание: поскольку объекты будут размещены в том же подокне, где будет находиться индикатор, при удалении индикатора с графика MetaTrader 5 также удалит всё, что находится внутри этого подокна. То есть, даже если мы не удалим объекты с графика, MetaTrader 5 сделает это за нас, поскольку объект, созданный в подокне, не сможет существовать, если это подокно перестанет существовать.

Однако здесь есть одна деталь. Обратите внимание, что в конце кода мы просим MetaTrader 5 освободить память, выделенную под экземпляр класса, созданный при инициализации индикатора. Это освобождение памяти действительно необходимо выполнить. В противном случае MetaTrader 5 отобразит предупреждение о том, что в памяти всё ещё остаются элементы, которые не были освобождены. Теперь вы видите, насколько важно правильно понимать, как работает платформа? Не понимая, как всё это работает, мы в итоге создавали бы лишний код. А в других случаях мы не включали бы код, который на самом деле необходим. Такие знания приобретаются только практикой и учёбой. Так что наберитесь терпения, если вы пока не поняли, что мы только что сделали.

Несмотря на все эти объяснения, до сих пор мы не сделали абсолютно ничего. Давайте пока оставим этот код 01, поскольку он лишь часть того, что нам на самом деле нужно здесь сделать, и рассмотрим кое-что другое — именно здесь и начинается самое сложное. Это связано с тем, что многие считают, будто нужно что-то создавать, хотя на самом деле нам не нужно создавать ничего — достаточно адаптировать то, что мы уже знаем, чтобы получить желаемый результат.

А теперь полностью сосредоточьтесь на том, что я сейчас объясню. Потому что в прошлом это уже вызывало большую путаницу. Наша задача — разместить два индикатора в подокне, показанном на изображении 01. Будьте очень внимательны: цель состоит в том, чтобы разместить два индикатора в одном и том же подокне. Однако эти два индикатора предназначены для размещения в разных подокнах. Таким образом, теоретически мы никогда не смогли бы разместить их оба в одном подокне. По крайней мере, не без того, чтобы их интерпретация стала довольно запутанной.

Поскольку цель здесь учебная и мы хотим, чтобы всё было как можно проще для понимания, на этом первом этапе мы не будем писать никакого кода. Мы будем использовать индикаторы, которые уже доступны по умолчанию в MetaTrader 5. Так все смогут понять, что написание кода здесь — лишь деталь. Главное — чтобы вы поняли, как далеко мы можем зайти, используя MetaTrader 5 без каких-либо чрезвычайно сложных и изощрённых средств.

Хорошо, есть несколько небольших хитростей, или шагов, если хотите, которым мы должны следовать, чтобы получить больше свободы и по возможности обойтись меньшим объёмом кода. То, что нам нужно сделать в первую очередь, показано в следующей анимации.


Анимация 01

Здесь мы следим за тем, чтобы использовалось всё окно. Это важно, чтобы избежать слишком большого смещения графика позже, когда индикаторы будут размещены в подокнах. После завершения всего процесса мы сможем снова включить смещение графика. Но пока что держите его отключённым. Если вы забудете отключить эту функцию, сможете сделать это позже. Однако это потребует гораздо больше работы. Так что давайте сократим объём работы и будем делать всё аккуратно и внимательно.

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

Хорошо, поскольку мы хотим использовать два индикатора в одном подокне, давайте не торопиться и добавим первый из них. Это показано в следующей анимации.


Анимация 02

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


Изображение 02

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


Изображение 03

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


Изображение 04

Отлично, мы уже почти у цели. Теперь выберем наш второй индикатор. В данном случае я выберу тот, который показан на следующей анимации.


Анимация 03

И, ещё раз, настраиваем индикатор так, как нам нужно, и действуем так же, как и с первым индикатором. То есть возвращаемся к изображению 02 и сохраняем шаблон. В итоге получим то, что показано на следующем изображении.


Изображение 05

Теперь обратите внимание, что оба нужных нам индикатора уже настроены. Обратите внимание, что каждый из них находится в отдельном шаблоне. Это заметно упростит то, что мы вскоре будем делать. Однако ничто не мешает вам сначала поместить их вместе, а потом разделить. Хотя это создаст гораздо больше работы. Этой работы можно избежать, просто следуя показанной здесь процедуре, то есть храня файлы отдельно.

Преимущество такого подхода в том, что он экономит нам немало работы и усилий. А сэкономленное время мы потом сможем посвятить чему-нибудь более интересному. Поэтому учиться что-то делать — это никогда не пустая трата времени, хотя многие порой и пытаются убедить нас в обратном. Но какой тогда следующий шаг? Что ж, если вы прочитали и поняли объяснения из предыдущей статьи, то ответ у вас, скорее всего, уже вертится на языке. Потому что следующим шагом будет применить эти сохранённые шаблоны к графику, созданному и поддерживаемому объектом OBJ_CHART.

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

Очень хорошо, давайте снова сосредоточим наше внимание на коде 01. А теперь остановитесь и на мгновение задумайтесь: как лучше всего указать в коде класса C_MultiChart, какие шаблоны следует использовать в каждом из объектов типа OBJ_CHART? Что ж, ответ таков: это зависит от обстоятельств. Поскольку каждый случай индивидуален, вы можете представить себе разные способы это сделать. Но давайте немного поиграем с этим кодом, чтобы сделать нечто довольно необычное. Обратите внимание, что в конструкторе класса мы указываем, что хотим показать в подокне два объекта типа OBJ_CHART. Но что, если мы захотим разместить больше? А если нам нужны не два, а только один? Что ж, это может показаться немного странным. Но мы можем адаптировать код под то, что хотим создать. И, сделав так, мы считаем, что код станет гораздо интереснее и увлекательнее, поскольку каждый сможет создать собственную конфигурацию с минимальными усилиями.

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

01. #property copyright "Daniel Jose"
02. #property description "DEMO Indicator"
03. #property indicator_separate_window
04. #property indicator_height          100
05. #property indicator_plots           0
06. //+------------------------------------------------------------------+
07. class C_MultiChart
08.   {
09. private:
10.    string            m_szObject[];
11. 
12.    void              NewObj_CHART(void)
13.      {
14.       int            nObjects = ArraySize(m_szObject) + 1;
15.       string         sz0 = "Demo_Chart #" + (string)ObjectsTotal(0, -1, -1);
16. 
17.       ArrayResize(m_szObject, nObjects);
18.       m_szObject[nObjects - 1] = sz0;
19. 
20.       ObjectCreate(0, sz0, OBJ_CHART, ChartWindowFind(), 0, 0);
21.       ObjectSetString(0, sz0, OBJPROP_SYMBOL, _Symbol);
22.       ObjectSetInteger(0, sz0, OBJPROP_YDISTANCE, 0);
23.       ObjectSetInteger(0, sz0, OBJPROP_PERIOD, PERIOD_M15);
24.       ObjectSetInteger(0, sz0, OBJPROP_DATE_SCALE, false);
25.       ObjectSetInteger(0, sz0, OBJPROP_PRICE_SCALE, false);
26.       ObjectSetInteger(0, sz0, OBJPROP_YSIZE, indicator_height);
27.      }
28. 
29. public:
30.                      C_MultiChart(const string &szTemplates[])
31.      {
32.       for(int c = 0; c < ArraySize(szTemplates); c++)
33.          NewObj_CHART();
34.      }
35. 
36.    void              Resize(void)
37.      {
38.       const int      nObjects = ArraySize(m_szObject);
39.       const int      step = (int)(ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) /
40.                                   (nObjects > 0 ? nObjects : 1));
41. 
42.       for(int c = 0, x = 0; c < nObjects; c++, x += step)
43.         {
44.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XDISTANCE, x);
45.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XSIZE, step);
46.         }
47.       ChartRedraw();
48.      }
49.   };
50. //+------------------------------------------------------------------+
51. C_MultiChart *gl_Multi = NULL;
52. //+------------------------------------------------------------------+
53. //| Функция инициализации пользовательского индикатора                         |
54. //+------------------------------------------------------------------+
55. int OnInit(void)
56.   {
57.    const string     szTemplates[] =
58.      {
59.       "Bollinger Bands.tpl",
60.       "Parabolic SAR.tpl"
61.      };
62. 
63.    gl_Multi = new C_MultiChart(szTemplates);
64. 
65.    return(INIT_SUCCEEDED);
66.   }
67. //+------------------------------------------------------------------+
68. //| Функция итерации пользовательского индикатора                              |
69. //+------------------------------------------------------------------+
70. int OnCalculate(const int rates_total,
71.                 const int prev_calculated,
72.                 const int begin,
73.                 const double &price[])
74.   {
75.    return(rates_total);
76.   }
77. //+------------------------------------------------------------------+
78. //| Функция ChartEvent                                              |
79. //+------------------------------------------------------------------+
80. void OnChartEvent(const int id,
81.                   const long &lparam,
82.                   const double &dparam,
83.                   const string &sparam)
84.   {
85.    switch(id)
86.      {
87.       case CHARTEVENT_CHART_CHANGE:
88.          if(gl_Multi != NULL)
89.             gl_Multi.Resize();
90.          break;
91.      }
92.   }
93. //+------------------------------------------------------------------+
94. //| Функция деинициализации пользовательского индикатора                       |
95. //+------------------------------------------------------------------+
96. void OnDeinit(const int reason)
97.   {
98.    delete gl_Multi;
99.   }
100.//+------------------------------------------------------------------+

Код 02

Какой замечательный этот код 02! Ведь, внеся лишь несколько изменений по сравнению с кодом 01, мы получили способ задавать количество объектов OBJ_CHART, которые нужно разместить в подокне. И всё, что для этого потребовалось, — простое изменение кода. Тем не менее, это по-прежнему даёт тот же результат, что и код 01. А теперь возникает вопрос: когда следует применять шаблон? И снова ответ: зависит от ситуации. "Вы и правда настойчивы. На каждый вопрос ответ всегда один: зависит, зависит и зависит. Не могли бы вы выражаться немного прямее? И сказать, когда на самом деле лучше всего это делать?". Хотя это может показаться немного странным и раздражающим, дело в том, что всё зависит от того, что вы собираетесь делать. В программировании не существует единственно правильного подхода.

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

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

                                   .
                                   .
                                   .
15. 
16.    void              NewObj_CHART(const string szTemplate)
17.      {
18.       int            nObjects = ArraySize(m_szObject) + 1;
19.       string         sz0 = "Demo_Chart #" + (string)ObjectsTotal(0, -1, -1);
20.       long           id;
21. 
22.       ArrayResize(m_szObject, nObjects);
23.       m_szObject[nObjects - 1] = sz0;
24. 
25.       ObjectCreate(0, sz0, OBJ_CHART, ChartWindowFind(), 0, 0);
26.       ObjectSetString(0, sz0, OBJPROP_SYMBOL, _Symbol);
27.       ObjectSetInteger(0, sz0, OBJPROP_YDISTANCE, 0);
28.       ObjectSetInteger(0, sz0, OBJPROP_PERIOD, PERIOD_M15);
29.       ObjectSetInteger(0, sz0, OBJPROP_DATE_SCALE, false);
30.       ObjectSetInteger(0, sz0, OBJPROP_PRICE_SCALE, false);
31.       ObjectSetInteger(0, sz0, OBJPROP_YSIZE, indicator_height);
32. 
33.       id = ObjectGetInteger(0, sz0, OBJPROP_CHART_ID);
34.       ChartApplyTemplate(id, szTemplate);
35.       ChartRedraw(id);
36.      }
37. 
38. public:
39.                      C_MultiChart(const string &szTemplates[])
40.      {
41.       for(int c = 0; c < ArraySize(szTemplates); c++)
42.          NewObj_CHART(szTemplates[c]);
43.      }
44. 
                                   .
                                   .
                                   .

Фрагмент 01

А теперь обратите особое внимание, потому что это очень важно. Обратите внимание, что мы передаём имя шаблона напрямую в вызове из строки сорок один. В строке тридцать один этого фрагмента 01 мы получаем идентификатор графика, созданного и содержащегося в объекте OBJ_CHART. Эти данные используются в строке тридцать два для загрузки и применения шаблона к графику. В строке 33 мы обеспечиваем первое обновление. Последующие обновления будут происходить, когда таймфрейм укажет, что график необходимо обновить повторно, но этим займётся MetaTrader 5. И хотя всё работает, как вы можете видеть в следующей анимации, следует иметь в виду, что в этом коде мы не выполняем никаких проверок на наличие возможных ошибок.

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



Анимация 04

Подобные вещи весьма интересны. Но что, если пойти немного дальше? Возможно, нам захочется использовать какой-нибудь более сложный индикатор, например стохастик или другой индикатор, который по своей природе использует подокно. Хорошо, в таком случае нам, возможно, понадобятся некоторые дополнительные настройки, хотя в отдельных ситуациях они могут и не потребоваться.

И именно в такие моменты у многих способных людей начинает возникать путаница. Но это происходит, потому что они не уделили должного внимания тому, что было объяснено до этого момента. Если это ваш случай, остановитесь, немного расслабьтесь и вернитесь к началу статьи. Так вы сможете понять, почему результат, полученный при выполнении кода 02, оказался именно таким, каким мы видим его в анимации 04. Когда вы это поймёте, сможете перейти на следующий уровень, который как раз и заключается в использовании индикаторов, естественным образом работающих в подокне.

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


Анимация 05

А теперь обратите внимание на следующую деталь. Если вы сохраните этот график в том виде, в каком он показан в конце анимации 05, отобразится график стохастика. Однако будет одно небольшое неудобство: на графике также будут отображаться котировки. Но подождите минутку. Я этого не понимаю. Как это, у нас ещё и график котировок будет? Хорошо, чтобы это продемонстрировать, давайте внесём некоторые изменения в код 02. И не волнуйтесь: в приложенном файле вы найдёте эти коды. Единственное, что я не буду включать, — это шаблоны, так как они могут перезаписать какие-либо шаблоны, имеющиеся в вашей установке MetaTrader 5. Не думаю, что у кого-то возникнут проблемы с воспроизведением того, что показано в статье. В любом случае сохраните шаблон, показанный в анимации 05, под именем, указанным на следующем изображении.


Изображение 06

После этого вернёмся к коду. Чтобы всё было чуть менее неудобно, мы позволим пользователю, или оператору, изменять высоту подокна. Одновременно мы загрузим в индикатор шаблон с изображения 06 и применим его к графику объекта OBJ_CHART. Полный код приведён ниже.

01. #property copyright "Daniel Jose"
02. #property description "DEMO Indicator"
03. #property indicator_separate_window
04. #property indicator_plots           0
05. //+------------------------------------------------------------------+
06. class C_MultiChart
07.   {
08. private:
09.    string            m_szObject[];
10.    int               m_Sub;
11. 
12.    void              NewObj_CHART(const string szTemplate)
13.      {
14.       int            nObjects = ArraySize(m_szObject) + 1;
15.       string         sz0 = "Demo_Chart #" + (string)ObjectsTotal(0, -1, -1);
16.       long           id;
17. 
18.       ArrayResize(m_szObject, nObjects);
19.       m_szObject[nObjects - 1] = sz0;
20. 
21.       ObjectCreate(0, sz0, OBJ_CHART, m_Sub = ChartWindowFind(), 0, 0);
22.       ObjectSetString(0, sz0, OBJPROP_SYMBOL, _Symbol);
23.       ObjectSetInteger(0, sz0, OBJPROP_YDISTANCE, 0);
24.       ObjectSetInteger(0, sz0, OBJPROP_PERIOD, PERIOD_M15);
25.       ObjectSetInteger(0, sz0, OBJPROP_DATE_SCALE, false);
26.       ObjectSetInteger(0, sz0, OBJPROP_PRICE_SCALE, false);
27. 
28.       id = ObjectGetInteger(0, sz0, OBJPROP_CHART_ID);
29.       ChartApplyTemplate(id, szTemplate);
30.       ChartRedraw(id);
31.      }
32. 
33. public:
34.                      C_MultiChart(const string &szTemplates[])
35.      {
36.       for(int c = 0; c < ArraySize(szTemplates); c++)
37.          NewObj_CHART(szTemplates[c]);
38.      }
39. 
40.    void              Resize(void)
41.      {
42.       const int      nObjects = ArraySize(m_szObject);
43.       const int      step = (int)(ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) /
44.                                   (nObjects > 0 ? nObjects : 1));
45. 
46.       for(int c = 0, x = 0; c < nObjects; c++, x += step)
47.         {
48.          ObjectSetInteger(0, m_szObject[c], OBJPROP_YSIZE,
49.                           ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, m_Sub));
50.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XDISTANCE, x);
51.          ObjectSetInteger(0, m_szObject[c], OBJPROP_XSIZE, step);
52.         }
53.       ChartRedraw();
54.      }
55.   };
56. //+------------------------------------------------------------------+
57. C_MultiChart *gl_Multi = NULL;
58. //+------------------------------------------------------------------+
59. //| Функция инициализации пользовательского индикатора                         |
60. //+------------------------------------------------------------------+
61. int OnInit(void)
62.   {
63.    const string     szTemplates[] =
64.      {
65.       "Stochastic.tpl"
66.      };
67. 
68.    gl_Multi = new C_MultiChart(szTemplates);
69. 
70.    return(INIT_SUCCEEDED);
71.   }
72. //+------------------------------------------------------------------+
73. //| Функция итерации пользовательского индикатора                              |
74. //+------------------------------------------------------------------+
75. int OnCalculate(const int rates_total,
76.                 const int prev_calculated,
77.                 const int begin,
78.                 const double &price[])
79.   {
80.    return(rates_total);
81.   }
82. //+------------------------------------------------------------------+
83. //| Функция ChartEvent                                               |
84. //+------------------------------------------------------------------+
85. void OnChartEvent(const int id,
86.                   const long &lparam,
87.                   const double &dparam,
88.                   const string &sparam)
89.   {
90.    switch(id)
91.      {
92.       case CHARTEVENT_CHART_CHANGE:
93.          if(gl_Multi != NULL)
94.             gl_Multi.Resize();
95.          break;
96.      }
97.   }
98. //+------------------------------------------------------------------+
99. //| Функция деинициализации пользовательского индикатора                       |
100.//+------------------------------------------------------------------+
101. void OnDeinit(const int reason)
102.   {
103.    delete gl_Multi;
104.   }
105.//+------------------------------------------------------------------+

Код 03

При выполнении этого кода вы получите результат, который можно увидеть в следующей анимации.


Анимация 06

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


Анимация 07

Здесь мы загружаем файл шаблона, который использовали в коде 03. Это подготавливает конфигурацию к следующему шагу. После этого действуем так, как показано в следующей анимации.


Анимация 08

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


Изображение 07

Теперь, когда всё готово, мы можем вернуться к коду 03 и изменить его, как показано в следующем фрагменте. Не забывайте, что мы не проверяем возможные сбои при загрузке. Поэтому проверьте используемые названия, чтобы избежать ошибок в дальнейшем.

                                   .
                                   .
                                   .
58. //+------------------------------------------------------------------+
59. //| Функция инициализации пользовательского индикатора                         |
60. //+------------------------------------------------------------------+
61. int OnInit(void)
62.   {
63.    const string     szTemplates[] =
64.      {
65.       "Stochastic.tpl",
66.       "Stochastic FULL.tpl"
67.      };
68. 
69.    gl_Multi = new C_MultiChart(szTemplates);
70. 
71.    return(INIT_SUCCEEDED);
72.   }
73. //+------------------------------------------------------------------+
                                   .
                                   .
                                   .

Фрагмент 02

Обратите внимание, как легко было изменить код, ведь всё, что нам нужно сделать, — это добавить имя шаблона. Это делается в строке 64. Результат, однако, очень, очень интересный, как вы можете видеть на следующей анимации.


Анимация 09

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


Изображение 08

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


Заключительные замечания

Наконец, мы должным образом объяснили то, что я показывал в одной из своих первых статей. Здесь весь акцент сделан на дидактическом аспекте: показано, как можно применять несколько индикаторов в одном и том же подокне. Я знаю, что многим это может показаться странным или даже совершенно абсурдным, поскольку обычно такого не увидишь. Однако именно такого рода знание и определяет разницу между тем, чтобы знать, и тем, чтобы считать, что уже знаешь.

Надеюсь, вы смогли проследить ход объяснения. Как я уже упоминал, в приложенном файле НЕ БУДЕТ ШАБЛОНОВ, но, следуя приведённым здесь указаниям, любой сможет добиться довольно интересных результатов. Помните, что коды, содержащиеся в приложенном файле, не будут выполнять никакой проверки для выявления возможных ошибок. Например, если вы попытаетесь использовать файл шаблона, которого не существует. Пусть каждый реализует такую проверку так, как сочтёт наиболее подходящим.

Файл MQ5 Описание
Indicators\Code 01 Базовая демонстрация
Indicators\Code 02 Базовая демонстрация
Indicators\Code 03 Базовая демонстрация


Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/17519

Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Индикатор средней цены входа маркет-мейкера Индикатор средней цены входа маркет-мейкера
В статье исследуется, можно ли по обычным данным MetaTrader 5 оценить среднюю стоимость позиции пассивной стороны рынка, если реальные сделки и позиции маркет-мейкеров недоступны. Пошагово строится модель: от оценки направленного потока и виртуальной позиции до Inventory Basis, Reservation Price по Avellaneda–Stoikov и адаптивных Bid/Ask с учётом токсичности потока.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Инжиниринг признаков для машинного обучения (Часть 4): Реализация временных признаков в MQL5 Инжиниринг признаков для машинного обучения (Часть 4): Реализация временных признаков в MQL5
Применение границ сессий из Python к временным меткам брокера в MQL5 приводит на любом брокере с часовым поясом, отличным от UTC, к ошибочной классификации принадлежности к сессиям на два–три часа и искажает флаги сессий по всей истории бэктеста. Мы реализуем файл CTimeFeatures.mqh, содержащий классы CRingBuffer и CTimeFeatures, с тремя методами, доступными для советников: Initialize (фиксация смещения относительно UTC и настройка частотного фильтра), Update (добавление логарифмической доходности в кольцевые буферы для соответствующих торговых сессий) и Calculate (циклическое кодирование, флаги торговых сессий и волатильность сессий). Результат — плоский массив типа double, совместимый по формату с Python-функцией get_time_features для таймфреймов меньше часа, часовых и дневных таймфреймов.