Español Português
preview
От начального к среднему уровню: Технические индикаторы (II)

От начального к среднему уровню: Технические индикаторы (II)

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

Введение

В предыдущей статье «От начального к среднему уровню: Технические индикаторы (I)» мы рассмотрели, как можно реализовать код, цель которого — показать, как использовать технический индикатор. Индикаторы, использование которых объяснялось в той статье, встроены в MetaTrader 5 и поддерживаются платформой. Доступ к ним из MQL5 можно получить очень просто и напрямую.

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

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

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

Итак, пришло время отложить в сторону всё, что может вас отвлекать, и сосредоточиться на том, что будет объясняться в этой статье.


Технические индикаторы (II)

В предыдущей статье мы завершили рассмотрение кода, показанного ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_applied_price   PRICE_CLOSE
08. #property indicator_buffers         1
09. #property indicator_plots           1
10. #property indicator_type1           DRAW_LINE
11. #property indicator_color1          clrRed
12. #property indicator_style1          STYLE_SOLID
13. #property indicator_width1          1
14. #property indicator_label1          "EMA"
15. //+------------------------------------------------------------------+
16. input uint              def_nPeriods   = 9;           //Период:
17. input ENUM_MA_METHOD    def_MA_Method  = MODE_EMA;    //Метод:
18. //+------------------------------------------------------------------+
19. double   Buff_iMA[];
20. int      Handle;
21. //+------------------------------------------------------------------+
22. int OnInit(void)
23. {
24.    SetIndexBuffer(0, Buff_iMA, INDICATOR_DATA);
25.    if ((Handle = iMA(NULL, 0, def_nPeriods, 0, def_MA_Method, _AppliedTo)) == INVALID_HANDLE)
26.    {
27.       Print("Could not start the indicator...");
28.       return INIT_FAILED;
29.    };
30. 
31.    return INIT_SUCCEEDED;
32. };
33. //+------------------------------------------------------------------+
34. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
35. {
36.     ArrayInitialize(Buff_iMA, EMPTY_VALUE);
37.    CopyBuffer(Handle, 0, 0, def_nPeriods, Buff_iMA);
38. 
39.    return rates_total;
40. };
41. //+------------------------------------------------------------------+
42. void OnDeinit(const int reason)
43. {
44.    if (Handle != INVALID_HANDLE) 
45.       IndicatorRelease(Handle);
46. };
47. //+------------------------------------------------------------------+

Код 01

Конечно, я не буду здесь повторять объяснение, приведённое в той статье, о том, какой результат даст выполнение этого кода 01 в MetaTrader 5. Но, думаю, у многих из вас возникло любопытство по поводу одного конкретного вопроса. Вот он: предположим, что вы хотите добавить несколько скользящих средних в один индикатор. Это избавит вас от необходимости настраивать каждую из них по отдельности всякий раз, когда вы захотите добавить их к любому инструменту. Я говорю об этом потому, что у некоторых трейдеров есть заранее заданный набор скользящих средних и индикаторов, которые должны постоянно отображаться на графике. Однако они не хотят или не любят использовать шаблоны для создания такого вида графика. Скоро мы увидим, как это делается. Но пока давайте сосредоточимся на текущей статье.

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

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

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_applied_price   PRICE_CLOSE
08. //+----------------+
09. #property indicator_buffers         2
10. #property indicator_plots           2
11. //+----------------+
12. #property indicator_type1           DRAW_LINE
13. #property indicator_color1          clrRed
14. #property indicator_style1          STYLE_SOLID
15. #property indicator_width1          2
16. //+----------------+
17. #property indicator_type2           DRAW_LINE
18. #property indicator_color2          clrBlue
19. #property indicator_style2          STYLE_SOLID
20. #property indicator_width2          2
21. //+------------------------------------------------------------------+
22. double   Buff_iMA_01[],
23.          Buff_iMA_02[];
24. int      Handle_01,
25.          Handle_02;
26. //+------------------------------------------------------------------+
27. int OnInit(void)
28. {
29.    SetIndexBuffer(0, Buff_iMA_01, INDICATOR_DATA);
30.    SetIndexBuffer(1, Buff_iMA_02, INDICATOR_DATA);
31. 
32.    if ((Handle_01 = iMA(NULL, NULL, 9, 0, MODE_EMA, _AppliedTo)) == INVALID_HANDLE)
33.    {
34.       Print("Could not start the indicator...");
35.       return INIT_FAILED;
36.    };
37.    if ((Handle_02 = iMA(NULL, NULL, 20, 0, MODE_SMA, _AppliedTo)) == INVALID_HANDLE)
38.    {
39.       Print("Could not start the indicator...");
40.       return INIT_FAILED;
41.    };
42. 
43.    return INIT_SUCCEEDED;
44. };
45. //+------------------------------------------------------------------+
46. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
47. {
48.     ArrayInitialize(Buff_iMA_01, EMPTY_VALUE);
49.     ArrayInitialize(Buff_iMA_02, EMPTY_VALUE);
50. 
51.    CopyBuffer(Handle_01, 0, 0, 9, Buff_iMA_01);
52.    CopyBuffer(Handle_02, 0, 0, 20, Buff_iMA_02);
53. 
54.    return rates_total;
55. };
56. //+------------------------------------------------------------------+
57. void OnDeinit(const int reason)
58. {
59.    if (Handle_01 != INVALID_HANDLE) 
60.       IndicatorRelease(Handle_01);
61.    if (Handle_02 != INVALID_HANDLE) 
62.       IndicatorRelease(Handle_02);
63. };
64. //+------------------------------------------------------------------+

Код 02

Когда мы запускаем этот код 02 и применяем его к графику, то получаем результат, похожий на показанный ниже.

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

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

Первая проблема, которую вы, мой уважаемый читатель, можете здесь заметить, заключается в том, что между строками 12 и 15 у нас есть несколько настроек. Практически те же самые настройки повторяются в строках с 17 по 20. И всё это потому, что мы определяем только две скользящие средние. Подумайте, что было бы, если бы скользящих средних было больше. Следующий момент, который, на мой взгляд, излишне усложняет код, — это объявления в строках с 22 по 25. Обратите внимание, что они также зависят от количества скользящих средних, которые мы собираемся добавить на график.

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

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

Таким образом, наша цель — показать вам альтернативу, которая позволит сократить все эти усилия и даст компилятору MQL5 возможность решить за нас большую часть этих проблем, если не все. Единственное, что нам действительно нужно будет сделать, — это указать, какие скользящие средние следует использовать. А дальше всё пойдёт само собой, потому что нам больше не придётся беспокоиться абсолютно ни о чём.

Хорошо, теперь мне действительно любопытно, как вы собираетесь решить все эти вопросы. Для некоторых я даже могу представить, как их решить. Однако в других случаях, например с проблемой в строках девять и десять, я понятия не имею, как это сделать. Тогда пойдёмте со мной и узнаем, как мы можем это решить. Здесь мы рассмотрим лишь один из многих возможных способов сделать одно и то же. Помните: главное — результат. Способ его достижения имеет мало значения.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_applied_price   PRICE_CLOSE
08. //+----------------+
09. #property indicator_buffers         2
10. #property indicator_plots           2
11. //+----------------+
12. #property indicator_type1           DRAW_LINE
13. #property indicator_color1          clrRed
14. #property indicator_style1          STYLE_SOLID
15. #property indicator_width1          2
16. //+----------------+
17. #property indicator_type2           DRAW_LINE
18. #property indicator_color2          clrBlue
19. #property indicator_style2          STYLE_SOLID
20. #property indicator_width2          2
21. //+------------------------------------------------------------------+
22. struct stInfos
23. {
24.    double         Buff_iMA[];
25.    int            Handle;
26.    uint           nPeriods;
27.    ENUM_MA_METHOD Method;
28. }Infos[2];
29. //+------------------------------------------------------------------+
30. int OnInit(void)
31. {
32.    Infos[0].nPeriods = 9;
33.    Infos[0].Method = MODE_EMA;
34.    Infos[1].nPeriods = 20;
35.    Infos[1].Method = MODE_SMA;
36.    for (uint c = 0; c < Infos.Size(); c++)
37.    {
38.       SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA);
39.       if ((Infos[c].Handle = iMA(NULL, NULL, Infos[c].nPeriods, 0, Infos[c].Method, _AppliedTo)) == INVALID_HANDLE)
40.       {
41.          Print("Could not start the indicator...");
42.          return INIT_FAILED;
43.       };
44.    }
45. 
46.    return INIT_SUCCEEDED;
47. };
48. //+------------------------------------------------------------------+
49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
50. {
51.    for (uint c = 0; c < Infos.Size(); c++)
52.    {
53.       ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE);
54.       CopyBuffer(Infos[c].Handle, 0, 0, Infos[c].nPeriods, Infos[c].Buff_iMA);
55.    }
56. 
57.    return rates_total;
58. };
59. //+------------------------------------------------------------------+
60. void OnDeinit(const int reason)
61. {
62.    for (uint c = 0; c < Infos.Size(); c++)
63.       IndicatorRelease(Infos[c].Handle);
64. };
65. //+------------------------------------------------------------------+

Код 03

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

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

Итак, давайте посмотрим, каким будет следующее улучшение, которое мы здесь реализуем. Чтобы понять, что мы будем делать, вам нужно вернуться к коду 03 и посмотреть на содержимое между строками 32 и 35. То, что находится в этих строках, вовсе не обязательно должно быть именно там. Кроме того, переменные, объявленные в строках 26 и 27, не обязательно определять именно таким образом.

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

"Но подождите-ка минутку. Как такое возможно? Если мы преобразуем эти значения в константы, как сделать так, чтобы код не стал ещё больше? Честно говоря, я не знаю, как сделать что-то подобное". Что ж, именно для этого мы здесь, мой дорогой читатель. Цель как раз в том, чтобы показать вам, как можно изменить код и сделать его гораздо проще и удобнее. Не стоит тратить время на написание кучи кода, если всё можно сделать гораздо быстрее и без лишних сложностей.

Таким образом, чтобы достичь нашей цели, мы изменим этот код 03 так, как показано ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_applied_price   PRICE_CLOSE
08. #property indicator_buffers         2
09. #property indicator_plots           2
10. //+----------------+
11. #property indicator_type1           DRAW_LINE
12. #property indicator_color1          clrRed
13. #property indicator_style1          STYLE_SOLID
14. #property indicator_width1          2
15. //+----------------+
16. #property indicator_type2           DRAW_LINE
17. #property indicator_color2          clrBlue
18. #property indicator_style2          STYLE_SOLID
19. #property indicator_width2          2
20. //+------------------------------------------------------------------+
21. const struct stAverange
22. {
23.     uint           nPeriods;
24.     ENUM_MA_METHOD Method;
25. }Averange[] =
26. {
27.     {9, MODE_EMA},
28.     {20, MODE_SMA}
29. };
30. //+----------------+
31. struct stInfos
32. {
33.     double   Buff_iMA[];
34.     int      Handle;
35. }Infos[Averange.Size()];
36. //+------------------------------------------------------------------+
37. int OnInit(void)
38. {
39.     for (uint c = 0; c < Averange.Size(); c++)
40.     {
41.         SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA);
42.         if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE)
43.         {
44.             Print("Could not start the indicator...");
45.             return INIT_FAILED;
46.         };
47.     }
48. 
49.     return INIT_SUCCEEDED;
50. };
51. //+------------------------------------------------------------------+
52. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
53. {
54.     for (uint c = 0; c < Averange.Size(); c++)
55.     {
56.         ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE);
57.         CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA);
58.     }
59. 
60.     return rates_total;
61. };
62. //+------------------------------------------------------------------+
63. void OnDeinit(const int reason)
64. {
65.     for (uint c = 0; c < Averange.Size(); c++)
66.         IndicatorRelease(Infos[c].Handle);
67. };
68. //+------------------------------------------------------------------+

Код 04

И вновь результат выполнения этого кода 04 остаётся таким, как показано на изображении 01. Однако посмотрите, что мы здесь сделали. Теперь у нас в строке 21 находится объявление константной структуры. Однако, вопреки тому, что вы, возможно, представляете, здесь КОНСТАНТОЙ ЯВЛЯЕТСЯ НЕ СТРУКТУРА, а объявление, сделанное в строке 25. "Погодите-ка. Теперь я хочу, чтобы вы мне всё это как следует объяснили. Потому что я ясно вижу, что объявление находится в строке 21, а не в строке 25. Тогда как объявление в строке 25 может быть константным, а в строке 21 — нет?" Хорошо, это требует объяснения. Проблема в том, что здесь я представляю код в более компактном формате. Чтобы лучше это понять, давайте воспользуемся небольшим скриптом. Он показан ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     struct stInfos
07.     {
08.         uint u32;
09.         double pf64;
10.     };
11. 
12.     const stInfos m1[] = 
13.     {
14.         {10, 1.3},
15.         {15, 3.14}
16.     };
17. 
18.     for (uint c = 0; c < m1.Size(); c++)
19.         Print("[", c, "]:  ", m1[c].u32, "   ", m1[c].pf64);
20. }
21. //+------------------------------------------------------------------+

Код 05

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

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

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

Это хорошо видно, если рассмотреть эти объявления по отдельности. Но что произошло бы, если бы мы объединили их, как показано в коде 04? Что бы вы сказали по этому поводу? Очевидно, можно было бы подумать, что структура стала бы считаться константой. Не так ли? Однако эта идея совершенно ошибочна. Это связано с тем, что вы НЕ ВИДИТЕ КОД, а видите лишь то, что там показано. Чтобы это действительно стало вам ясно, мой дорогой читатель, давайте изменим код 05 и проведём небольшой тест, чтобы это доказать. Этот новый код показан ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const struct stInfos
07.     {
08.         uint u32;
09.         double pf64;
10.     } m1[] = 
11.     {
12.         {10, 1.3},
13.         {15, 3.14}
14.     };
15. 
16.     stInfos var;
17. 
18.     var.u32 = 5;
19.     var.pf64 = 2.59;
20. 
21.     for (uint c = 0; c < m1.Size(); c++)
22.         Print("[", c, "]:  ", m1[c].u32, "   ", m1[c].pf64);
23.     Print("[ variable ]:  ", var.u32, "   ", var.pf64);
24. }
25. //+------------------------------------------------------------------+

Код 06

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

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

"Но как это понимать? Для меня это совершенно лишено смысла". Если вы всё ещё этого не поняли, спокойно изучите этот код 06 и поэкспериментируйте с ним, пока не увидите, что происходит. На практике, по сути, ключевое слово const, которое появляется в строке шесть, связано не с объявлением структуры, а с переменной, объявленной в строке десять. Однако, поскольку ключевое слово const появляется в строке шесть перед объявлением переменной в строке десять, компилятор сразу распознаёт, что в строке десять у нас объявление константы. Поэтому в строке 16 мы можем сделать объявление простой переменной, содержимое которой должно соответствовать структуре, определённой в строке шесть.

"Хорошо, кажется, теперь я это понял. Тем не менее мне это по-прежнему кажется немного запутанным". Итак, остались ли у вас, мой дорогой читатель, ещё какие-нибудь сомнения по этому вопросу? "Ммм, я кое-что заметил, и есть одна деталь в этих кодах, которую я не до конца понимаю. Помимо вопроса о константе, который я позже ещё попрактикую, чтобы лучше понять, сейчас у меня вопрос связан с этим объявлением или, точнее, с тем, как инициализируется этот массив. Я вижу, что вы располагаете значения определённым образом. Почему?" Что ж, это вполне уместный вопрос. И поскольку до сих пор это не объяснялось, думаю, оно заслуживает надлежащего объяснения.

Обычно, когда мы инициализируем константный массив, мы используем то, что уже объяснялось в статье «От начального уровня к среднему: массив (II)». Однако то, что мы там увидели, применимо только к дискретным значениям. В случае со структурами необходимо учитывать некоторые дополнительные особенности.

В этом случае мы сосредоточимся на коде 06. Каждый элемент, так сказать, представляет собой блок, который должен содержать соответствующую структуру. А теперь обратите внимание, потому что это важно. Как правило, чтобы код был как можно более читаемым, мы определяем по одному блоку структуры в каждой строке. И внутри этого блока порядок элементов определяет результат, который мы получим.

Но как это понимать? Итак, мой дорогой читатель, в некоторых языках программирования, например в Python, при объявлении можно указать, какому полю соответствует каждое конкретное значение. Это будет примерно так, как показано в строках 18 и 19 кода 06. Однако во многих языках программирования, в том числе в MQL5, порядок, в котором объявлены поля структуры, определяет порядок, в котором должны располагаться значения внутри блока инициализации.

То есть, поскольку в структуре, объявленной в строке 6 кода 06, первый тип, объявленный в строке 8, является целочисленным, первое значение внутри блока в строке 12 ТАКЖЕ должно быть целым числом. И не только это: первый элемент внутри блока будет, по сути, соответствовать первому элементу самой структуры. Это может показаться ерундой. Но подобные вещи нужно соблюдать неукоснительно. Во многих программах или фрагментах кода возникают неверные значения именно потому, что программист поместил значение элемента не на своё место внутри блока. И тем самым мы можем полностью изменить поведение нашего кода.

Чтобы вы могли представить, насколько вредным это может быть, давайте создадим очень простой пример. В одном случае блок создаётся правильно, а в другом — неправильно. Этот пример показан ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const struct stAverange
07.     {
08.         uint    nPeriods,
09.                 Method;
10.     }Averange[] =
11.     {
12.         {9, MODE_EMA},
13.         {MODE_SMA, 20}
14.     };
15. 
16.     for (uint c = 0; c < Averange.Size(); c++)
17.         Print("[", c, "]:  Periods=> ", Averange[c].nPeriods, "   ", EnumToString((ENUM_MA_METHOD)Averange[c].Method));
18. }
19. //+------------------------------------------------------------------+

Код 07

При выполнении этот код 07 даст результат, показанный ниже.

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

Итак, возможно, вы думаете: «Но кто же может быть настолько невнимательным, чтобы не заметить, что в этом коде 07 что-то не так?» А я отвечу: каждый из нас может допустить подобную ошибку, мой дорогой читатель. И, как ни странно, это встречается гораздо чаще, чем вы могли бы себе представить. Здесь, конечно, я намеренно довожу ситуацию до крайности, поскольку в одном случае мы используем целое число, а в другом — перечисление.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_applied_price   PRICE_CLOSE
08. #property indicator_buffers         2;
09. #property indicator_plots           2;
10. //+------------------------------------------------------------------+
11. const struct stAverange
12. {
13.    uint           nPeriods;
14.    ENUM_MA_METHOD Method;
15.    color          cor;
16.    string         szLabel;
17. }Averange[] = 
18. {
19.    {9, MODE_EMA, clrRed, "exponential of %d"},
20.    {20, MODE_SMA, clrGreen, "arithmetica of %d"}
21. };
22. //+----------------+
23. struct stInfos
24. {
25.    double   Buff_iMA[];
26.    int      Handle;
27. }Infos[Averange.Size()];
28. //+------------------------------------------------------------------+
29. int OnInit(void)
30. {
31.    for (uint c = 0; c < Averange.Size(); c++)
32.    {
33.       SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA);
34.       PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE);
35.       PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID);
36.       PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 2);
37.       PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor);
38.       PlotIndexSetString(c, PLOT_LABEL, StringFormat(Averange[c].szLabel, Averange[c].nPeriods));
39.       if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE)
40.       {
41.          Print("Could not start the indicator...");
42.          return INIT_FAILED;
43.       };
44.    }
45. 
46.    return INIT_SUCCEEDED;
47. };
48. //+------------------------------------------------------------------+
49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
50. {
51.    for (uint c = 0; c < Averange.Size(); c++)
52.    {
53.        ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE);
54.       CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA);
55.    }
56. 
57.    return rates_total;
58. };
59. //+------------------------------------------------------------------+
60. void OnDeinit(const int reason)
61. {
62.    for (uint c = 0; c < Averange.Size(); c++)
63.       IndicatorRelease(Infos[c].Handle);
64. };
65. //+------------------------------------------------------------------+

Код 08

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

Обратите внимание на фрагмент между строками 34 и 38. Эти строки динамически выполняют то, что раньше статически делалось с помощью объявлений в коде 04. Конечно, эта строка 38 может показаться вам довольно странной. Но для компилятора в этом есть полный смысл. Поэтому я продвигаюсь с ней шаг за шагом, чтобы вы действительно могли понять эту концепцию. Обратите внимание, что в строке 16 мы объявляем новый элемент, который станет частью структуры. Этот элемент получает разные значения, определённые в строках 19 и 20. Однако обратите внимание на эти две строки. Они кажутся вам знакомыми?

Если да, поздравляю: значит, вы изучали статьи и практиковались, чтобы научиться делать всё правильно. Для тех, кто не смог этого понять: эти две строки на самом деле представляют собой копию одной из тех строк, которые мы можем использовать в таких функциях, как PrintFormat или StringFormat. Это служит для форматирования строки и создания информации в очень конкретном формате.

Поскольку мы делаем это в строках 19 и 20, в строке 38 мы можем использовать эти константы в качестве первого аргумента функции StringFormat, чтобы отформатировать код, который покажем далее.

Так или иначе, конечный результат будет точно таким, как на изображении 01. Однако здесь есть одно отличие. Когда вы впервые примените индикатор к графику, вы увидите то, что показано на следующем изображении.

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

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

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

Обратите внимание, что теперь, поскольку код в строках с 34 по 38 выполнился в первый раз, у нас появилась информация, которая действительно имеет смысл. И это очень интересно именно по следующей причине. Обратите внимание, что в строках 35, 36 и 37 мы задаём постоянные значения. Однако — и в этом вся интересная часть — эти же значения будут перезаписаны теми, которые будут заданы в окне, показанном на изображении 06. То есть даже если вы зададите их определённым образом при написании кода, MetaTrader 5 будет считать приоритетными значения, заданные пользователем. Это даёт нам гораздо большую гибкость, чем многие могли бы себе представить, просто взглянув на код.

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

                   .
                   .
                   .
10. //+------------------------------------------------------------------+
11. const struct stAverange
12. {
13.    uint           nPeriods;
14.    ENUM_MA_METHOD Method;
15.    color          cor;
16. }Averange[] = 
17. {
18.    {9, MODE_EMA, clrRed},
19.    {20, MODE_SMA, clrGreen}
20. };
21. //+----------------+
                   .
                   .
                   .
37.       PlotIndexSetString(c, PLOT_LABEL, StringFormat("%s of %d", (Averange[c].Method == MODE_EMA ? "Exponential" : "Arithmetica"), Averange[c].nPeriods));
                   .
                   .
                   .

Фрагмент 01

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

Хорошо, всё выглядит очень красиво и замечательно. Но, несмотря на это, в нашем коде по-прежнему есть одно ограничение. Даже этот код 08, который уже выглядит вполне подходящим, всё ещё можно улучшить, чтобы прийти к решению, которое я хочу показать в этой статье. Это связано с тем, что у нас по-прежнему остаётся проблема со строками 8 и 9 этого кода 08. Как видите, нам по-прежнему приходится менять эти числа вручную. Хорошо, но разве нет другого способа, который позволил бы нам ещё больше упростить дело? Да, мой дорогой читатель, есть ещё один способ. Но прежде чем показать его, мне нужно объяснить вам ещё одну вещь. Обратите внимание, что в обеих строках — и в восьмой, и в девятой — у нас задано одно и то же значение. Нельзя ли заменить это объявлением? Что ж, давайте посмотрим. Для этого изменим код, как показано в следующем фрагменте.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #define nAveranges                  2
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_applied_price   PRICE_CLOSE
10. #property indicator_buffers         nAveranges;
11. #property indicator_plots           nAveranges;
12. //+------------------------------------------------------------------+
13. const struct stAverange
14. {
15.    uint           nPeriods;
16.    ENUM_MA_METHOD Method;
17.    color          cor;
18. }Averange[] = 
                   .
                   .
                   .

Фрагмент 02

"Хм, как интересно. Я не знал, что мы можем это делать. Мне это нравится, потому что, если мне понадобится что-то изменить, теперь достаточно просто изменить значение в шестой строке, и всё будет решено. Но, если хорошенько подумать, это не кажется мне таким уж удивительным. Теперь я вспомнил, что в статье «От начального к среднему уровню: определения (II)» говорилось, что определения можно использовать для многих вещей". Это правда, мой дорогой читатель. Но идея использования определения — лишь небольшая деталь, которая поможет вам понять кое-что другое. Теперь давайте изменим этот фрагмент 02 так, чтобы он выглядел, как показано ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. const int nAveranges = 2;
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_applied_price   PRICE_CLOSE
10. #property indicator_buffers         nAveranges;
11. #property indicator_plots           nAveranges;
12. //+------------------------------------------------------------------+
13. const struct stAverange
14. {
15.    uint           nPeriods;
16.    ENUM_MA_METHOD Method;
17.    color          cor;
18. }Averange[] = 
                   .
                   .
                   .

Фрагмент 03

А теперь будьте внимательны, потому что мы собираемся сделать кое-что довольно необычное. Обратите внимание, что единственное, что мы изменили между фрагментом 02 и этим фрагментом 03, — это строка шесть. Изменена только она. Однако, если вы попытаетесь скомпилировать код индикатора, то получите следующее сообщение от компилятора.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. const struct stAverange
07. {
08.    uint           nPeriods;
09.    ENUM_MA_METHOD Method;
10.    color          cor;
11. }Averange[] = 
12. {
13.    {9, MODE_EMA, clrRed},
14.    {20, MODE_SMA, clrGreen},
15.    {50, MODE_SMA, clrBlue},
16.    {200, MODE_SMA, clrBlack}
17. };
18. //+----------------+
19. #property indicator_chart_window
20. #property indicator_applied_price   PRICE_CLOSE
21. #property indicator_buffers         Averange.Size();
22. #property indicator_plots           Averange.Size();
23. //+------------------------------------------------------------------+
24. struct stInfos
25. {
26.    double   Buff_iMA[];
27.    int      Handle;
28. }Infos[Averange.Size()];
29. //+------------------------------------------------------------------+
30. int OnInit(void)
31. {
32.    for (uint c = 0; c < Averange.Size(); c++)
33.    {
34.       SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA);
35.       PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE);
36.       PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID);
37.       PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 2);
38.       PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor);
39.       PlotIndexSetString(c, PLOT_LABEL, StringFormat("%s of %d", (Averange[c].Method == MODE_EMA ? "Exponential" : "Arithmetica"), Averange[c].nPeriods));
40.       if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE)
41.       {
42.          Print("Could not start the indicator...");
43.          return INIT_FAILED;
44.       };
45.    }
46. 
47.    return INIT_SUCCEEDED;
48. };
49. //+------------------------------------------------------------------+
50. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
51. {
52.    for (uint c = 0; c < Averange.Size(); c++)
53.    {
54.        ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE);
55.       CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA);
56.    }
57. 
58.    return rates_total;
59. };
60. //+------------------------------------------------------------------+
61. void OnDeinit(const int reason)
62. {
63.    for (uint c = 0; c < Averange.Size(); c++)
64.       IndicatorRelease(Infos[c].Handle);
65. };
66. //+------------------------------------------------------------------+

Код 09

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

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

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

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


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

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

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

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

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

Прикрепленные файлы |
Anexo.zip (3.6 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разработка индикатора рыночной энтропии: торговая система, основанная на теории информации Разработка индикатора рыночной энтропии: торговая система, основанная на теории информации
В статье рассматривается разработка индикатора рыночной энтропии, основанного на принципах теории информации, для измерения неопределённости и информационного содержания финансовых рынков. Применяя к ценовым движениям такие понятия, как энтропия Шеннона, индикатор количественно определяет, находится ли рынок в структурированном (трендовом), переходном или хаотичном состоянии.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Нейронная сеть на практике: Градиентный спуск Нейронная сеть на практике: Градиентный спуск
В этой статье я постараюсь как можно проще и нагляднее изложить один из самых спорных вопросов, возникающих при обсуждении нейронных сетей: как найти наилучшую возможную точку или минимальную функцию потерь. Я покажу разницу между линейной регрессией и градиентным спуском. Оба случая довольно просты и служат примером того, что не всегда путь, который кажется очевидным, действительно является лучшим.