English Español Português
preview
Разработка системы репликации (Часть 35): Внесение корректировок (I)

Разработка системы репликации (Часть 35): Внесение корректировок (I)

MetaTrader 5Примеры | 23 апреля 2024, 15:38
218 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 34): Система ордеров (III), я рассказал о том, что в системе есть несколько довольно странных и даже загадочных ошибок. Причина в том, что данные сбои были вызваны каким-то взаимодействием внутри системы. Несмотря на попытки понять причину некоторых ошибок и закончить с ними, все эти попытки провалились, так как некоторые из них не имели смысла. Когда мы используем указатели или рекурсию в C / C++, программа аварийно завершается. Одним из первых шагов является проверка данных механизмов. В MQL5 всё это происходит не так, как в C / C++. Но после небольших доработок один из недостатков удалось устранить. Однако не думаю, что это такое уж элегантное решение. На самом деле, благодаря этому один из недостатков полностью исчез.

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

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


Устраняем указание занятости сервиса

Первую из этих ошибок легче всего исправить. Как она проявляется: когда мы нажимаем CTRL или SHIFT для выставления ордеров, появляется индикация занятости сервиса. Это означает, что во время работы, несмотря на нормальное функционирование системы, сервис выполняет какую-то другую задачу. Данная задача предполагает смещение во времени. То есть создаются необходмые бары, прежде чем на графике репликации/моделирования вы сможете проводить анализ или исследование. Хотя эта ошибка не является чрезвычайно вредной. Это делает опыт использования репликации/моделирования довольно неприятным, потому что она больше запутывает нас, чем информирует.

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

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(m_id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        ulong ul = 1;

        m_id = ChartID();
        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != m_id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

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

В том же файле, но в другой функции, мы должны выполнить что-то очень похожее, посмотрите чуть ниже:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
  static bool bWait = false;
  u_Interprocess Info;
        
  Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
  if (!bWait)
  {
     if (Info.s_Infos.isWait)
     {
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 0, 0, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 1, 0, "");
        bWait = true;
     }
  }else if (!Info.s_Infos.isWait)
  {
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
     bWait = false;
  }
        
  return rates_total;
}

Как и в случае с кодом OnInit. Здесь зачеркнутые части удалены, а на их месте находится новый код, но единственное различие между оригинальным и новым кодом заключается в значении параметра lparam. И снова то же самое произошло при вызове функции EventChartCustom.

Можно подумать: но почему это происходит и действительно ли это работает? На самом деле, внесение данных изменений в код индикатора не решит проблему. Чтобы действительно решить проблему, нам нужно перейти в класс C_Control и добавить небольшой тест в функцию обработки сообщений. Ниже можно увидеть новый код:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
//... Restante do código ...
  }
}

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

Но подождите, это же не имеет никакого смысла! На самом деле, сначала я тоже не понимал, почему. Но по какой-то причине, когда мы нажимаем клавишу SHIFT или CTRL, чтобы воспользоваться системой ордеров который разрабатываем, платформа генерирует событие, которое запускает два предыдущих события. Но в основном CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn, который заставляет изображение занятого сервиса появляться на графике. И как только мы отпустим клавиши SHIFT или CTRL, сработает событие CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff и это всё возвращает в правильное русло.

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


Более глубокое повторное использование класса C_Mouse

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

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

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

struct st_Mem
{
   bool     CrossHair,
            IsFull;
   datetime dt;
}m_Mem;

Данная переменная, которая была добавлена и выделена, будет использоваться для выполнения проверки в классе. Его приоритет - определить, как именно будет работать класс: это относится к объектам, с которыми классу придется работать в течение своей жизни. После объявления данной переменной нам нужно начать работу. Для этого мы воспользуемся конструктором класса, но теперь возникает первая сложность: как узнать, использовать ли систему в режиме FULL или нет? Одна важная деталь: при использовании режима FULL мы фактически используем графические объекты. Данные объекты будут использоваться советником. Когда мы не используем класс в советнике, такие объекты НЕ должны создаваться, но как мы можем сообщать классу, используем ли мы советник или что-то другое! Есть несколько способов. Конечно, мы это сделаем, но нам нужно будет использовать тот вариант, который выполняется через конструктор класса. Это позволяет избежать большого количества последующих дополнительных проверок и рисков, связанных с неправильным использованием класса. Поэтому мы изменим конструктор и он будет выглядеть так, как показано ниже:

C_Mouse(C_Terminal *arg, color corH = clrNONE, color corP = clrNONE, color corN = clrNONE)
{
   Terminal = arg;
   if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   if (_LastError != ERR_SUCCESS) return;
   m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
   ZeroMemory(m_Info);
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
}

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

Код деструктора также претерпел небольшие изменения. Вот код:

~C_Mouse()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName);
}

Если построение класса завершится неудачей из-за того, что указатель на класс C_Terminal был неправильно инициализирован, мы не сможем использовать данный указатель в деструкторе. Чтобы избежать использования недействительного указателя, мы выполняем эту проверку в деструкторе класса. Это была самая простая часть первоначального написания кода. Теперь давайте посмотрим на старый код класса, чтобы понять, какие изменения нам нужно внести.

void CreateStudy(void)
{
   if (m_Mem.IsFull)
   {
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
      CreateObjectInfo(0, 0, def_NameObjectStudy);
   }
   m_Info.Study = eStudyCreate;
}
//+------------------------------------------------------------------+
void ExecuteStudy(const double memPrice)
{
   double v1 = GetInfoMouse().Position.Price - memPrice;
   int w, h;
                                
   if (!CheckClick(eClickLeft))
   {
      m_Info.Study = eStudyNull;
      ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
      if (m_Mem.IsFull) ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName + "T");
   }else if (m_Mem.IsFull)
   {
      string sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ",
      MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1/ memPrice) * 100.0)));
      GetDimensionText(sz1, w, h);
      ObjectSetString(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                           
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h));                            
      ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
   }
   m_Info.Data.ButtonStatus = eKeyNull;
}

Мы изменили эти две процедуры таким образом, чтобы сделать их очень простыми и понятными. Обратите внимание, что в них добавлена небольшая проверка для создания объектов и работы с ними. Только если мы используем класс в его режиме FULL, такие манипуляции действительно будут иметь место. Но у нас есть и другая процедура в классе C_Mouse. Это очень важно для нас, поскольку это процедура, с помощью которой мы будем взаимодействовать с классом. С его кодом можно ознакомиться ниже:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   int w = 0;
   static double memPrice = 0;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         ChartXYToTimePrice(def_InfoTerminal.ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
         if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = def_AcessTerminal.AdjustPrice(m_Info.Data.Position.Price));
         m_Info.Data.Position.dt = def_AcessTerminal.AdjustTime(m_Info.Data.Position.dt);
         ChartTimePriceToXY(def_InfoTerminal.ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
         if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
         m_Info.Data.ButtonStatus = (uint) sparam;
         if (CheckClick(eClickMiddle))
            if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
                  if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
                  m_Info.Study = eStudyExecute;
               }
         if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
         m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
         break;
   }
}

Как и предыдущие, данная процедура также подверглась модификации. На самом деле, единственное, что мы сделали, - это добавили проверки на то, нужно ли манипулировать объектами в графике. Можно подумать, что эти проверки не нужны, поскольку в некоторых случаях объекты не будут находиться в графике, а если и будут, то манипулировать ими придется точно так же. Это связано с тем, что платформа MetaTrader 5 не будет создавать дублирующие объекты. Однако, я хочу, чтобы вы подумали о том, что когда мы выполняем эти манипуляции с объектами, мы не просто делаем их один раз. Тот факт, что советник и индикатор управления используют один и тот же класс, но не зная, что оба используют его одновременно, заставляет платформу MetaTrader 5 делать двойной вызов обработчиков объектов. На самом деле это не проблема, но надо помнить, что мы используем систему для имитации или моделирования рынка. С самого начала этого проекта я несколько раз повторял, что наша главная задача - сделать моделирование или репликацию как можно более близкими к тому, что происходит на реальном рынке. Речь идет не о простом создании баров, а о времени, которое уходит на их создание. На то, чтобы MetaTrader 5 дважды обращался к объектам, которые можно было бы представить в одном вызове, мы тратим драгоценное время, неважно, какой процессор или оборудование используются. Если мы начнем терять миллисекунды или даже наносекунды из-за дублирования кодов в плане вызовов, то в скором времени мы начнем терять секунды или слишком сильно задерживать создание баров. Так что да, мы должны тестировать, чтобы MetaTrader 5 не пришлось выполнять лишнюю работу для отображения объектов на графике.

Хорошо, с модификацией кода класса C_Mouse всё готово, нам не нужно беспокоиться о коде советника, хотя структура каталогов изменилась, это повлияло только на код включения. Поэтому я не вижу необходимости подробно останавливаться на этом в статье. То, что код советника не претерпел никаких отклонений в реализации, - небольшое облегчение, но это временно, поскольку код индикатора управления, который используется для управления репликацией/моделированием, претерпит гораздо более глубокие изменения, которые заслуживают подробного и спокойного объяснения. Давайте начнем теперь новую тему.


Изменения индикатора управления

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

#include "..\Auxiliar\C_Terminal.mqh"
#include "..\Auxiliar\C_Mouse.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Controls : protected C_Mouse
{
        protected:
                enum EventCustom {ev_WaitOn, ev_WaitOff};
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider,
                                szBarSliderBlock;
                        int     posPinSlider,
                                posY,
                                Minimal;
                }m_Slider;
                C_Terminal *Terminal;

Можно сразу увидеть, что теперь в коде есть два вызова для включения файлов, как класса C_Terminal, так и класса C_Mouse, но почему бы не использовать класс C_Study, который наследуется от класса C_Mouse? Причина в том, что мы не собираемся заставлять индикатор управления проводить или генерировать исследования. Это работа советника, по крайней мере, на данном этапе реализации, а не индикатора управления. Поэтому мы будем использовать класс C_Mouse. Обратите внимание, что класс C_Control наследуется от класса C_Mouse. Даже если такое наследование произойдет, индикатор управления не получит от него никакой пользы, по крайней мере, прямой пользы. По этой причине такое наследование можно даже сделать частным, но я обычно отношусь к этому виду наследования как к защищенному. И последнее, что стоит отметить в данных объявлениях, - указатель, который будет использовать класс C_Terminal.

ПРИМЕЧАНИЕ: В исходном коде класса C_Control была приватная глобальная переменная, которая предоставляла доступ к индексу окна графика. Этот индекс больше не будет существовать в данной версии. Причина в том, что мы собираемся использовать класс C_Terminal для выполнения данной работы, которая ранее выполнялась через внутренний индекс класса C_Control.

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

C_Controls(C_Terminal *arg)
          :C_Mouse(arg),
           m_bWait(false)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      m_szBtnPlay             = NULL;
      m_Slider.szBarSlider    = NULL;
      m_Slider.szBtnPin       = NULL;
      m_Slider.szBtnLeft      = NULL;
      m_Slider.szBtnRight     = NULL;
   }

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

~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

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

void Init(const bool state)
{
   if (m_szBtnPlay != NULL) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true);
   CreateBtnPlayPause(state);
   GlobalVariableTemp(def_GlobalVariableReplay);
   if (!state) CreteCtrlSlider();
   ChartRedraw();
}

Мы удалили строки выше, потому что то, что они устанавливают, уже было реализовано в других местах. Это связано с тем, что класс C_Mouse запускает движение мыши, а класс C_Terminal сообщает платформе MetaTrader 5, что мы хотим получить уведомление о том, что объект удален с графика. По этим причинам данные строки были удалены, так как если бы они остались в коде, то в некоторых ситуациях мы могли бы наблюдать странное поведение кода. Запомните: Мы ни в коем случае не должны предполагать или дублировать код. Это не является ошибкой, и одновременно это ошибка, так как затрудняет корректное выполнение кода в некоторых сценариях и очень сложно поддерживать его со временем.

Следующий код, который нам необходимо изменить, - это обработчик сообщений класса. Это можно увидеть ниже:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
         {
            if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
            {
               RemoveCtrlSlider();
               CreteCtrlSlider();
            }else
            {
               Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
               CreateBtnPlayPause(Info.s_Infos.isPlay);
            }
            ChartRedraw();
         }
         break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            ChartRedraw();
         }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
         else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + 86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + 114;
            if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > 0) PositionPinSlider(sps + x - six);
         }else if (six > 0)
         {
            six = -1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

Данный код претерпел некоторые изменения по сравнению с тем, что было раньше. То, что вы видите, - это окончательная версия, так будет проще объяснить, что здесь происходит. В качестве индикатора управления вам необходимо знать, как ведет себя мышь. Использование только события CHARTEVENT_MOUSE_MOVE для этого не достаточно на 100%. Нам нужен класс C_Mouse, чтобы выполнить свою работу. Помните, что хотя советник, как и индикатор, использует класс C_Mouse, они не обмениваются информацией о том, что делает мышь.

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

  1. Если мы обмениваемся данными мыши между советником и индикатором управления, используя какую-либо характеристику платформы MetaTrader 5, у нас могут возникнуть проблемы при использовании советника вне системы репликации/моделирования. Или у нас могут быть проблемы с тем, что индикатор управления не понимает, что делает мышь.
  2. Другой возможностью было бы использование общей памяти через DLL, но это создаст зависимость от платформы или сервиса репликации/моделирования. И, честно говоря, на данный момент я не заинтересован в создании такой зависимости. Таким образом, использование общей памяти для того, чтобы MetaTrader 5 не приходилось управлять классом C_Mouse так, как это делает он сам, больше не актуально.

Эти причины могут показаться не очень вескими, но я хочу в полной мере использовать возможности языка MQL5 и платформы MetaTrader 5, чтобы построить систему репликации/моделирования, демонстрирующую, что мы можем сделать гораздо больше, чем многие считают возможным на платформе или в языке. Поэтому нам нужно, чтобы класс C_Mouse также обновлял данные для всего, что связано с мышью. Для этого используется вызов с этого места. Поскольку мы собираемся обрабатывать события мыши через CHARTEVENT_MOUSE_MOVE, мы можем переключиться на данное конкретное событие.

Первое, что мы делаем, - проверяем, проводит ли пользователь какое-то исследование на графике. В этом случае всё, что связано с обработкой событий мыши в классе C_Control, должно быть проигнорировано. С этого момента мы не будем обрабатывать события мыши локально, - вместо этого мы спросим класс C_Mouse о том, что произошло, а затем примем соответствующие решения и действия, чтобы выполнить пожелания пользователя. Затем мы проверяем, что пользователь нажал левую кнопку мыши и что объект ползунка присутствует на графике (режим паузы). Если true, мы проверяем позицию, в которой произошел щелчок. Если он находился на объекте ползунка, он будет реагировать соответствующим образом, пока нажата левая кнопка. Таким образом, пользователь может перетаскивать объект ползунка с одной стороны на другую, как и раньше.

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


Заключение

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

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


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

Прикрепленные файлы |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Разработка системы репликации (Часть 36): Внесение корректировок (II) Разработка системы репликации (Часть 36): Внесение корректировок (II)
Одна из вещей, которая может усложнить нашу жизнь как программистов, - это предположения. В этой статье я покажу вам, как опасно делать предположения: как в части программирования на MQL5, где принимается, что у курса будет определенная величина, так и при использовании MetaTrader 5, где принимается, что разные серверы работают одинаково.
Разработка системы репликации (Часть 34): Система ордеров (III) Разработка системы репликации (Часть 34): Система ордеров (III)
В этой статье мы завершим первый этап конструкции. Несмотря на то, что это выполняется довольно быстро, я расскажу о деталях, которые не обсуждались ранее. Но здесь я объясню некоторые моменты, которые многие не понимают. Например, знаете ли вы, почему вам приходится нажимать клавишу Shift или Ctrl на клавиатуре?
Разрабатываем мультивалютный советник (Часть 9): Сбор результатов оптимизации одиночных экземпляров торговой стратегии Разрабатываем мультивалютный советник (Часть 9): Сбор результатов оптимизации одиночных экземпляров торговой стратегии
Наметим основные этапы по разработке нашего советника. Одним из первых будет проведение оптимизации одиночного экземпляра разработанной торговой стратегии. Попробуем собрать в одном месте всю необходимую информацию о проходах тестера при оптимизации.
Нейросети — это просто (Часть 86): U-образный Трансформер Нейросети — это просто (Часть 86): U-образный Трансформер
Мы продолжаем рассмотрение алгоритмов прогнозирования временных рядов. И в данной статье я предлагаю Вам познакомиться с методов U-shaped Transformer.