English Español Deutsch Português
preview
Разработка системы репликации (Часть 40): Начало второй фазы (I)

Разработка системы репликации (Часть 40): Начало второй фазы (I)

MetaTrader 5Примеры | 18 июня 2024, 10:19
399 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 39): Прокладываем путь (III), мы посмотрели, как можно организовать связь между процессами, чтобы можно было выполнять определенные действия. В настоящий момент мы используем советник и индикатор, но по мере необходимости мы сможем расширять данные инструменты.

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

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

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


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

Наш код будет собран из того, что у нас уже есть в настоящий момент. Мы внесем минимальные изменения, чтобы код советника стал индикатором.

Первое, что нужно сделать, - это проверить код заголовочного файла InterProcess.mqh. Полный код можно увидеть ниже.

Код файла InterProcess.mqh:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_SymbolReplay                    "RePlay"
04. #define def_GlobalVariableReplay            def_SymbolReplay + "_Infos"
05. #define def_GlobalVariableIdGraphics        def_SymbolReplay + "_ID"
06. #define def_GlobalVariableServerTime        def_SymbolReplay + "_Time"
07. #define def_MaxPosSlider                    400
08. //+------------------------------------------------------------------+
09. union u_Interprocess
10. {
11.     union u_0
12.     {
13.             double  df_Value;  // Value of the terminal global variable...
14.             ulong   IdGraphic; // Contains the Graph ID of the asset...
15.     }u_Value;
16.     struct st_0
17.     {
18.             bool    isPlay;     // Indicates whether we are in Play or Pause mode...
19.             bool    isWait;     // Tells the user to wait...
20.             bool    isHedging;  // If true we are in a Hedging account, if false the account is Netting...
21.             bool    isSync;     // If true indicates that the service is synchronized...
22.             ushort  iPosShift;  // Value between 0 and 400...
23.     }s_Infos;
24.     datetime        ServerTime;
25. };
26. //+------------------------------------------------------------------+
27. union uCast_Double
28. {
29.     double   dValue;
30.     long     _long;                    // 1 Information
31.     datetime _datetime;                // 1 Information
32.     int      _int[sizeof(double)];     // 2 Informations
33.     char     _char[sizeof(double)];    // 8 Informations
34. };
35. //+------------------------------------------------------------------+

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

Данные строки добавлены в файл InterProcess.mqh. Продолжим, но сейчас внесем другие изменения в код. Их надо реализовать в файле класса C_Study.mqh.

Для начала мы кое-что изменим в начале класса:

class C_Study : public C_Mouse
{
        protected:
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
        private :
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};

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

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

//+------------------------------------------------------------------+
void Update(const eStatusMarket arg)
void Update(void)
{
   datetime dt;
                                
   switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
   switch (m_Info.Status)
   {
      case eCloseMarket : m_Info.szInfo = "Closed Market";
         break;
      case eInReplay    :
      case eInTrading   :
         if ((dt = GetBarTime()) < ULONG_MAX)
         {
            m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
            break;
         }
      case eAuction     : m_Info.szInfo = "Auction";
         break;
      default           : m_Info.szInfo = "ERROR";
   }
   Draw();
}
//+------------------------------------------------------------------+
void Update(const MqlBookInfo &book[])
{
   m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
   for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
      if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
   this.Update();
}
//+------------------------------------------------------------------+

Все зачеркнутые строки были удалены из класса, а на их место добавлены выделенные строки.

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

Код, о котором идет речь, относится к использованию мыши как средства для проведения исследований.

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

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

Давайте рассмотрим приведенный ниже код индикатора. Это полный вариант.

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.40"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/ru/articles/11624"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. #include <Market Replay\Auxiliar\InterProcess.mqh>
015. //+------------------------------------------------------------------+
016. C_Terminal *Terminal  = NULL;
017. C_Study    *Study     = NULL;
018. //+------------------------------------------------------------------+
019. input C_Study::eStatusMarket user00 = C_Study::eAuction;   //Market Status
020. input color user01 = clrBlack;                             //Price Line
021. input color user02 = clrPaleGreen;                         //Positive Study
022. input color user03 = clrLightCoral;                        //Negative Study
023. //+------------------------------------------------------------------+
024. C_Study::eStatusMarket m_Status;
025. int m_posBuff = 0;
026. double m_Buff[];
027. //+------------------------------------------------------------------+
028. int OnInit()
029. {
030.    if (!CheckPass("Indicator Mouse Study")) return INIT_FAILED;
031.            
032.    Terminal = new C_Terminal();
033.    Study = new C_Study(Terminal, user01, user02, user03);
034.    if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
035.    {
036.            MarketBookAdd((*Terminal).GetInfoTerminal().szSymbol);
037.            OnBookEvent((*Terminal).GetInfoTerminal().szSymbol);
038.            m_Status = C_Study::eCloseMarket;
039.    }else
040.            m_Status = user00;
041.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
042.    ArrayInitialize(m_Buff, EMPTY_VALUE);
043.    
044.    return INIT_SUCCEEDED;
045. }
046. //+------------------------------------------------------------------+
047. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
048. {
049.    m_posBuff = rates_total - 4;
050.    (*Study).Update(m_Status);      
051.    
052.    return rates_total;
053. }
054. //+------------------------------------------------------------------+
055. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
056. {
057.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
058.    SetBuffer();
059.    
060.    ChartRedraw();
061. }
062. //+------------------------------------------------------------------+
063. void OnBookEvent(const string &symbol)
064. {
065.    MqlBookInfo book[];
066.    C_Study::eStatusMarket loc = m_Status;
067.    
068.    if (symbol != (*Terminal).GetInfoTerminal().szSymbol) return;
069.    MarketBookGet((*Terminal).GetInfoTerminal().szSymbol, book);
070.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
071.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
072.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
073.    if (loc != m_Status) (*Study).Update(m_Status);
074. }
075. //+------------------------------------------------------------------+
076. void OnDeinit(const int reason)
077. {
078.    if (reason != REASON_INITFAILED)
079.    {
080.            if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
081.                    MarketBookRelease((*Terminal).GetInfoTerminal().szSymbol);
082.            delete Study;
083.            delete Terminal;
084.    }
085. }
086. //+------------------------------------------------------------------+
087. bool CheckPass(const string szShortName)
088. {
089.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName + "_TMP");
090.    if (ChartWindowFind(ChartID(), szShortName) != -1)
091.    {
092.            ChartIndicatorDelete(ChartID(), 0, szShortName + "_TMP");
093.            Print("Only one instance is allowed...");
094.            
095.            return false;
096.    }
097.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
098.    
099.    return true;
100. }
101. //+------------------------------------------------------------------+
102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }
116. //+------------------------------------------------------------------+


Посмотрев исходный код индикатора и сравнив его с кодом, приведенным в статье "Разработка системы репликации (Часть 39)": Прокладываем путь (III)", можно заметить много сходств.

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

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

В строках 9 и 11 мы сообщаем компилятору, как именно будет планироваться индикатор. Во всех кодах индикаторов должна быть как минимум одна строка 9 и одна строка 10. ВО ВСЕХ. Строка 11 сообщает нам, что мы будем использовать буфер, и что любой процесс, который хочет прочитать что-то через функцию CopyBuffer, может сделать это, обратившись к внутреннему буферу индикатора.

Строки 13 и 14 - это "includes", которые указывают компилятору, какие заголовочные файлы нужно использовать. Хотя мы уже внесли некоторые изменения в данные файлы, нам нужно будет внести другие изменения. На данный момент мы можем использовать предоставленные заголовочные файлы без проблем.

В строках 16 и 17 мы объявляем две внутренние глобальные переменные индикатора. Эти указатели и помогут нам получить контролируемый доступ к классам. Обычно глобальная переменная не инициализируется по умолчанию, так как компилятор делает это автоматически. Но здесь я хочу пояснить, что, хотя эти переменные (указатели) не указывают на какую-либо конкретную область памяти, если их объявить явно, то мы должны помнить, что компилятор по умолчанию будет делать это неявно.

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

Надо понять одну вещь: ни одна программа не должна рассматриваться как «чистая» и «простая». Всегда нужно думать о программе как о функции, поскольку она получает информацию, обрабатывает ее определенным образом и выдает какой-то результат. Какой элемент используется в программировании, который имеет такое же поведение? ОДНА ФУНКЦИЯ. Так что давайте начнем смотреть на вещи шире. Мы должны перестать фантазировать и мечтать о возможностях и начнем видеть вещи такими, какими они являются на самом деле: программы - это функции, независимо от того, что мы можем сделать. Программы - это функции.

В строке 19 у нас есть параметр, у которого нет смысла, когда пользователь помещает индикатор на график. Данный параметр существует не для его настройки пользователем; он существует для того, чтобы другая программа могла использовать этот индикатор, который мы создаем. Как это возможно? Это непонятно 🤔. Не беспокойтесь пока об этом. Помните: это первая статья на втором этапе создания системы репликации/моделирования. В следующих статьях будет показано несколько вещей, которые помогут вам понять, почему существует строка 19. Без паники.

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

Теперь у нас возникает вопрос: зачем нужна строка 24? Почему у нас есть глобальная переменная того же типа, что и в строке 19? Причина в том, что параметр в строке 19 на самом деле считается не переменной, а константой. Поэтому нам необходимо объявить переменную, которая будет использоваться и устанавливаться во время работы индикатора. Это и есть причина объявления в строке 24.

Переменная, объявленная в строке 25, служит видом памяти, но с более благородной целью, чем обычная память. Мы скоро вернемся к этому вопросу. В строке 26 мы объявляем наш буфер, который будет служить выходом индикатора. Внимание: мы объявили буфер, но он пока не работает. Если вы попытаетесь что-то сделать в нем, то появится предупреждение об ошибке времени выполнения, а при попытке прочитать его, вы не сможете это сделать.

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

Среди них есть событие Init, которое при срабатывании генерирует вызов функции OnInit. Эта функция начинается в строке 28 и не принимает никаких параметров, но мы можем использовать параметры взаимодействия, объявленные между строками 19 и 22, в качестве параметров в функции OnInit.

Давайте посмотрим, как работает функция OnInit. Первое, что мы сделаем, - зарегистрируемся, что происходит в строке 30, где мы вызываем функцию. Затем поток выполнения перенаправляется на строку 87. Давайте проанализируем ее, чтобы понять, что это за процедура и почему, если функция OnInit не работает, она возвращает ошибку инициализации индикатора.

Функция в строке 87 принимает один аргумент - короткое имя индикатора. Данное название всегда должно быть определено, чтобы гарантировать некоторые условия использования или условия работы. Первое, что мы делаем (и это происходит в строке 89), - даем индикатору временное имя. Как только индикатор получит временное имя, мы сможем продолжить данный этап регистрации. Следующее, что мы сделаем, - посмотрим на строку 90 для нашего индикатора на графике. Если он уже присутствует на графике, MetaTrader 5 укажет, где именно. Если нет, то мы вернемся к значению -1.

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

Если тест в строке 90 сообщает, что индикатор отсутствует, мы выполним строку 97, которая сообщит MetaTrader 5 настоящее имя индикатора. А в строке 99 мы возвращаемся в точку вызова и сообщаем там, что индикатор может быть успешно размещен на графике. Но это не означает, что он действительно установился на графике, а лишь информирует нас о том, что MetaTrader 5 не нашел его на графике, хотя теперь его можно запускать.

Это вернет нас к строке 30, где будет принято решение. В случае успеха мы перейдем к строке 32. В случае неудачи MetaTrader 5 запустит событие для вызова функции в строке 76. Однако, поскольку строка 30 сообщает нам о том, что произошел сбой, как только строка 78 выполнится она помешает выполнению кода между строками 80 и 83. Это важно для избежания потери информации на выходе индикатора.

Но давайте вернемся к строке 32, где мы действительно начинаем использовать индикатор. В строке 32 мы запускаем указатель для доступа к классу C_Terminal. С этого момента мы сможем использовать функции класса C_Terminal. Сразу после этого, в строке 33, мы запускаем указатель для доступа к классу C_Study. Данный класс позволит нам использовать мышь и сгенерировать графические исследования. Поскольку нам нужен доступ к классу C_Terminal, необходимо выполнить следующую последовательность действий.

В строке 34 у нас есть новый тест, который определит, используем ли мы актив репликации/моделирования или другой актив. Если актив является активом репликации/моделирования, содержание входного конфигурационного параметра индикатора, объявленного в строке 19, надо будет поместить в нашу глобальную переменную. Это происходит в строке 40.

Если строка 34 указывает на то, что мы работаем с другим видом актива, тогда будут выполнены строки с 36 по 38. Данные строки инициализируют использование книги заявок, сообщая MetaTrader 5, что мы хотим получать события, произошедшие в книге заявок. Кроме того, в строке 38 мы указываем, что рынок закрыт. Это условие временное, так как мы получим указания из книги заявок о том, какой будет правильная информация.

Теперь идет очень важная для индикатора строка 41. Помните, что объявление буфера в строках 11 и 26 не гарантирует, что к нему можно будет обратиться, и что при попытке обращения к нему будет сгенерирована ошибка выполнения. Без строки 41 индикатор практически полностью бесполезен. В этой статье мы рассказали об индексе, матрице и их пользе. Обычно мы начинаем с нулевого значения в поле индекса. В поле массива мы указываем, какая переменная используется как буфер. Что касается третьего поля, которое сообщает нам, для чего используется буфер, мы можем использовать одно из перечислений ENUM_INDEXBUFFER_TYPE, но поскольку мы собираемся хранить данные, мы используем INDICATOR_DATA. Однако ничто не мешает нам использовать другой вид в этом конкретном коде.

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

Это было объяснение того, как инициализируется индикатор. Теперь нам нужно знать, как реагировать на события, запускаемые MetaTrader 5. Чтобы объяснить данную часть, давайте начнем с рассмотрения кода события книги заявок в функции OnBookEvent. Эта процедура начинается в строке 63, и вызывается MetaTrader 5 каждый раз, когда что-то происходит в книге заявок.

Если посмотрим внимательно, то можно увидеть, что содержание этой процедуры, расположенной между строками 70 и 72, практически не отличается от содержания класса C_Study. Почему же мы не оставили эту процедуру в классе C_Study? Трудно объяснить, почему именно здесь и сейчас, но поверьте, есть причина, которая станет более понятной позже. Данный фрагмент уже наверняка знаком вам, если, конечно, вы следили за серией статей и изучали их. В этой функции, находится дополнительный код.

В строке 66 мы объявляем локальную и временную переменную, которая будет временно хранить состояние рынка, поскольку оно может измениться в строках 70 или 72. Если это произойдет, мы должны вызвать строку 73, чтобы удобно обновить информацию о графике. Прежде чем мы рассмотрим вопрос обновления, давайте бегло просмотрим другие строки.

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

После этого, в строке 69, мы фиксируем данные, которые содержатся в книге заявок, чтобы иметь возможность анализировать их в соответствии с нашими потребностями. Затем, в строке 70, мы сообщаем индикатору, закрыт ли (или открыт) рынок, ссылаясь на физический рынок, то есть находится ли торговый сервер в пределах разрешенного торгового окна или нет. Чтобы узнать это, мы проверяем, пустой ли набор книги заявок или нет.

Теперь, в строке 71, мы входим в цикл, который будет проверять матрицу точка за точкой, чтобы увидеть, есть ли позиции, указывающие на то, что актив выставлен на аукцион. Актив находится в состоянии аукциона, если выполняется одно из двух условий, отмеченных в строке 72. Поскольку мы не знаем, где именно в книге заявок находятся BID и ASK, мы используем цикл в строке 71. Однако если используемый нами актив имеет фиксированную позицию BID и ASK, можно пропустить цикл, удалив строку 71 и просто указав точку в массиве, где расположены BID и ASK. Если любой из них указывает на то, что актив находится на аукционе, состояние будет обновлено.

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

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

Вторая точка, в которой происходит обновление состояния, - это как раз то событие, которое мы рассмотрим: функция OnCalculate.

Каждый раз, когда цена обновляется или на графике появляется новый бар, MetaTrader 5 запускает событие. Данное событие вызывает функцию OnCalculate в коде индикатора. Конечно, обычно мы всегда хотим, чтобы этот код выполнялся как можно быстрее, чтобы не пропустить события, которые нас интересуют. В отличие от события Order Book, которое сообщает об изменении состояния, как только оно происходит, событие OnCalculate действует только тогда, когда что-то влияет на цену.

Тем не менее, мы можно видеть, что в строке 49 мы сохраняем и корректируем значение, которое там не используется. Если у нас есть вызов функции, нам нужно использовать данное значение, но почему такое происходит 🤔? Опять же, нам нужно, чтобы функция OnCalculate выполнялась как можно быстрее, и нет смысла обновлять буфер при каждом движении цены или при появлении каждого нового бара. Не забывайте, что мы работаем не с ценой или с барами на графике, а с мышью.

Поэтому следующее событие, запускаемое MetaTrader 5, отвечает за обновление буфера: функция OnChartEvent.

Функция OnChartEvent, которая начинается в строке 55, довольно простая и понятная. В функции в строке 57 есть вызов для корректной обработки событий классом исследования. Это, в свою очередь, выполнит внутреннюю работу, необходимую для полной обработки события мыши. Можно посмотреть подробнее в статье "Разработка системы репликации (Часть 31)": Проект советника — класс C_Mouse (V)". Независимо от этого, мы вызовем процедуру, и поток выполнения остановится на строке 102.

Прежде чем перейти к строке 102, давайте рассмотрим событие в строке 76, о котором упоминалось ранее при объяснении процесса инициализации. Теперь давайте посмотрим, как этот же код будет вести себя при правильной инициализации индикатора. Когда это произойдет, будут выполнены строки от 80 до 83. В строке 80 мы проверим, для какого актива используется индикатор. Причина в том, что если актив является одним из тех, которые можно отслеживать на предмет событий книги заявок, мы должны сообщить MetaTrader 5, что мы больше не хотим получать такие события; это осуществляется в строке 81. Строки 82 и 83 просто уничтожат класс, гарантируя, что индикатор будет корректно удален с графика.

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


Процедура SetBuffer: Здесь происходит волшебство

Если вы следили и изучали статьи в этой серии, то вы могли заметить, что в статье "Разработка системы репликации (часть 39): Прокладываем путь (III)", есть целая тема, посвященная исключительно объяснению того, как помещать значения в буфер для отправки индикаторной информации другому процессу.

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

Если посмотреть на код между строками 102 и 115, то вы увидите, что я делаю всё по-своему. Но почему я делаю это именно так?

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

102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0  ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }

Для начала давайте разберемся в следующем: в строке 49 мы указываем, что собираемся поместить в буфер четыре значения double. Но где именно в буфере? Об этом говорится в статье, упомянутой выше. Мы будем использовать позицию rates_total, ровно на четыре позиции назад от данной точки. Если мы занимаемся репликацией или моделированием, мы можем начать с нулевой позиции, потому что мы являемся теми, кто запускает процесс. Инициализация была выполнена в строке 25.

Чтобы облегчить преобразования, которые мы будем делать и которые нам понадобятся, мы используем строку 104 для объявления локальной переменной. Теперь обратите внимание на нужную проверку в строке 106. Это предотвращает возникновение нештатных ситуаций, в основном из-за строки 49, которая присутствует в функции OnCalculate. В случае с реальным активом мы вряд ли увидим отрицательное значение в m_posBuff. Но в активе репликации или моделирования мы можем получить отрицательное значение. Поэтому строка 106 исправляет это, заставляя m_posBuff указывать на правильную позицию.

Если бы m_posBuff указывал на индекс ниже нуля, индикатор бы «сломался». Обратите внимание, что в строке 107 мы начинаем обновлять данные индикатора. Именно данный пункт является самым простым. Теперь наступает самый сложный момент, представленный в строках 109, 112 и 114. В этих строках мы помещаем в буфер другие значения, всегда начиная с первоначально вычисленной позиции.

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

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

  • Прежде всего, в нулевой позиции мы сохраним цену, на которой находится линия мыши;
  • В первой позиции мы будем хранить значение времени, в котором находится указатель мыши;
  • Во второй позиции мы будем хранить значения касательно положения экрана, то есть в координатах X и Y. Сначала значение X, а затем значение Y;
  • В третьей позиции мы будем хранить дополнительные элементы. Текущее состояние кнопок мыши. Оно должно быть помещено в наименее значимый байт (LSB). То есть в нулевом байте.

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


Заключение

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

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

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

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Нейросети — это просто (Часть 95): Снижение потребления памяти в моделях Transformer Нейросети — это просто (Часть 95): Снижение потребления памяти в моделях Transformer
Модели на основе архитектуры Transformer демонстрируют высокую эффективность, однако их использование осложняется большими затратами ресурсов как на этапе обучения, так и в процессе эксплуатации. В этой статье я предлагаю познакомиться с алгоритмами, которые позволяют уменьшить использование памяти такими моделями.
Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть II): Сборка и результаты Алгорим оптимизации химическими реакциями — Chemical reaction optimisation, CRO (Часть II): Сборка и результаты
Во второй части статьи мы соберем химические операторы в единый алгоритм и представим подробный анализ результатов его работы. Узнаем, как метод оптимизации химическими реакциями (CRO) справился с вызовом в решении сложных задач на тестовых функциях.
Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5 Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5
В данной статье рассматривается переход от процедурного написания кода к объектно-ориентированному программированию (ООП) в MQL5 с упором на интеграцию с REST API. Сегодня мы обсуждаем организацию функций HTTP-запросов (GET и POST) в классы и подчеркнем такие преимущества, как инкапсуляция, модульность и простота обслуживания. Подробно рассмотрим рефакторинг кода и покажем замену изолированных функций методами класса. Статья содержит практические примеры и тесты.
Разработка системы репликации (Часть 39): Прокладываем путь (III) Разработка системы репликации (Часть 39): Прокладываем путь (III)
Прежде, чем приступить ко второму этапу разработки, необходимо закрепить несколько идей. Знаете ли вы, как заставить MQL5 делать то, что вам необходимо? Пытались ли когда-нибудь выйти за рамки того, что содержится в документации? Если нет, то приготовьтесь. Потому что прямо сейчас мы будем делать то, чем большинство людей обычно не занимается.