English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 27): Проект советника — класс C_Mouse (I)

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

MetaTrader 5Тестер | 13 февраля 2024, 15:03
521 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка системы репликации (Часть 26): Проект Expert Advisor (I)", я подробно рассказал о начале построения первого класса. Теперь давайте расширим эти идеи и сделаем их более полезными, что подводит нас к созданию класса C_Mouse. Он обеспечивает возможности программирования на самом высоком уровне. Однако разговоры о высокоуровневых или низкоуровневых языках программирования не связаны с включением в код нецензурных слов или жаргона. Всё наоборот. Когда мы говорим о высокоуровневом или низкоуровневом программировании, мы имеем в виду, насколько легко или сложно понять код другим программистам. Фактически, различие между высокоуровневым и низкоуровневым программированием показывает, насколько простым или сложным может быть код для других разработчиков. Таким образом, код считается высокоуровневым, если он похож на естественный язык, и низкоуровневым, если он меньше похож на естественный и ближе к тому, как процессор интерпретирует инструкции.

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


Класс C_Mouse: начинаем взаимодействие с пользователем

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_MousePrefixName "MOUSE_"
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TV"
#define def_NameObjectLineT def_MousePrefixName + "TT"
#define def_NameObjectBitMp def_MousePrefixName + "TB"
#define def_NameObjectText  def_MousePrefixName + "TI"
//+------------------------------------------------------------------+
#define def_Fillet      "Resource\\Fillet.bmp"
#resource def_Fillet
//+------------------------------------------------------------------+

Мы включили заголовочный файл, который содержит класс C_Terminal. Как уже говорилось в предыдущей статье, данный файл класса C_Mouse расположен в том же каталоге, что и файл класса C_Terminal, что позволяет нам без проблем использовать этот синтаксис. Мы определяем имя ресурса, который будет включен в исполняемый файл, что позволяет вам переносить его без необходимости загружать ресурс отдельно. Это очень полезно во многих случаях, особенно когда критически важен ресурс и существенно важно его наличие во время использования. Обычно мы помещаем ресурс в определенный каталог для более легкого доступа. Таким образом, он всегда будет компилироваться вместе с заголовочным файлом. Мы добавили каталог под названием Resource в папку, где находится файл C_Mouse.mqh. Внутри данного каталога Resource находится файл Fillet.bmp. Если мы изменим структуру каталогов, сохранив при этом то же моделирование, компилятор будет точно знать, где найти файл Fillet.bmp. После компиляции кода мы можем загрузить исполняемый файл, не беспокоясь о том, что ресурс не будет найден, поскольку он будет встроен в сам исполняемый файл.

На этом этапе мы сначала определяем имя, а на самом деле префикс для других имен, которые мы определим позже. Использование определений значительно упрощает разработку и поддержание, что является обычной практикой в ​​профессиональном коде. Программист определяет различные имена и элементы, которые будут использоваться в коде, что обычно делается в файле Defines.mqh или другом аналогичном файле, что упрощает изменение определений. Однако, поскольку эти определения существуют только в данном файле, нет необходимости объявлять их где-либо еще.

#undef def_MousePrefixName
#undef def_NameObjectLineV
#undef def_NameObjectBitMp
#undef def_NameObjectLineH
#undef def_NameObjectLineT
#undef def_NameObjectText
#undef def_Fillet

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

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

class C_Mouse : public C_Terminal
{

   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x01, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int      X,
                     Y;
            double   Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
      };

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

Данное перечисление дает нам возможность, которая в другом случае была бы гораздо сложнее для поддержания; то есть оно сэкономит нам много работы. Это конкретное перечисление создает определения имен, что эквивалентно команде предварительной обработки `#define`. Однако мы решили использовать перечисление вместо определений. Это позволит нам применить немного другую технику, но в то же время она гораздо проще для понимания в коде. Используя данное перечисление, мы увидим, как код становится намного более читабельным. И это становится решающим в сложном коде. Если вы думаете, что у этого перечисления есть объявление, которое на первый взгляд очень запутанное и сложное, то такие мысли у вас из-за того, что вы, пожалуй, до конца не понимаете, как работают перечисления. С точки зрения компилятора, перечисление — это не что иное, как последовательность определений, где по умолчанию первый элемент начинается с нулевого индекса. Однако ничто не мешает нам определить, каким будет начальный индекс перечисления, и с этого момента компилятор начнет увеличивать значения последующих индексов. Это очень полезно во многих сценариях, где значение определенного индекса служит начальным значением последовательности. Нередко можно встретить длинные списки перечислений, в которых значения ошибок установлены на основе какого-то конкретного критерия. Если определить имя и присвоить ему определенное значение, компилятор будет автоматически увеличивать значения всех последующих имен. Это значительно упрощает создание больших списков определений, без риска дублирования значений в какой-то момент.

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

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

Продолжая эту идею, в следующем фрагменте мы можем увидеть переменные, присутствующие в нашем классе C_Mouse:


   private :
      enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
      struct st01
      {
         st_Mouse Data;
         color    corLineH,
                  corTrendP,
                  corTrendN;
         eStudy   Study;
      }m_Info;
      struct st_Mem
      {
         bool    CrossHair;
      }m_Mem;

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

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

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

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
   }

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

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

inline const st_Terminal GetInfoTerminal(void) const
   {
      return m_Infos;
   }

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

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

inline void CreateLineH(void)
   {
      CreateObjectBase(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }

Она отвечает за создание горизонтальной линии, представляющей ценовую линию. Важно отметить, что, делегируя всю сложность другой процедуре, нам нужно всего лишь написать одну строку. Данный вид подхода обычно заменяется каким-либо макросом. Однако я предпочитаю попробовать сделать это без использования такого ресурса. Другой способ — вставить то же содержимое, учитывая, что это одна строка, в тех местах, где будет происходить вызов. Лично я не рекомендую эту практику не потому, что она неправильна, а потому, что она, при изменении только одной строки, требует с нашей стороны изменения всех строк. Это может быть утомительной задачей, чреватой ошибками. Поэтому, хотя может показаться более практичным размещать код непосредственно в точках, к которым ссылались, но безопаснее сделать это с использованием макроса или кода со словом `inline` в его объявлении.

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

void CreateStudy(void)
{
   CreateObjectBase(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
   CreateObjectBase(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
   CreateObjectBase(def_NameObjectBitMp, OBJ_BITMAP, clrNONE);
   CreateObjectBase(def_NameObjectText, OBJ_TEXT, clrNONE);                                
   ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONT, "Lucida Console");
   ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONTSIZE, 10);
   ObjectSetString(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_BMPFILE, "::" + def_Fillet);
   ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
   m_Info.Study = eStudyCreate;
}

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

Многие исследования включают в себя большое разнообразие объектов и комбинаций между ними, которые иногда требуют расчетов или поиска позиции в ценовом диапазоне (исследования на основе максимумов и минимумов). Выполнять всё это вручную не только медленно, но и утомительно из-за необходимости постоянно добавлять и удалять объекты из графика. В противном случае график может стать перегруженным и запутанным, что затруднит выявление необходимой информации. Так что используйте данный метод как основу для создания чего-то более изысканного и адаптированного к вашим потребностям. Однако пока спешки нет; позже будет возможность улучшить его.

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

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

void ExecuteStudy(const double memPrice)
{
   if (CheckClick(eClickLeft))
   {
      ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectMove(GetInfoTerminal().ID, def_NameObjectBitMp, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectMove(GetInfoTerminal().ID, def_NameObjectText, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
      ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_TEXT, StringFormat("%." + (string)GetInfoTerminal().nDigits + "f ", m_Info.Data.Position.Price - memPrice));
   } else {
      m_Info.Study equal eStudyNull;
      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
      ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
   }
   m_Info.Data.ButtonStatus equal eKeyNull;
}

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

На самом деле нам нужен метод, позволяющий правильно завершить презентацию исследования, что и делается во втором сегменте кода. Хотя на первый взгляд данный сегмент не кажется особенно примечательным, есть один аспект, заслуживающий особого внимания: использование функции `ObjectsDeleteAll`. Почему этот момент важен и требует внимания? Ответ находится в классе C_Terminal. Если посмотреть на конструктор класса C_Terminal, то мы заметим следующую строку:

ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);

Данная строка сообщает платформе, что каждый раз, когда объект удаляется из графика, она должна генерировать событие, уведомляющее, какой объект был удален. Используя функцию ObjectsDeleteAll, мы удаляем все элементы или объекты, используемые в рассматриваемом исследовании, это заставляет MetaTrader 5 генерировать событие для каждого объекта, удаленного из графика. По сути, платформа будет делать именно это, и наш код должен решить, будут ли созданы снова эти объекты или нет. Проблема возникает в случае сбоя, когда объекты не удаляются (поскольку код создает их снова) или удаляются без получения кода какого-либо уведомления. В данной ситуации для свойства `CHART_EVENT_OBJECT_DELETE` будет установлено значение false. Хотя изначально это не происходит, по мере расширения кода бывают случаи, когда данное свойство может быть случайно изменено, и мы можем забыть его повторно активировать. Как следствие, платформа не будет вызывать событие, уведомляющее наш код об удалении объектов из графика, что может привести к неточностям и ошибкам в управлении объектами внутри исследования.

Давайте теперь разберем конструктор класса C_Mouse.

C_Mouse(color corH, color corP, color corN)
   :C_Terminal()
{
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   m_Mem.CrossHair  = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
   CreateLineH();
}

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

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

~C_Mouse()
{
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
}

Этот вопрос, хотя с виду может показаться нелогичным, имеет важное значение. Причина включения именно данной строки кода заключается в том, что при попытке удалить объекты, созданные классом C_Mouse, в частности ценовую линию, платформа генерирует событие, уведомляющее нас о том, что объект был удален с графика. Затем наш код попытается вернуть данный объект в график, даже если он находится в процессе удаления. Чтобы платформа не сгенерировала такое событие, мы должны четко указать, что мы не хотим, чтобы такое произошло. Можно задаться вопросом: "Но разве класс C_Terminal не позаботится об этом, сообщив нам, что мы больше не хотим получать события, связанные с удалением объектов из графика?" Да, класс C_Terminal сделал бы это, но поскольку нам всё еще нужны некоторые данные, присутствующие в классе C_Terminal, мы позволяем компилятору неявно выполнять вызов деструктора класса C_Terminal, который происходит только после выполнения последней строки деструктора класса C_Mouse. Без добавления выделенной строки кода платформа продолжит генерировать событие, поэтому даже если ценовая линия изначально будет удалена, ее можно будет вернуть обратно до того, как код будет полностью завершен. Остальные строки деструктора проще, поскольку всё, что мы делаем, — это возвращаем график в исходное состояние.

Мы подошли к последним функциям, обсуждаемым в данной статье.

inline bool CheckClick(const eBtnMouse value) { return (m_Info.Data.ButtonStatus & value) == value; }

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

Следующая функция так же проста, как и предыдущая, и следует тому же принципу, что и функция GetInfoTerminal класса C_Terminal.

inline const st_Mouse GetInfoMouse(void) const { return m_Info.Data; }

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

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


Заключение

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


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

Теория категорий в MQL5 (Часть 23): Другой взгляд на двойную экспоненциальную скользящую среднюю Теория категорий в MQL5 (Часть 23): Другой взгляд на двойную экспоненциальную скользящую среднюю
В этой статье мы продолжаем рассматривать популярные торговые индикаторы под новым углом. Мы собираемся обрабатывать горизонтальную композицию естественных преобразований. Лучшим индикатором для этого является двойная экспоненциальная скользящая средняя (Double Exponential Moving Average, DEMA).
Разработка системы репликации (Часть 26): Проект советника — Класс C_Terminal Разработка системы репликации (Часть 26): Проект советника — Класс C_Terminal
Мы уже можем начать создавать советника для использования в репликации/моделировании. Однако нам нужно нечто усовершенствованное, а не какое-то случайное решение. Несмотря на это, нас не должна пугать первоначальная сложность. Очень важно начать с чего-то, иначе в конечном итоге мы придем к тому, что размышляем о сложности задачи, даже не пытаясь ее преодолеть. Суть программирования именно в этом: преодолеть препятствия посредством изучения, тестирования и обширных исследований.
Разработка системы репликации (Часть 28): Проект советника — класс C_Mouse (II) Разработка системы репликации (Часть 28): Проект советника — класс C_Mouse (II)
Когда начали создаваться первые системы, способные что-то считать, всё потребовало вмешательства инженеров, обладающих обширными знаниями о том, что проектируется. Мы говорим о рассвете компьютерной техники, о времени, когда не было даже терминалов, позволяющих что-либо программировать. По мере развития и роста интереса к тому, чтобы большее число людей могли создавать что-либо, появлялись новые идеи и методы программирования этих машин, которые раньше сводились к изменению положения соединителей. Именно тогда появились первые терминалы.
Альтернативные показатели риска и доходности в MQL5 Альтернативные показатели риска и доходности в MQL5
В этой статье мы представим реализацию нескольких показателей доходности и риска, рассматриваемых как альтернативы коэффициенту Шарпа, и исследуем гипотетические кривые капитала для анализа их характеристик.