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

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

MetaTrader 5Тестер | 4 марта 2024, 08:54
393 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 28): Проект Expert Advisor — класс C_Mouse (II), мы рассмотрели, как создать более читаемый код. Хотя данная модель весьма интересна для того, чтобы сделать программу более понятной, думаю, вы заметите: программирование с использованием этого подхода может занять больше времени. Но это происходит не из-за того, что программирование становится запутанным, а как раз наоборот. Сложность заключается в том, что данный способ сделать программу более читабельной имеет свои ограничения. Одним из таких ограничений является синтаксис любого языка программирования. Хотя синтаксис спроектирован так, чтобы иметь определенный формат и структуру, обращение к определениям хотя и помогает, но действует как костыль, ограничивая нас в других аспектах. Тем не менее, считаю уместным показать это в реальном коде со своей точки зрения.

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

Расширяя свое сознание, выйдем за рамки обычного, и перед нами откроется целая вселенная.

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

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


Готовим почву для расширения

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

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, 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;
         bool    ExecStudy;
      };

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

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);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

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

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

Далее мы внесем небольшие изменения в следующую функцию:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().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);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().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) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.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 (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

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

  1. Данная строка будет корректировать значение времени, чтобы мы могли фактически использовать любой объект на графике, используя не только экранные координаты (X и Y), но и координаты актива (Цена и Время). Ответ на этот вопрос я искал очень долго. И это с учетом того, что работать в координатах актива гораздо интереснее, чем иногда пользоваться экранными координатами. Уровень свободы, который это нам дает, очевиден. Но можно заметить, что мы совершаем вызов, и по практическим соображениям это происходит в классе C_Terminal.
  2. Добавлен этот вызов в функцию ChartTimePriceToXY, чтобы координаты актива преобразовались в координаты экрана.
  3. И последний пункт был именно таким. Здесь мы указываем, находится ли класс C_Mouse в режиме обучения. Чтобы не запутаться, обратите внимание на синтаксис.

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

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

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

Когда эта функция вызывается, она получает значение даты и времени в качестве параметра. Важно рассматривать данное значение не только как дату и время, а как значение ulong, поскольку оно и является таким на самом деле: 8-байтовое значение. Во-первых, нужно понимать это. Следующее, что нужно понять: поскольку datetime является значением ulong, то информация о дате и времени сжимается внутри переменной довольно специфическим образом. То, что нас действительно интересует, - это младшие биты (LSB), где значения секунды, минуты, часа, дня, месяца и года расположены в порядке от младшего бита к самому значимому биту.

Важно отметить, что nSeconds содержит значение в секундах периода, используемого на графике. Это определяется функцией PeriodSeconds, которая предоставляет данную информацию. Теперь переменная dt содержит значение создания последнего бара, присутствующего на графике, независимо от используемого актива. Этот момент для нас актуален, поскольку если значение dt меньше значения параметра вызова, это будет указывать, в каком моменте времени мы находимся, то есть в будущем. Таким образом, положение времени относительно графической координаты экрана (X и Y) указывает, где находится или будет создан будущий бар. На данный момент невозможно узнать с помощью функции iTime, где это будет, поскольку график до этой точки еще не построен. Однако даже тогда нам необходимо знать, где оно будет на самом деле, особенно если мы проводим исследование графика, связанное с будущим.

Чтобы узнать координаты экрана (X и Y), мы воспользуемся тем, что datetime по сути является значением ulong. Разделив данное значение на количество секунд nSeconds, мы получим значение double, то есть дробное значение. Далее идет важный момент: если мы умножим это дробное значение на количество nSeconds, мы получим исходное значение. Например, если мы разделим 10 на 3 и умножим результат деления на 3, мы снова получим 10. Однако при выполнении преобразования типов (и вот в этом ключ) мы преобразуем значение double в значение ulong или, что еще лучше, в значение типа datetime. Здесь нет дробной части. Следовательно, умножив значение на nSeconds, мы получим значение в будущем и уже исправленное. Это самый интересный момент. Однако у данного метода есть одна проблема.

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

Вы можете подумать, что данная функция совершенно не нужна. И зачем утруждать себя её разработкой? Однако, с помощью этой функции можно преобразовать координаты актива (Цена и Время) в экранные координаты (X и Y). Таким образом, мы сможем использовать любой вид графического объекта, не ограничиваясь экранными координатами или объектами ресурсов. У нас будет возможность конвертировать один тип координат в другой, и для этого мы будем использовать вызовы: ChartXYToTimePrice (для преобразования экранных координат в координаты актива) и ChartTimePriceToXY (для преобразования координат актива в экранные координаты). Помните этот важный факт: в некоторых видах исследований нам нужно владеть как можно более точной информацией. Когда мы хотим, чтобы конкретный бар использовался в качестве индикаторной точки для чего-либо, данное преобразование становится необходимым. Кроме того, это нам дает очень интересную информацию, которую мы рассмотрим позже.


Создаем класс C_Studys

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

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

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

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

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

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

Данный вид определения (псевдоним) довольно распространен, когда мы хотим получить доступ к определенным вещам, но не хотим рисковать вводить что-то неправильно во время написания кода. Это всегда очень интересный ресурс.

Как только данное введение будет завершено, мы запустим код класса, который представлен следующим образом:

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

На этом этапе мы фокусируемся исключительно на частных переменных класса. Обратите внимание на один из них, который действует как своего рода указатель на класс C_Mouse. Указатели являются одними из самых мощных ресурсов в программировании, но они требуют особого внимания из-за проблем, которые могут возникнуть при их использовании. Поэтому очень важно использовать указатели осторожно, даже если в MQL5 они не имеют тех же характеристик, что и в C/C++. Нужно соблюдать осторожность, чтобы избежать осложнений. Нельзя недооценивать указатели. В остальной части кода пока нет ничего особенного.

Первая из функций класса представлена ​​ниже:

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

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

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

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

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

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

Далее у нас есть функция, которая отображает информацию на нашем графике.

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

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

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

  • Оставшееся время до появления следующего бара на графике;
  • Информация о том, что рынок закрыт;
  • Информация о том, что мы имеем дело с репликацией;
  • Информация о том, что актив выставлен на аукцион;
  • И (в исключительно редких случаях) сообщение об ошибке.

Обратите внимание: то, что мы делаем, не является чем-то необычным. Однако это служит лишь для демонстрации техники работы, которую мы будем использовать. Далее мы представляем деструктор.

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

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

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

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                         break;
         case eAuction   : m_Info.szInfo = "Auction";                                break;
         case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break;
         case eInReplay  : m_Info.szInfo = "In Replay";                              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();
   }

Но как же так: у нас есть две функции с именем Update?! Такое возможно?! Да, мы можем объявлять функции с одинаковым именем. Это явление известно как перегрузка. Хотя имена схожи, для компилятора имена обеих функций различны. Это связано с параметрами. Таким способом можно перегружать функции и процедуры, но единственное правило — параметры должны быть разными. Нельзя забывать про то, что во второй функции мы вызываем первую, используя метод, параметры которого не требуются. Такая практика довольно распространена при программировании методов, которые оказываются перегружены, и стремятся создать нечто такое, что облегчит отладку.

Теперь мы приближаемся к интересному моменту: как мы можем узнать, выставлен ли актив на аукцион или нет? Обычно информация о ценах представляется в книге заявок. Однако может случиться так, что в книге заявок актива будет указано одно из этих конкретных значений, и когда это произойдет, это будет означать, что актив выставлен на аукцион. Обратите на это внимание, ведь это может быть интересным ресурсом для добавления в автоматический советник. Я уже говорил об этом в другой серии статей:  Как создать советник, который торгует автоматически (Часть 14): Автоматизация (VI), но я не стал подробно рассказывать о том, как понять, выставлен ли актив на аукцион или нет. Мы не ставили своей целью подробное освещение всех аспектов торгового процесса с помощью автоматического советника.

Чтобы реагировать на некоторые события платформы, у нас есть следующая функция:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

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

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

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

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

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

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

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

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

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

Размер шрифта определяется с помощью положительных или отрицательных значений. Данный факт определяет зависимость размера текста от настроек операционной системы (шкалы размеров).

  • Если размер указан положительным числом, этот размер преобразуется в единицы физических измерений устройства (пиксели) при переходе от логического шрифта к физическому, и данный размер соответствует высоте символов глифа, выбранных из доступных шрифтов. Данный случай не рекомендуется с текстами, отображаемыми функцией TextOut(), и текстами, отображаемыми OBJ_LABEL («Метки»), где графические объекты используются вместе на графике..
  • Если размер определяется отрицательным числом, это число должно быть определено в десятых долях логической точки (-350 равно 35 логическим точкам), разделенной на 10. Полученное значение затем преобразуется в единицы физических измерений устройства (пиксели) и соответствует абсолютной величине высоты символа, выбранному из доступных шрифтов. Умножьте размер шрифта, определенный в свойствах объекта, на -10, чтобы сделать размер текста на экране аналогичным размеру объекта OBJ_LABEL.

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


Заключение

Обязательно попробуйте прилагаемое нами приложение, оно предлагает доступ к текущей системе. Эксперименты желательно проводить как в репликации/моделировании, так и на аккаунте, работающем на маркете (DEMO или REAL),

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

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

Прикрепленные файлы |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
Возможности Мастера MQL5, которые вам нужно знать (Часть 07): Дендрограммы Возможности Мастера MQL5, которые вам нужно знать (Часть 07): Дендрограммы
Классификация данных для анализа и прогнозирования — очень разнообразная область машинного обучения с большим количеством подходов и методов. В этой статье рассматривается один из таких подходов, а именно агломеративная иерархическая классификация (Agglomerative Hierarchical Classification).
DoEasy. Сервисные функции (Часть 1): Ценовые паттерны DoEasy. Сервисные функции (Часть 1): Ценовые паттерны
В статье начнём разрабатывать методы поиска ценовых паттернов по данным таймсерий. Паттерн имеет определённый набор параметров, общий для любого вида и типа паттернов. Все данные такого рода будут сосредоточены в классе объекта базового абстрактного паттерна. Сегодня создадим класс абстрактного паттерна и класс паттерна Пин-бар.
Показатель склонности (Propensity score) в причинно-следственном выводе Показатель склонности (Propensity score) в причинно-следственном выводе
В статье рассматривается тема матчинга в причинно-следственном выводе. Матчинг используется для сопоставления похожих наблюдений в наборе данных. Это необходимо для правильного определения каузальных эффектов, избавления от предвзятости. Автор рассказывает, как это помогает в построении торговых систем на машинном обучении, которые становятся более устойчивыми на новых данных, на которых не обучались. Центральная роль отводится показателю склонности, который широко используется в причинно-следственном выводе.
Нейросети — это просто (Часть 79): Агрегирование запросов в контексте состояния (FAQ) Нейросети — это просто (Часть 79): Агрегирование запросов в контексте состояния (FAQ)
В предыдущей статье мы познакомились с одним из методом обнаружение объектов на изображении. Однако, обработка статического изображения несколько отличается от работы с динамическими временными рядами, к которым относится и динамика анализируемых нами цен. В данной статье я хочу предложить Вам познакомиться с методом обнаружения объектов на видео, что несколько ближе к решаемой нами задаче.