English Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 66): Нажатие кнопки воспроизведения в сервисе (VII)

Разработка системы репликации (Часть 66): Нажатие кнопки воспроизведения в сервисе (VII)

MetaTrader 5Примеры | 12 февраля 2025, 08:39
265 0
Daniel Jose
Daniel Jose

Введение

В этой статье мы начнем немного с другого. Но сначала давайте вспомним, о чем шла речь в предыдущей статье, "Разработка системы репликации (часть 65)": Нажатие кнопки воспроизведения в сервисе (VI)". Здесь мы решили проблему с процентным показателем, отображаемым курсором мыши, который при использовании данного же курсора в системе репликации или моделирования давал нам неверное значение. Кроме того, мы внедрили систему, которая позволяет быстро переходить к определенному моменту времени и не ждать несколько минут или даже часов. Но прежде, чем вы продолжите читать эту статью, я хотел бы, чтобы вы посмотрели следующее видео. Это поможет вам понять то, что я объясню в начале статьи.


Есть на что посмотреть...

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

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

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


Реализуем оставшееся время для следующего бара

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

В определенных таймфреймах или благодаря опыту трейдера можно получить приблизительное представление об оставшемся времени. Однако начинающие трейдеры еще не обладают такой способностью. Поэтому им необходимо реализовать какой-то индикатор. В случае с реальным рынком это сделать довольно просто, поскольку мы всегда подключены к торговому серверу (на демо- или реальном счету). Кроме того, тот факт, что операции постоянно развиваются, еще больше упрощает внедрение данной индикации, поскольку время не стоит на месте. Но именно этот последний аспект чуть усложняет ситуацию при применении репликации/моделирования. Тот факт, что пользователь может приостановить работу приложения на неопределенное время или даже перейти в положение, когда бары находятся в начале или почти в конце, значительно усложняет ситуацию. Доходит до того, что приходится жонглировать, чтобы не создать "Франкенштейна" только для того, чтобы отчитаться за оставшееся время работы бара.

Прежде чем делать что-либо еще, давайте посмотрим, что у нас есть. В предыдущей статье мы изменили код указателя мыши, чтобы начать использовать другую версию функции, которая обрабатывает событие OnCalculate. В некотором смысле это изменение будет очень полезным в данном случае, ведь у нас есть массив, в котором хранится значение времени. Немного поработав, мы получим фрагмент, показанный ниже:
44. //+------------------------------------------------------------------+
45. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
46.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
47.                 const long& volume[], const int& spread[]) 
48. {
49.    Print(TimeToString(time[rates_total - 1], TIME_DATE | TIME_SECONDS)); // To Testing ...
50.    GL_PriceClose = close[rates_total - 1];
51.    m_posBuff = rates_total;
52.    (*Study).Update(m_Status);   
53.    
54.    return rates_total;
55. }
56. //+------------------------------------------------------------------+

Фрагмент исходного кода: Mouse Study.mq5

Была добавлена новая строка. Строка 49 позволит нам вывести последнее значение массива time. А теперь обратите внимание на кое-что важное. Данное значение в массиве задается для того, чтобы минутный бар строился правильно, то есть в пределах минутного окна.

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

Anime 01

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

69. //+------------------------------------------------------------------+
70. inline void CreateBarInReplay(bool bViewTick)
71.          {
72.             bool    bNew;
73.             double dSpread;
74.             int    iRand = rand();
75. 
76.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
77.             {
78.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
79.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
80.                {                  
81.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
82.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
83.                   {
84.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
85.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
86.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
87.                   {
88.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
89.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
90.                   }
91.                }
92.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
93.                m_Infos.Rate[0].time = m_MemoryData.Info[m_Infos.CountReplay].time; //< To Testing...
94.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
95.             }
96.             m_Infos.CountReplay++;
97.          }
98. //+------------------------------------------------------------------+

Фрагмент исходного кода: C_Replay.mqh

Это интересно и просто в реализации. Чтобы получить возможность добавлять значение секунд, достаточно добавить строку 93 к фрагменту кода. Проще не бывает. Но будет ли это действительно работать? Помните следующее: в документации к функции CustomRatesUpdate указано, что значение времени, за которое должны быть созданы Rates, должно находиться в пределах одноминутного окна. Можно подумать, что это не проблема, поскольку мы не изменяем окно, а просто добавляем значение секунд, которое, скорее всего, будет проигнорировано функцией, но будет передано указателю мыши, чтобы его обнаружила функция OnCalculate. Да, в каком-то смысле я должен с вами согласиться, но пока мы не проверим выдвинутую гипотезу, она останется лишь гипотезой. Затем мы скомпилируем код, и результат показан на следующем видео:


Краткая демонстрация

Что это было? Что за безумие произошло! Я не ожидал, что такое произойдет, я ведь думал, что это сработает. На самом деле, идея не совсем неверная: наверное, вы заметили, что указатель мыши показывает то, что мы хотим. Однако содержание схемы... Корочке говоря, нам лучше найти другой способ сделать это. Но вы, конечно, заметили, что мы действительно можем реализовать довольно интересные вещи. На самом деле, в прошлом я использовал именно эту идею для создания индикатора. С этим индикатором, по крайней мере, в его открытой версии, вы можете ознакомиться в статье "Разработка торгового советника с нуля (Часть 13)": Время и торговля (II)". Данная версия уже устарела, но она поможет вам понять, что здесь произошло. И пока никто не спросил: НЕТ, я не буду продавать, отдавать, одалживать или демонстрировать самую актуальную версию данного индикатора, он предназначен для личного пользования.

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

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


Другой способ сообщения об оставшемся времени

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

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

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

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

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

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

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


Реализуем базовую версию, чтобы сообщать об оставшемся времени

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

12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. datetime GL_TimeAdjust;
15. //+------------------------------------------------------------------+
16. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
17. //+------------------------------------------------------------------+
18. C_Study *Study       = NULL;
19. //+------------------------------------------------------------------+
20. input color user02   = clrBlack;                         //Price Line
21. input color user03   = clrPaleGreen;                     //Positive Study
22. input color user04   = clrLightCoral;                    //Negative Study
23. //+------------------------------------------------------------------+
24. C_Study::eStatusMarket m_Status;
25. int m_posBuff = 0;
26. double m_Buff[];
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {
30.    ResetLastError();
31.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
32.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
33.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
34.    {
35.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
36.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
37.       m_Status = C_Study::eCloseMarket;
38.    }else
39.       m_Status = C_Study::eInReplay;
40.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
41.    ArrayInitialize(m_Buff, EMPTY_VALUE);
42.    
43.    return INIT_SUCCEEDED;
44. }
45. //+------------------------------------------------------------------+
46. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
47.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
48.                 const long& volume[], const int& spread[]) 
49. {
50.    GL_PriceClose = close[rates_total - 1];
51.    GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0);
52.    m_posBuff = rates_total;
53.    (*Study).Update(m_Status);   
54.    
55.    return rates_total;
56. }
57. //+------------------------------------------------------------------+

Фрагмент исходного кода: Mouse Study.mq5

Если сравнить этот фрагмент с последней версией исходного кода указателя мыши, то увидим, что в него добавлена строка 14. То есть теперь у нас есть новая глобальная переменная в индикаторе. Я не собираюсь добавлять больше никаких глобальных переменных в данный модуль указателя мыши. Но эта переменная особенная по причине, которую вы скоро поймете. В любом случае, переменная получит значение в строке 51. Это значение будет передаваться через спред. В этом кроется опасность, которая в то же время граничит с тем, чтобы быть чем-то полезным. В любом случае, эта глобальная переменная используется только очень конкретным образом. Чтобы лучше понять это, давайте посмотрим на новый код в файле C_Study.mqh, показанном ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          datetime       TimeDevice;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       void Draw(void)
028.          {
029.             double v1;
030.             
031.             if (m_Info.bvT)
032.             {
033.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
034.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
035.             }
036.             if (m_Info.bvD)
037.             {
038.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
039.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
040.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
041.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
042.             }
043.             if (m_Info.bvP)
044.             {
045.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
046.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
047.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
048.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
049.             }
050.          }
051. //+------------------------------------------------------------------+
052. inline void CreateObjInfo(EnumEvents arg)
053.          {
054.             switch (arg)
055.             {
056.                case evShowBarTime:
057.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
058.                   m_Info.bvT = true;
059.                   break;
060.                case evShowDailyVar:
061.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
062.                   m_Info.bvD = true;
063.                   break;
064.                case evShowPriceVar:
065.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
066.                   m_Info.bvP = true;
067.                   break;
068.             }
069.          }
070. //+------------------------------------------------------------------+
071. inline void RemoveObjInfo(EnumEvents arg)
072.          {
073.             string sz;
074.             
075.             switch (arg)
076.             {
077.                case evHideBarTime:
078.                   sz = m_Info.szBtn1;
079.                   m_Info.bvT = false;
080.                   break;
081.                case evHideDailyVar:
082.                   sz = m_Info.szBtn2;
083.                   m_Info.bvD   = false;
084.                   break;
085.                case evHidePriceVar:
086.                   sz = m_Info.szBtn3;
087.                   m_Info.bvP = false;
088.                   break;
089.             }
090.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
091.             ObjectDelete(GetInfoTerminal().ID, sz);
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
093.          }
094. //+------------------------------------------------------------------+
095.    public   :
096. //+------------------------------------------------------------------+
097.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
098.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
099.          {
100.             if (_LastError != ERR_SUCCESS) return;
101.             ZeroMemory(m_Info);
102.             m_Info.Status = eCloseMarket;
103.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
104.             m_Info.corP = corP;
105.             m_Info.corN = corN;
106.             CreateObjInfo(evShowBarTime);
107.             CreateObjInfo(evShowDailyVar);
108.             CreateObjInfo(evShowPriceVar);
109.          }
110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                      
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading     :
123.                   i0 = PeriodSeconds();
124.                   dt = (m_Info.Status == eInReplay ? m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
127.                   break;
128.                case eAuction      :
129.                   m_Info.szInfo = "Auction";
130.                   break;
131.                default            :
132.                   m_Info.szInfo = "ERROR";
133.             }
134.             Draw();
135.          }
136. //+------------------------------------------------------------------+
137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
138.          {
139.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
140.             switch (id)
141.             {
142.                case CHARTEVENT_CUSTOM + evHideBarTime:
143.                   RemoveObjInfo(evHideBarTime);
144.                   break;
145.                case CHARTEVENT_CUSTOM + evShowBarTime:
146.                   CreateObjInfo(evShowBarTime);
147.                   break;
148.                case CHARTEVENT_CUSTOM + evHideDailyVar:
149.                   RemoveObjInfo(evHideDailyVar);
150.                   break;
151.                case CHARTEVENT_CUSTOM + evShowDailyVar:
152.                   CreateObjInfo(evShowDailyVar);
153.                   break;
154.                case CHARTEVENT_CUSTOM + evHidePriceVar:
155.                   RemoveObjInfo(evHidePriceVar);
156.                   break;
157.                case CHARTEVENT_CUSTOM + evShowPriceVar:
158.                   CreateObjInfo(evShowPriceVar);
159.                   break;
160.                case (CHARTEVENT_CUSTOM + evSetServerTime):
161.                   m_Info.TimeDevice = (datetime)lparam;
162.                   break;
163.                case CHARTEVENT_MOUSE_MOVE:
164.                   Draw();
165.                   break;
166.             }
167.             ChartRedraw(GetInfoTerminal().ID);
168.          }
169. //+------------------------------------------------------------------+
170. };
171. //+------------------------------------------------------------------+
172. #undef def_ExpansionPrefix
173. #undef def_MousePrefixName
174. //+------------------------------------------------------------------+

Файл исходного кода: C_Study.mqh

Хорошо. Очень хорошо. Будьте внимательны, потому что то, что сейчас будет объяснено, крайне важно для понимания общего функционирования. Вы заметите, что код в заголовочном файле изменился. Однако есть только два действительно важных момента. Первый находится в строке 160, где мы имеем дело с настраиваемым событием. Обратите внимание: в строке 161 мы сохраняем значение, которое передает нам событие в приватной переменной класса. Это первый момент. Теперь перейдем к тому месту, где действительно происходит "волшебство". Для этого мы поднимемся на несколько строк и перейдем к строке 111, где находится процедура Update. В результате данной процедуры будет сгенерирована информация, которая отобразится позже. Будьте внимательны. В строке 123 мы фиксируем количество секунд во временном периоде графика пользовательского символа. В строке 124 мы выполним небольшой расчет или воспользуемся значением, которое предоставляет нам MetaTrader 5. Решение о том, выполнять ли расчет или использовать значение, зависит от статуса индикатора. Когда мы наводим курсор мыши на символ репликации, мы выполняем вычисление. Иначе мы используем значение, предоставляемое MetaTrader 5.

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

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

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

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

1. //+------------------------------------------------------------------+
2. #property copyright "Daniel Jose"
3. //+------------------------------------------------------------------+
4. #define macroRemoveSec(A) (A - (A % 60))
5. #define macroGetDate(A)   (A - (A % 86400))
6. #define macroGetSec(A)    (A - (A - (A % 60)))
7. //+------------------------------------------------------------------+

Файл исходного кода: Macros.mqh

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_FileBars.mqh"
05. #include "C_Simulation.mqh"
06. #include "..\..\Auxiliar\Macros.mqh"
07. //+------------------------------------------------------------------+
08. //#define macroRemoveSec(A)   (A - (A % 60))
09. #define def_MaxSizeArray    16777216 // 16 Mbytes
10. //+------------------------------------------------------------------+
11. class C_FileTicks
12. {
13.    protected:
14.       enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};

Фрагмент исходного кода: C_FilesTick.mqh

Обратите внимание, что в данном случае была добавлена строка 06, а строку 08, которую выделили, мы должны удалить из кода. Так происходит, потому что заголовочный файл Macros.mqh теперь содержит код, который ранее находился в выделенной строке. Очень хорошо. После внесения данных изменений мы можем приступить к работе с файлом C_Replay.mqh. Новый код для этого файла представлен ниже в полном объеме.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl    "Market Replay Control"
013. #define def_MaxSlider             (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.            uCast_Double             Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void UpdateIndicatorControl(void)
038.          {
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
045.                   m_IndControl.Memory.dValue = Buff[0];
046.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+
058.       void SweepAndCloseChart(void)
059.          {
060.             long id;
061.             
062.             if ((id = ChartFirst()) > 0) do
063.             {
064.                if (ChartSymbol(id) == def_SymbolReplay)
065.                   ChartClose(id);
066.             }while ((id = ChartNext(id)) > 0);
067.          }
068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick)
092.                {
093.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
094.                   if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, "");
095.                }
096.                m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time);
097.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
098.             }
099.             m_Infos.CountReplay++;
100.          }
101. //+------------------------------------------------------------------+
102.       void AdjustViewDetails(void)
103.          {
104.             MqlRates rate[1];
105. 
106.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
107.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
108.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
109.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
110.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
111.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
112.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
113.             if (rate[0].close > 0)
114.             {
115.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
116.                   m_Infos.tick[0].last = rate[0].close;
117.                else
118.                {
119.                   m_Infos.tick[0].bid = rate[0].close;
120.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
121.                }               
122.                m_Infos.tick[0].time = rate[0].time;
123.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
124.             }else
125.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
126.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
127.          }
128. //+------------------------------------------------------------------+
129.       void AdjustPositionToReplay(void)
130.          {
131.             int nPos, nCount;
132.             
133.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
134.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
135.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
136.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
137.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
138.                CreateBarInReplay(false);
139.          }
140. //+------------------------------------------------------------------+
141.    public   :
142. //+------------------------------------------------------------------+
143.       C_Replay()
144.          :C_ConfigService()
145.          {
146.             Print("************** Market Replay Service **************");
147.             srand(GetTickCount());
148.             SymbolSelect(def_SymbolReplay, false);
149.             CustomSymbolDelete(def_SymbolReplay);
150.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
151.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
152.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
153.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
154.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
155.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
156.             SymbolSelect(def_SymbolReplay, true);
157.             m_Infos.CountReplay = 0;
158.             m_IndControl.Handle = INVALID_HANDLE;
159.             m_IndControl.Mode = C_Controls::ePause;
160.             m_IndControl.Position = 0;
161.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
162.          }
163. //+------------------------------------------------------------------+
164.       ~C_Replay()
165.          {
166.             SweepAndCloseChart();
167.             IndicatorRelease(m_IndControl.Handle);
168.             SymbolSelect(def_SymbolReplay, false);
169.             CustomSymbolDelete(def_SymbolReplay);
170.             Print("Finished replay service...");
171.          }
172. //+------------------------------------------------------------------+
173.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
174.          {
175.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
176.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
177.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
178.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
179.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
180.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
181.             SweepAndCloseChart();
182.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
183.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
184.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
185.             else
186.                Print("Apply template: ", szNameTemplate, ".tpl");
187. 
188.             return true;
189.          }
190. //+------------------------------------------------------------------+
191.       bool InitBaseControl(const ushort wait = 1000)
192.          {
193.             Print("Waiting for Mouse Indicator...");
194.             Sleep(wait);
195.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
196.             if (def_CheckLoopService)
197.             {
198.                AdjustViewDetails();
199.                Print("Waiting for Control Indicator...");
200.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
201.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
202.                UpdateIndicatorControl();
203.             }
204.             
205.             return def_CheckLoopService;
206.          }
207. //+------------------------------------------------------------------+
208.       bool LoopEventOnTime(void)
209.          {         
210.             int iPos;
211. 
212.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
213.             {
214.                UpdateIndicatorControl();
215.                Sleep(200);
216.             }
217.             m_MemoryData = GetInfoTicks();
218.             AdjustPositionToReplay();
219.             EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, "");
220.             iPos = 0;
221.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
222.             {
223.                if (m_IndControl.Mode == C_Controls::ePause) return true;
224.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
225.                CreateBarInReplay(true);
226.                while ((iPos > 200) && (def_CheckLoopService))
227.                {
228.                   Sleep(195);
229.                   iPos -= 200;
230.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
231.                   UpdateIndicatorControl();
232.                }
233.             }
234. 
235.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
236.          }
237. };
238. //+------------------------------------------------------------------+
239. #undef def_SymbolReplay
240. #undef def_CheckLoopService
241. #undef def_MaxSlider
242. //+------------------------------------------------------------------+

Файл исходного кода: C_Replay.mqh

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

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

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

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

Но почему именно так? Разве нет другого способа? Да, есть и другой способ, но он оказался недостаточно подходящим. Не для этого момента, а для того, что нам только предстоит решить. Дело в том, что эти пользовательские события срабатывают только чтобы сообщить нам о закрытии минутного бара. Мы можем сделать это и другим способом, без необходимости запускать пользовательское событие на графике. Для этого нам придется воспользоваться функцией библиотеки iTime, чтобы узнать, когда был создан минутный бар. Обратите внимание на этот момент: НАМ ВАЖНО НЕ ВРЕМЯ БАРА НА ИСПОЛЬЗУЕМОМ ТАЙМФРЕЙМЕ, А ВРЕМЯ ОДНОМИНУТНОГО БАРА. Может быть вы запутались, но взгляните на следующий отрывок.

110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                      
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading   :
123.                   i0 = PeriodSeconds();
124.                   dt = (m_Info.Status == eInReplay ? iTime(NULL, 0, 0) + GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
127.                   break;
128.                case eAuction      :
129.                   m_Info.szInfo = "Auction";
130.                   break;
131.                default            :
132.                   m_Info.szInfo = "ERROR";
133.             }
134.             Draw();
135.          }
136. //+------------------------------------------------------------------+

Фрагмент мода из архива: C_Study.mqh

Изменение находится в строке 124. Использование данной функции для определения времени бара приводит к ненужной трате времени, поскольку полученное значение будет таким же, как и в функции OnCalculate. Однако, если изменить этот же фрагмент, как показано ниже, всё будет совсем по-другому.

110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                      
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading   :
123.                   i0 = PeriodSeconds();
124.                   dt = (m_Info.Status == eInReplay ? iTime(NULL, PERIOD_M1, 0) + GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
127.                   break;
128.                case eAuction      :
129.                   m_Info.szInfo = "Auction";
130.                   break;
131.                default            :
132.                   m_Info.szInfo = "ERROR";
133.             }
134.             Draw();
135.          }
136. //+------------------------------------------------------------------+

Фрагмент мода из архива: C_Study.mqh

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


Заключительные идеи

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

На следующем видео можно увидеть реальную систему в действии.


Запуск демонстрационной версии

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Стратегия торговли каскадами ордеров на основе пересечений EMA для MetaTrader 5 Стратегия торговли каскадами ордеров на основе пересечений EMA для MetaTrader 5
В статье представлен автоматизированный алгоритм на основе пересечений EMA для MetaTrader 5. Подробная информация обо всех аспектах демонстрации советника на языке MQL5 и его тестирования в MetaTrader 5, от анализа характеристик ценового диапазона до управления рисками.
MQL5-советник, интегрированный в Telegram (Часть 2): Отправка сигналов из MQL5 в Telegram MQL5-советник, интегрированный в Telegram (Часть 2): Отправка сигналов из MQL5 в Telegram
В этой статье мы создадим MQL5-советник, интегрированный с Telegram, который отправляет в мессенджер сигналы пересечения скользящих средних. Мы подробно опишем процесс генерации торговых сигналов на основе пересечений скользящих средних, реализуем необходимый код на языке MQL5 и обеспечим бесперебойную работу интеграции. В результате мы получим систему, которая отправляет торговые оповещения в реальном времени непосредственно в групповой чат Telegram.
Возможности Мастера MQL5, которые вам нужно знать (Часть 31): Выбор функции потерь Возможности Мастера MQL5, которые вам нужно знать (Часть 31): Выбор функции потерь
Функция потерь (Loss Function) — это ключевая метрика алгоритмов машинного обучения, которая обеспечивает обратную связь для процесса обучения, количественно определяя, насколько хорошо данный набор параметров работает по сравнению с предполагаемым целевым значением. Мы рассмотрим различные форматы этой функции в пользовательском классе Мастера MQL5.
От начального до среднего уровня: Оператор FOR От начального до среднего уровня: Оператор FOR
В этой статье мы рассмотрим самые основные понятия оператора FOR. Всё, что будет здесь показано, нужно хорошо понять и усвоить. В отличие от других операторов, о которых мы говорили ранее, оператор FOR имеет некоторые особенности, которые быстро делают его очень сложным. Так что не позволяйте подобным материалам накапливаться. Приступайте к изучению и практике как можно скорее.