English Español Deutsch Português
preview
Разработка системы репликации (Часть 39): Прокладываем путь (III)

Разработка системы репликации (Часть 39): Прокладываем путь (III)

MetaTrader 5Примеры | 17 июня 2024, 13:50
357 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 38): Прокладываем путь (II), я объяснил и продемонстрировал, как отправлять данные, в нашем случае из советника в индикатор, с целью настроить последний так, чтобы он мог возвращать нам какую-то правдоподобную информацию.

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

Здесь есть несколько разных вещей. Некоторые из которых довольно просты в объяснении, другие не очень. Соответственно часть из них будет продемонстрировать легко, а на другую часть потребуется больше времени. Но так или иначе, в этой статье мы создадим необходимую основу для построения нашего Chart Trader. Chat Trader, который будет разработан здесь, в системе репликации/моделирования, будет очень похож на тот, который был представлен в прошлой статье.

Однако отличаться от того, что мы видели в статье:  Разработка торгового советника с нуля (Часть 30): CHART TRADE теперь в качестве индикатора?!. То, что мы будем делать здесь, может показаться детской игрой, благодаря выбранному способу создания того же самого Chart Trader. Но не будем портить сюрприз. Иначе будет неинтересно. Чтобы упростить объяснение того, что мы будем делать, сначала необходимо разобраться еще в нескольких вещах, которые и будут рассмотрены в этой статье.


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

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

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

И первое, что мы сделаем – создадим заголовочный файл, который приведен ниже:

1. #property copyright "Daniel Jose"
2. #property link      ""
3. //+------------------------------------------------------------------+
4. #define def_ShortName        "SWAP MSG"
5. //+------------------------------------------------------------------+

Этот заголовочный файл будет называться Defines.mqh. Его необходимо сохранить в каталоге Includes, в подпапке Mode Swap. Единственная строка, которая имеет для нас какое-то значение – это строка 4. В ней мы задаем имя, которое будет использоваться как в советнике, так и в индикаторе, просто для удобства использования. Так как можно указать имя в индикаторе и при этом забыть указать то же имя в советнике. И я сейчас не про название файла. Я имею в виду имя, под которым индикатор будет распознаваться MetaTrader 5.

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

Исходный код индикатора:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 0
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
33. {
34.     return rates_total;
35. }
36. //+------------------------------------------------------------------+

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

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

Исходный код советника:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Mode Swap\\Swap MSG.ex5"
08. #resource "\\Indicators\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     m_id = ChartID();
18. 
19.     EraseIndicator();
20.     m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
21.     ChartIndicatorAdd(m_id, 0, m_handle);
22.     
23.     Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed");
24.     
25.     return INIT_SUCCEEDED;
26. }
27. //+------------------------------------------------------------------+
28. void OnDeinit(const int reason)
29. {
30.     EraseIndicator();
31. }
32. //+------------------------------------------------------------------+
33. void OnTick()
34. {
35. }
36. //+------------------------------------------------------------------+
37. void EraseIndicator(void)
38. {
39.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE) return;
40.     ChartIndicatorDelete(m_id, 0, def_ShortName);
41.     IndicatorRelease(m_handle);
42. }
43. //+------------------------------------------------------------------+

Здесь у нас кое-что есть, что очень отличается, но при внимательном рассмотрении – не так уж и сильно. Похоже, единственная реальная разница заключается в наличии функции EraseIndicator. Это можно увидеть в строке 37. Эта процедура вызывается в двух моментах: первый находится в строке 19, а второй — в строке 30. Думаю, ни у кого не возникает вопросов, почему она вызывается в строке 30. Но вызов со строки 19? Известна ли вам причина этого вызова?

Причина вызова из строки 19 заключается именно в том, чтобы обеспечить синхронизацию между значением, сообщаемым в советнике, и значением, которое будет отправлено на индикатор. Но есть кое-что, что требует спокойного рассмотрения. Посмотрите на строку 39 кода советника. Обратите внимание, что при вызове процедуры, как в строке 19 так и в строке 30, мы проверяем, находится ли индикатор на графике. Если его нет, код завершит свое выполнение. При этом как строка 40, удаляющая индикатор с графика, так и строка 41, отпускающая хэндл, не будут исполнены.

Но зачем нам нужно удалять индикатор с графика, используя строку 40? Причина этого находится не в коде советника, а в коде индикатора.

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

Но вернемся к коду советника. Есть идеи насчет того, что представляют собой строки 07 и 08? И почему строка 20 отличается от той, что рассматривалась в предыдущей статье?

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

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

Что же это за деталь? Это довольно тонкий момент, который почти незаметен. Именно поэтому каждый хороший программист еще и очень хороший наблюдатель. Итак, переходим к детали. Рассмотрим строку 20 кода советника. Обратите внимание, что в объявлении третьего параметра появляется небольшая цепочка (:: ). Эта небольшая цепочка, которая идет перед определением, представляет собой оператор разрешения контекста.

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

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

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

20.     m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00);

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

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

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

Давайте реализуем одну очень простую, вот прямо очень простую, версию коммуникации. Чтобы объяснить это очень подробно и последовательно, перейдем к новой теме.


Читая данные с индикатора

По сути, существует только один способ чтения любой информации, представленной в индикаторе, – считывание его буфера. Но и здесь есть маленький нюанс: функция CopyBuffer не позволяет нам работать с любым типом данных, присутствующих в буфере. Не в естественной форме. И взглянув на объявление этой функции, вы поймете, что я имею в виду.

Определение функции CopyBuffer:

int  CopyBuffer(
   int       indicator_handle,     // Индикаторный хэндл
   int       buffer_num,           // Номер индикаторного буфера
   int       start_pos,            // Начальная позиция
   int       count,                // Количество для копирования
   double    buffer[]              // Целевой массив для копирования
   );

Чтобы стало понятней, можете посмотреть выше, как объявлен один из вариантов функции CopyBuffer. Я говорю один вариант, потому что их три, но в действительности нам важна последняя переменная, присутствующая в объявлении. Обратите внимание на ее тип – double, то есть, мы можем возвращать только значения типа double. Это только в теории, потому что на практике мы можем возвращать что угодно. И в статье Разработка торгового советника с нуля (Часть 17): Доступ к данным в сети (III) я показал способ обойти это ограничение.

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

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

Исходный код индикатора

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. double m_Buff[];
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     m_id = ChartID();
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
21.     if (ChartWindowFind(m_id, def_ShortName) != -1)
22.     {
23.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
24.             Print("Only one instance is allowed...");
25.             return INIT_FAILED;
26.     }
27.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
28.     Print("Indicator configured with the following value:", user00);
29.     
30.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
31.     ArrayInitialize(m_Buff, EMPTY_VALUE);
32. 
33.     return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[rates_total - 1] = user00 * 2.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

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

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

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

Обратив внимание на эту строку, вы заметите, что я не указываю ни на одну область в буфере. Но почему?

Причина в том, что нам нужно каким-то образом стандартизировать процесс. Если разместить информацию (в данном случае результат умножения значения, сообщаемого советником, на 2.0), может быть трудно определить, где искать действительно полезную информацию. Напоминаю, мы продвигаемся не спеша. Но в дальнейшем все усложнится.

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

Чтобы было понятней, обратимся к исходному коду советника, представленному ниже.

Исходный код советника:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
08. #resource "\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     double Buff[];
18.     
19.     m_id = ChartID();
20. 
21.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
22.     {
23.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
24.             ChartIndicatorAdd(m_id, 0, m_handle);
25.     }
26.             
27.     Print ("Buffer reading:", (m_handle == INVALID_HANDLE ? "Error..." : CopyBuffer(m_handle, 0, 0, 1, Buff) > 0 ?  (string)Buff[0] : " Failed."));
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     ChartIndicatorDelete(m_id, 0, def_ShortName);
35.     IndicatorRelease(m_handle);
36. }
37. //+------------------------------------------------------------------+
38. void OnTick()
39. {
40. }
41. //+------------------------------------------------------------------+

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

Во-первых, теперь у нас есть объявление переменной в строке 17. Это будет наш буфер возврата или буфер чтения индикатора. Кроме этой строки 17, у нас также появилась новая строка – 27.

Многим покажется, что эта строка 27 — настоящий хаос, но по сути – это два вложенных тернарных оператора. Сначала проверяем, действителен ли хэндл индикатора; если нет, то в окне сообщений терминала появится соответствующее уведомление. Если хэндл действителен, прочтем одну позицию из буфера индикатора. И какая же позиция будет прочитана? Первая. Не понимаю. Спокойно, расслабьтесь, позже я все объясню. Если нам удастся прочитать буфер, мы выведем на печать содержащееся в нем значение. Если нет, будет выведено еще одно сообщение об ошибке. Оно будет отличаться от первого, указывая на другую причину сбоя.

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


Рассмотрим процесс записи и чтения буфера

Есть изображение, взятое из документации MQL5, которое четко иллюстрирует то, что я собираюсь объяснить. Его можно увидеть ниже:

Рисунок 01

Рисунок 01 – Запись и чтение буфера индикатора

Это изображение практически говорит само за себя. Но все же давайте разберемся, как происходит запись в буфер индикатора, и как она читается из него.

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

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

Фрагмент кода — модель 01:

35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[0] = user00 * 3.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

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

И не при записи, а при попытке прочитать буфер вне кода индикатора, например,в советнике.

Когда выполняется чтение буфера, MetaTrader 5 обычно возвращает максимум 2000 позиций. То есть мы сталкиваемся с тем, что изображено на рисунке 01. Если случайно объем данных, присутствующих в индикаторе, превысит эти 2000 позиций, возникнут проблемы. Помните, что запись осуществлялась в нулевую позицию, но нулевая позиция ЭТО НЕ та же самая нулевая позиция, на которую ссылается CopyBuffer. Для CopyBuffer эта нулевая позиция, которая есть в коде индикатора, на самом деле может быть позицией 2001, и если это так, прочитать нулевую позицию из буфера индикатора не получится. Это значение будет зафиксировано в индикаторе.

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

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

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

Примечательно, что мы можем писать в буфер как в обратном порядке, так и обычным способом, или скорее, следуя порядку: первая информация – первое значение, вторая информация – второе значение. Если же делается наоборот, то будет так: первая информация – последнее значение, вторая информация – последнее значение минус одна позиция, и так далее. Но чтобы упростить интерпретацию, не при записи, а при чтении, давайте делать вещи обычным способом.

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

Фрагмент кода — модель 01:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_ShortName       "SWAP MSG"
04. //+------------------------------------------------------------------+
05. union uCharDouble
06. {
07.     double  dValue;
08.     char    cInfo[sizeof(double)];
09. };
10. //+------------------------------------------------------------------+

Как видим, между строками 05 и 09 у нас есть объединение. Это объединение позволит передавать текстовые данные с использованием значения типа double. Если вы видите это впервые, это может показаться странным. Но мы уже делали такое ранее. Пример этого можно увидеть в статье: Разработка торгового советника с нуля (Часть 17): Доступ к данным в сети (III). Но вернемся к нашему вопросу. Теперь у нас есть способ отправить небольшую строку (string) из индикатора в советник. И причина использования значения double заключается в том, что мы не можем отправить через CopyBuffer значение другого типа. Мы в обязательном порядке должны использовать тип double.

Сделав это изменение в файле Defines.mqh, мы можем перейти к исходному коду индикатора.

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

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. #property indicator_buffers 1
06. //+------------------------------------------------------------------+
07. #include <Mode Swap\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_ShortNameTmp    def_ShortName + "_Tmp"
10. //+------------------------------------------------------------------+
11. input double user00 = 0.0;
12. //+------------------------------------------------------------------+
13. long m_id;
14. double m_Buff[];
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
30.     ArrayInitialize(m_Buff, EMPTY_VALUE);
31. 
32.     return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.     uCharDouble info;
38.     int pos = rates_total - 3;
39.     
40.     StringToCharArray("Config", info.cInfo);
41.     m_Buff[pos + 0] = info.dValue;
42.     m_Buff[pos + 1] = user00 * 2.0;
43.     m_Buff[pos + 2] = user00 * 2.5;
44.             
45.     return rates_total;
46. }
47. //+------------------------------------------------------------------+

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

Теперь давайте разбираться, что происходит. В строке 37 мы объявляем переменную, которая будет использоваться для преобразования строки (string) в значение типа double. Нюанс: длина строки ограничена 8 символами; если информация содержит больше символов, для этого необходимо предоставить массив, всегда учитывая информацию блоками по 8.

В строке 38 мы объявляем переменную, которая будет использоваться для записи информации обычным способом, то есть так, как если бы мы писали текст слева направо. В случае арабского языка, запись будет производиться справа налево. Думаю, идея понятна. На этой же строке 38 мы указываем количество значений типа double, которые будем публиковать. В нашем случае их будет 3.

В строке 40 мы преобразуем строку (string) в массив символов. Так мы получим значение, которое будет использоваться в нашей первой позиции. Затем, в строке 41, мы помещаем это значение в буфер. 

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

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

Исходный код EA, обновленный для чтения более чем одной позиции:

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. //+------------------------------------------------------------------+
04. #include <Mode Swap\Defines.mqh>
05. //+------------------------------------------------------------------+
06. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
07. #resource "\\" + def_SWAP
08. //+------------------------------------------------------------------+
09. input double user00 = 2.2;
10. //+------------------------------------------------------------------+
11. int m_handle;
12. long m_id;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {   
16.     double Buff[];
17.     uCharDouble Info;
18.     int iRet;
19.     string szInfo;
20.     
21.     m_id = ChartID();
22.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
23.     {
24.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
25.             ChartIndicatorAdd(m_id, 0, m_handle);
26.     }
27.     ArraySetAsSeries(Buff, false);
28.     if (m_handle == INVALID_HANDLE) szInfo = "Invalid handler to read the buffer.";
29.     else
30.     {
31.             if ((iRet = CopyBuffer(m_handle, 0, 0, 3, Buff)) < 3) szInfo = "Buffer reading failed.";
32.             else
33.             {
34.                     Info.dValue = Buff[0];
35.                     szInfo = CharArrayToString(Info.cInfo) + " [ " + (string)Buff[1] + " ] [ " + (string)Buff[2] + " ]";
36.             }
37.     }
38.     Print("Return => ", szInfo);
39.             
40.     return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     ChartIndicatorDelete(m_id, 0, def_ShortName);
46.     IndicatorRelease(m_handle);
47. }
48. //+------------------------------------------------------------------+
49. void OnTick()
50. {
51. }
52. //+------------------------------------------------------------------+

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

Между строками 17 и 19 есть объявление новых переменных, которые будут использоваться для расшифровки информации, содержащейся в буфере. Это может показаться странным, но на самом деле информация, присутствующая в буфере, закодирована. Поскольку мы повторяем строку в одной из позиций.

Но наш интерес на самом деле находится между строками 27 и 38. Именно в этой части происходит чтение буфера. Начнем по порядку.

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

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

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

Когда мы записываем данные в буфер внутри индикатора, мы знаем, сколько позиций информации отправляем. Строка 31 делает именно это: считывает ожидаемое количество позиций. Целесообразней было бы сделать комбинацию с использованием заголовочного файла. Таким образом, если количество позиций с информацией увеличится, то и советник, и индикатор всегда будут об этом осведомлены. Но поскольку здесь мы просто приводим примеры для объяснения концепции, которая будет использована позже, пока это можно игнорировать.

Если количество прочитанных позиций меньше ожидаемого, это будет ошибкой, и информация об этом появится в терминале. Если количество соответствует ожиданиям, в строке 34 мы преобразуем информацию, заданную в виде double, в строку (string). Таким образом мы обратно восстанавливаем информацию, помещенную индикатором в буфер.

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

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

После завершения преобразования, мы используем строку 35 для формирования сообщения, которое будет отображено в терминале. Все просто.


Заключение

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

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

Очень надеюсь на понимание приведенных концепций и реальной значимости последних трех статей. Далее все станет значительно сложнее.

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

Увидимся в следующей статье, где мы начнем интегрировать Chart Trader для использования в системе репликации/моделирования.



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

Прикрепленные файлы |
EA.mq5 (1.81 KB)
swap.mq5 (1.72 KB)
Defines.mqh (0.24 KB)
Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть II): Сборка и результаты Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть II): Сборка и результаты
Во второй части статьи мы соберем химические операторы в единый алгоритм и представим подробный анализ результатов его работы. Узнаем, как метод оптимизации химическими реакциями (CRO) справился с вызовом в решении сложных задач на тестовых функциях.
Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть I): Химия процессов в оптимизации Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть I): Химия процессов в оптимизации
В первой части данной статьи мы окунемся в мир химических реакций и откроем новый подход к оптимизации! Метод оптимизации химическими реакциями (CRO) использует для достижения эффективных результатов принципы, определяемые законами термодинамики. Мы раскроем секреты декомпозиции, синтеза и других химических процессов, которые стали основой этого инновационного метода.
Разработка системы репликации (Часть 40): Начало второй фазы (I) Разработка системы репликации (Часть 40): Начало второй фазы (I)
Сегодня поговорим о новой фазе системы репликации/моделирования. На данном этапе разговор станет поистине интересным, а содержанием довольно насыщенным. Я настоятельно рекомендую вам внимательно прочитать статью и пользоваться приведенными в ней ссылками. Это поможет вам лучше понять содержание.
Возможности Мастера MQL5, которые вам нужно знать (Часть 12): Полином Ньютона Возможности Мастера MQL5, которые вам нужно знать (Часть 12): Полином Ньютона
Полином Ньютона, который создает квадратные уравнения из набора нескольких точек, представляет собой архаичный, но интересный подход к рассмотрению временных рядов. В этой статье мы попытаемся изучить, какие аспекты этого подхода могут быть полезны трейдерам, а также устранить его ограничения.