English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации - Моделирование рынка (Часть 25): Подготовка к следующему этапу

Разработка системы репликации - Моделирование рынка (Часть 25): Подготовка к следующему этапу

MetaTrader 5Тестер | 10 февраля 2024, 13:56
605 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка системы репликации - Моделирование рынка (Часть 24): ФОРЕКС (V)" я продемонстрировал, как можно вполне гармонично интегрировать две вселенные, которые на первый взгляд кажутся разными. С одной стороны рынок с ценой построения графика в BID, а с другой - с ценой в LAST. Реальная задача заключалась в том, чтобы создать метод, который моделировал бы или, точнее, генерировал вероятное движение цены, считая только бары, которые в идеале представляют графическое время в 1 минуту. Без сомнения, это была интригующая и приятная задача, которую необходимо было преодолеть. Представленное решение, хотя и эффективное, но не является единственным способом достижения этой конкретной цели. Однако, поскольку решение оказалось эффективным, я считаю данный этап завершенным. До тех пор, пока он не окажется неспособным решить конкретную модель. В этом случае мы снова улучшим предложенное решение, чтобы оно могло охватывать нерешенную до сих пор модель.

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

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


Ограничиваем использование индикатора управления

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

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


Начальный этап: Включение и выключение кнопок тонкой настройки

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

Рисунок 01

Рисунок 01: Расположение кнопок тонкой настройки

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

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

#define def_ButtonPlay          "Images\\Market Replay\\Play.bmp"
#define def_ButtonPause         "Images\\Market Replay\\Pause.bmp"
#define def_ButtonLeft          "Images\\Market Replay\\Left.bmp"
#define def_ButtonLeftBlock     "Images\\Market Replay\\Left_Block.bmp"
#define def_ButtonRight         "Images\\Market Replay\\Right.bmp"
#define def_ButtonRightBlock    "Images\\Market Replay\\Right_Block.bmp"
#define def_ButtonPin           "Images\\Market Replay\\Pin.bmp"
#define def_ButtonWait          "Images\\Market Replay\\Wait.bmp"
#resource "\\" + def_ButtonPlay
#resource "\\" + def_ButtonPause
#resource "\\" + def_ButtonLeft
#resource "\\" + def_ButtonLeftBlock
#resource "\\" + def_ButtonRight
#resource "\\" + def_ButtonRightBlock
#resource "\\" + def_ButtonPin
#resource "\\" + def_ButtonWait

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

void CreteCtrlSlider(void)
   {
      u_Interprocess Info;
                                
      m_Slider.szBarSlider = def_NameObjectsSlider + " Bar";
      m_Slider.szBtnLeft   = def_NameObjectsSlider + " BtnL";
      m_Slider.szBtnRight  = def_NameObjectsSlider + " BtnR";
      m_Slider.szBtnPin    = def_NameObjectsSlider + " BtnP";
      m_Slider.posY = 40;
      CreteBarSlider(82, 436);
      CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
      CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
      CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
      if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
      PositionPinSlider(Info.s_Infos.iPosShift);

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

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

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

Анимация 01

Анимация 01: Демонстрация системы включения/выключения кнопок

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


Сообщаем пользователю об изменении границ

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

Анимация 02

Анимация 02: Но почему я не могу дойти до нуля?

В анимации 02 очевидна путаница, которая может возникнуть у пользователя, когда ползунок не достигает нуля, несмотря на то, что левая кнопка сообщает о невозможности передвижения. Данная ситуация показывает, что текущие показания недостаточно ясны, что указывает на необходимость улучшения сообщения о существующих ограничениях или границах, которые мешают передвижению ползунка дальше определенной точки. Теперь, прежде чем подробно описывать, как будет реализована эта индикация, вам должно быть интересно узнать, какой метод используется для блокировки элемента управления до того, как он достигнет нулевой точки. Признайтесь, любопытство – это здорово! К счастью, я не прибегал к каким-то сложным программным уловкам; я просто определил точку остановки. Но где именно? Местоположение можно увидеть ниже:

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

Вы, должно быть, задаетесь вопросом: "что здесь было сделано?" Не волнуйтесь, следует отметить тонкую, но важную деталь: переменная `minimal` установлена ​​на ноль. Что произойдет, если мы изменим это значение, скажем, на 100 или 80? Проверка значения в этой точке приведет к отключению кнопки в левом углу. Однако это не помешает системе уменьшить значение, если пользователь щелкнет левой кнопкой мыши или перетащит ползунок влево. Это верно. Однако теперь я устанавливаю ползунок в положение, точно определяемое переменной `minimal`. Вы понимаете? Независимо от того, сколько пользователь пытается переместить ползунок или нажать левую кнопку, указанная точка не опустится ниже значения, установленного как минимально возможное.

Интересно, правда? Определение минимально возможного значения является задачей сервиса репликации/моделирования, которая автоматически корректирует это значение по мере продвижения репликации или моделирования. Однако пользователь может изменить эту точку, если сервис не изменил минимальное значение, которое можно использовать. Это может показаться сложным, но это проще, чем вы думаете, хотя мы углубимся в это позже. А пока давайте сосредоточимся на проблеме, поднятой анимацией 02, которая показывает отсутствие четкого указания пользователю о положении нижней границы. Есть несколько способов сделать это, некоторые могут показаться эстетически странными, а другие – немного причудливыми. Однако мы можем выбрать промежуточное решение. Как насчет создания подсказки «на стене»? На мой взгляд, это разумный выбор, поскольку он может предложить интересный визуальный аспект. Если вы художник-график, результат может быть даже лучше, чем в том примере, который я использую. Для этого мы будем использовать следующий код:

inline void CreteBarSlider(int x, int size)
   {
      ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
//---
      ObjectCreate(m_id, m_Slider.szBarSliderBlock, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Slider.posY - 9);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
   }

Строки, выделенные зеленым, обозначают код, который создает такую ​​индикацию нижнего предела. И да, для этого мы используем объект. Но при желании вы можете использовать растровое изображение для получения более привлекательных визуальных результатов. Однако я хочу, чтобы код был простым, учитывая, что многие из читателей могут иметь ограниченные знания в области программирования. Таким образом, более доступный код облегчает понимание того, как все было реализовано. Добавить растровое изображение или даже текстурный рисунок несложно, а результаты могут быть весьма интересными, особенно если вы используете программирование через DirectX. И да, MQL5 позволяет это. Но мы оставим это на другой раз. А пока давайте сделаем всё простым, но функциональным. При этом результат будет таким, как показано на анимации 03 ниже:

Анимация 03

Анимация 03: Теперь у нас есть нижняя граница...

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

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
      ChartRedraw();
   }

Размер полосы индикатора определяется переменной `minimal`, это означает, что по мере того, как сервис репликации/моделирования обновляет свои данные, полоса будет корректироваться пропорционально. Теперь следующий шаг — убедиться, что это ограничение правильно обновлено сервисом репликации/моделирования. Этой теме будет посвящена следующая тема.


Разговариваем со сервисом репликации/моделирования

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

class C_Controls
{
   private :
//+------------------------------------------------------------------+
      string  m_szBtnPlay;
      long    m_id;
      bool    m_bWait;
      struct st_00
      {
         string  szBtnLeft,
                 szBtnRight,
                 szBtnPin,
                 szBarSlider,
                 szBarSliderBlock;
         int     posPinSlider,
                 posY,
                 Minimal;
      }m_Slider;
//+------------------------------------------------------------------+
      void CreteCtrlSlider(void)
         {
            u_Interprocess Info;
                                
            m_Slider.szBarSlider      = def_NameObjectsSlider + " Bar";
            m_Slider.szBarSliderBlock = def_NameObjectsSlider + " Bar Block";
            m_Slider.szBtnLeft        = def_NameObjectsSlider + " BtnL";
            m_Slider.szBtnRight       = def_NameObjectsSlider + " BtnR";
            m_Slider.szBtnPin         = def_NameObjectsSlider + " BtnP";
            m_Slider.posY = 40;
            CreteBarSlider(82, 436);
            CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
            CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
            CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
            if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
            m_Slider.Minimal = Info.s_Infos.iPosShift;
            PositionPinSlider(Info.s_Infos.iPosShift);
         }
//+------------------------------------------------------------------+
inline void PositionPinSlider(int p, const int minimal = 0)
         {
            m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            m_Slider.posPinSlider = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != m_Slider.Minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
            ChartRedraw();
         }
//+------------------------------------------------------------------+

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

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


Решаем эстетические проблемы в сервисе репликации/моделирования.

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

void AdjustPositionToReplay(const bool bViewBuider)
   {
      u_Interprocess Info;
      MqlRates       Rate[def_BarsDiary];
      int            iPos, nCount;
                                
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
      iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
      Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
      if (iPos < m_ReplayCount)
      {
         CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
         CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, LONG_MAX);
         if ((m_dtPrevLoading == 0) && (iPos == 0)) FirstBarNULL(); else
         {
            for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
            m_ReplayCount++;
         }
      }else if (iPos > m_ReplayCount)
      {
      CreateBarInReplay(true);
      if (bViewBuider)
      {
         Info.s_Infos.isWait = true;
         GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
      }else
      {
         for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++);
         for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
         nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
      }
      for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false);
      CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount);
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      Info.s_Infos.isWait = false;
      GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
   }

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

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

bool LoopEventOnTime(const bool bViewBuider)
   {
      u_Interprocess Info;
      int iPos, iTest;
                                
      if (!m_Infos.bInit) ViewInfos();
      if (!m_Infos.bInit)
      {
         ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
         m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
         m_MountBar.Rate[0].time = 0;
         m_Infos.bInit = true;
         ChartRedraw(m_IdReplay);
      }
      iTest = 0;
      while ((iTest == 0) && (!_StopFlag))
      {
         iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
         iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
         iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
         if (iTest == 0) Sleep(100);
      }
      if ((iTest < 0) || (_StopFlag)) return false;
      AdjustPositionToReplay(bViewBuider);
      iPos = 0;
      while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
      {
         iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
         CreateBarInReplay(true);
         while ((iPos > 200) && (!_StopFlag))
         {
            if (ChartSymbol(m_IdReplay) == "") return false;
            GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            if (!Info.s_Infos.isPlay) return true;
            Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            Sleep(195);
            iPos -= 200;
         }
      }                               
      return (m_ReplayCount == m_Ticks.nTicks);
   }                               

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

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

void ViewInfos(void)
   {
      MqlRates Rate[1];
                                
      ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
      m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
      m_MountBar.Rate[0].time = 0;
      m_Infos.bInit = true;
      CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))

         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      if (Rate[0].close > 0)
      {
         if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else
         {
            m_Infos.tick[0].bid = Rate[0].close;
            m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick);
         }                                       
         m_Infos.tick[0].time = Rate[0].time;
         m_Infos.tick[0].time_msc = Rate[0].time * 1000;
      }else
         m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount];
      CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
      ChartRedraw(m_IdReplay);
   }

Эти строки кода были извлечены из функции, части которой были перечеркнуты. Основное внимание здесь уделяется необходимым дополнительным строкам. Что мы делаем, так это идентифицируем последний бар, помещенный на график сервисом репликации/моделирования, используя общую функцию для работы с индикаторами. Если нам удастся захватить бар, то есть если значение закрытия больше нуля, мы установим специальный тик в зависимости от используемого режима построения. Если значение закрытия равно нулю, мы будем использовать первый действительный тик из списка загруженных или смоделированных тиков. Функция, отвечающая за поиск действительного тика, — это именно две упомянутые строки. Эта функция будет особенно полезна при работе в режиме построения LAST, поскольку в режиме BID первый тик уже действителен. В конечном итоге этот специально созданный тик будет отображаться в окне наблюдения за рынком, в результате чего ценовые линии появятся на графике, как только сервис даст команду платформе MetaTrader 5 открыть график.

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

inline void FirstBarNULL(void)
   {
      MqlRates rate[1];
      int c0 = 0;
                                
      for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
      rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
      rate[0].open = rate[0].high = rate[0].low = rate[0].close;
      rate[0].tick_volume = 0;
      rate[0].real_volume = 0;
      rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
      CustomRatesUpdate(def_SymbolReplay, rate);
      m_ReplayCount = 0;
   }

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


Заключение

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

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

Прикрепленные файлы |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Альтернативные показатели риска и доходности в MQL5 Альтернативные показатели риска и доходности в MQL5
В этой статье мы представим реализацию нескольких показателей доходности и риска, рассматриваемых как альтернативы коэффициенту Шарпа, и исследуем гипотетические кривые капитала для анализа их характеристик.
Разработка системы репликации - Моделирование рынка (Часть 24): FOREX (V) Разработка системы репликации - Моделирование рынка (Часть 24): FOREX (V)
Сегодня мы снимем ограничение, которое препятствовало выполнению моделирований, основанных на построении LAST, и введем новую точку входа специально для этого типа моделирования. Обратите внимание на то, что весь механизм работы будет основан на принципах валютного рынка. Основное различие в данной процедуре заключается в разделении моделирований BID и LAST. Однако важно отметить, что методология, используемая при рандомизации времени и его корректировке для совместимости с классом C_Replay, остается идентичной в обоих видах моделирования. Это хорошо, поскольку изменения в одном режиме приводят к автоматическим улучшениям в другом, особенно если это касается обработки времени между тиками.
Разработка системы репликации (Часть 26): Проект советника — Класс C_Terminal Разработка системы репликации (Часть 26): Проект советника — Класс C_Terminal
Мы уже можем начать создавать советника для использования в репликации/моделировании. Однако нам нужно нечто усовершенствованное, а не какое-то случайное решение. Несмотря на это, нас не должна пугать первоначальная сложность. Очень важно начать с чего-то, иначе в конечном итоге мы придем к тому, что размышляем о сложности задачи, даже не пытаясь ее преодолеть. Суть программирования именно в этом: преодолеть препятствия посредством изучения, тестирования и обширных исследований.
Нейросети — это просто (Часть 76): Изучение разнообразных режимов взаимодействия (Multi-future Transformer) Нейросети — это просто (Часть 76): Изучение разнообразных режимов взаимодействия (Multi-future Transformer)
В данной статье мы продолжаем тему прогнозирования предстоящего ценового движения. И предлагаю Вам познакомиться с архитектурой Multi-future Transformer. Основная идея которого заключается в разложении мультимодального распределение будущего на несколько унимодальных распределений, что позволяет эффективно моделировать разнообразные модели взаимодействия между агентами на сцене.