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

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

MetaTrader 5Примеры |
249 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 57): Анализируем тестовый сервис я подробно объяснил исходный код, необходимый для демонстрации возможного способа взаимодействия между модулями, которые мы будем использовать в нашей системе репликации/симуляции.

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

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

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


Модификация старого сервиса репликации/моделирования

Хотя мы уже давно не делали никаких изменений или улучшений кода репликации/моделирования, некоторые заголовочные файлы, используемые в создании исполняемого файла репликации/моделирования, претерпели кое-какие модификации. Возможно, наиболее заметным является удаление заголовочного файла InterProcess.mqh. Его заменил файл с гораздо более широким назначением — заголовочный файл Defines.mqh.

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

Рис. 01

Рисунок 01. Попытка скомпилировать сервис репликации/моделирования

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_MaxSizeArray    16777216 // 16 Mbytes de posições
08. //+------------------------------------------------------------------+
09. class C_Simulation
10. {
11.    private   :
12. //+------------------------------------------------------------------+
13.       int       m_NDigits;
14.       bool       m_IsPriceBID;

Фрагмент исходного кода файла C_Simulation.mqh

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_BarsDiary   1440
08. //+------------------------------------------------------------------+
09. class C_FileBars
10. {
11.    private   :
12.       int      m_file;

Фрагмент исходного кода файла C_FilesBars.mqh

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

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

А чтобы не путаться, разделим объяснение на темы. И первое, что мы сделаем, это исправим ошибку, которая хоть и не очень серьезна, но нарушает одну из предпосылок объектно-ориентированного программирования – инкапсуляцию.


Пересмотр инкапсуляции кода

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

Но с этого момента я перестану это практиковать. Я имею в виду нарушение инкапсуляции, присутствующее в коде класса C_ConfigService.

Если вы посмотрите на код, присутствующий в заголовочном файле этого класса, C_ConfigService.mqh, вы заметите наличие объявленной защищенной секции, а внутри этой секции есть переменные. Именно наличие этих переменных здесь нарушает инкапсуляцию. Хотя эти переменные используются только в классах C_ConfigService и C_Replay, нецелесообразно разрешать их видимость за пределами класса C_ConfigService так, как это сделано сейчас. В текущем состоянии эти переменные видимы. Если вы посмотрите на код класса C_Replay, то увидите, что эти переменные модифицируются этим классом, а это совершенно неуместно. В C++ существуют способы сделать частные переменные классов видимыми и изменяемыми вне базового класса. Однако, такая практика часто перегружает код, делая его трудным для поддержки, не говоря уже о том, что его сложно улучшить.

Поскольку MQL5 является производным от C++ и старается избегать потенциально опасных практик, которые могут быть реализованы в C++, я считаю более чем уместным показать и правильно использовать три основных предпосылки объектно-ориентированного программирования.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Support\C_FileBars.mqh"
05. #include "Support\C_FileTicks.mqh"
06. #include "Support\C_Array.mqh"
07. //+------------------------------------------------------------------+
08. class C_ConfigService : protected C_FileTicks
09. {
10.    protected:
11.         datetime m_dtPrevLoading;
12.         int      m_ReplayCount,
13.                  m_ModelLoading;
14. //+------------------------------------------------------------------+
15. inline void FirstBarNULL(void)
16.          {
17.             MqlRates rate[1];
18.             int c0 = 0;
19.             
20.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
21.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
22.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
23.             rate[0].tick_volume = 0;
24.             rate[0].real_volume = 0;
25.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
26.             CustomRatesUpdate(def_SymbolReplay, rate);
27.             m_ReplayCount = 0;
28.          }
29. //+------------------------------------------------------------------+
30.    private   :
31.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
32.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
33.       struct st001
34.       {
35.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
36.          int      Line;
37.       }m_GlPrivate;
38.       string    m_szPath;
39.       bool      m_AccountHedging;
40.       datetime  m_dtPrevLoading;
41.       int       m_ReplayCount,
42.                 m_ModelLoading;
43. //+------------------------------------------------------------------+
44. inline void FirstBarNULL(void)
45.          {
46.             MqlRates rate[1];
47.             int c0 = 0;
48.             
49.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
50.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
51.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
52.             rate[0].tick_volume = 0;
53.             rate[0].real_volume = 0;
54.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
55.             CustomRatesUpdate(def_SymbolReplay, rate);
56.             m_ReplayCount = 0;
57.          }
58. //+------------------------------------------------------------------+
59. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)

Фрагмент исходного кода файла C_ConfigService.mqh

Обратите внимание, что содержимое строк 11 – 13 было перенесено в строки 40 и 42. То есть теперь получить доступ к этим переменным за пределами тела класса C_ConfigService будет невозможно. Помимо этого, было сделано еще одно изменение. Это изменение можно было бы проигнорировать, но поскольку некоторые вещи не будут использоваться вне класса, я решил сделать процедуру FirstBarNULL приватной. Таким образом, содержимое, находившееся между строками 15 и 28, было перенесено и теперь находится между строками 44 и 57.

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

Отлично. Теперь, после внесения этих изменений, нам придется радикально изменить код, присутствующий в файле C_Replay.mqh. Но давайте продолжим отделять одно от другого и рассмотрим это в следующей теме.


Перезапуск кодирования класса C_Replay

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

Ниже приведен весь код, принадлежащий классу C_Replay для этой статьи.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. //+------------------------------------------------------------------+
006. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
007. #resource "\\" + def_IndicatorControl
008. //+------------------------------------------------------------------+
009. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""))
010. //+------------------------------------------------------------------+
011. #define def_ShortNameIndControl "Market Replay Control"
012. //+------------------------------------------------------------------+
013. class C_Replay : public C_ConfigService
014. {
015.    private   :
016.       long      m_IdReplay;
017.       struct st00
018.       {
019.          ushort Position;
020.          short  Mode;
021.       }m_IndControl;
022. //+------------------------------------------------------------------+
023. inline bool MsgError(string sz0) { Print(sz0); return false; }
024. //+------------------------------------------------------------------+
025. inline void UpdateIndicatorControl(void)
026.          {
027.             uCast_Double info;
028.             int handle;
029.             double Buff[];
030.             
031.             if ((handle = ChartIndicatorGet(m_IdReplay, 0, def_ShortNameIndControl)) == INVALID_HANDLE) return;
032.             info.dValue = 0;
033.             if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
034.                info.dValue = Buff[0];
035.             IndicatorRelease(handle);
036.             if ((short)(info._16b[0]) != SHORT_MIN)
037.                m_IndControl.Mode = (short)info._16b[1];
038.             if (info._16b[0] != m_IndControl.Position)
039.             {
040.                if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX))
041.                   m_IndControl.Position = info._16b[0];
042.                info._16b[0] = m_IndControl.Position;
043.                info._16b[1] = (ushort)m_IndControl.Mode;
044.                EventChartCustom(m_IdReplay, evCtrlReplayInit, 0, info.dValue, "");
045.             }
046.          }
047. //+------------------------------------------------------------------+
048.       void SweepAndCloseChart(void)
049.          {
050.             long id;
051.             
052.             if ((id = ChartFirst()) > 0) do
053.             {
054.                if (ChartSymbol(id) == def_SymbolReplay)
055.                   ChartClose(id);
056.             }while ((id = ChartNext(id)) > 0);
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+
061.       C_Replay()
062.          :C_ConfigService()
063.          {
064.             Print("************** Market Replay Service **************");
065.             srand(GetTickCount());
066.             SymbolSelect(def_SymbolReplay, false);
067.             CustomSymbolDelete(def_SymbolReplay);
068.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
069.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
070.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
071.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
072.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
073.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
074.             SymbolSelect(def_SymbolReplay, true);
075.          }
076. //+------------------------------------------------------------------+
077.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
078.          {
079.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
080.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
081.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
082.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
083.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
084.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
085.             SweepAndCloseChart();
086.             m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
087.             if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl"))
088.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
089.             else
090.                Print("Apply template: ", szNameTemplate, ".tpl");
091. 
092.             return true;
093.          }
094. //+------------------------------------------------------------------+
095.       bool InitBaseControl(const ushort wait = 1000)
096.          {
097.             int handle;
098.             
099.             Print("Waiting for Mouse Indicator...");
100.             Sleep(wait);
101.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
102.             if (def_CheckLoopService)
103.             {
104.                Print("Waiting for Control Indicator...");
105.                if ((handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay)) == INVALID_HANDLE) return false;
106.                ChartIndicatorAdd(m_IdReplay, 0, handle);
107.                IndicatorRelease(handle);
108.                m_IndControl.Position = 0;
109.                m_IndControl.Mode = SHORT_MIN;
110.                UpdateIndicatorControl();
111.             }
112.             
113.             return def_CheckLoopService;
114.          }
115. //+------------------------------------------------------------------+
116.       bool LoopEventOnTime(void)
117.          {         
118.             
119.             while (def_CheckLoopService)
120.             {
121.                UpdateIndicatorControl();
122.                Sleep(250);
123.             }
124.             
125.             return false;
126.          }
127. //+------------------------------------------------------------------+
128.       ~C_Replay()
129.          {
130.             SweepAndCloseChart();
131.             SymbolSelect(def_SymbolReplay, false);
132.             CustomSymbolDelete(def_SymbolReplay);
133.             Print("Finished replay service...");
134.          }
135. //+------------------------------------------------------------------+

Исходный код файла C_Replay.mqh

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

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

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

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

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

А вот в строке 23 у нас есть небольшая функция, единственная цель которой — вывести сообщение на терминал и вернуть ложное значение. Но почему мы возвращаем здесь ложное значение? Причина в том, что если бы мы не вернули ложное значение, нам потребовалась бы вторая строка для вывода ошибки на терминал. Чтобы было понятнее, посмотрите на строку 79, где мы проверяем определенное условие. Если это условие указывает на ошибку, в строке 80 мы должны отобразить сообщение об ошибке, а затем использовать вызов return, чтобы сообщить вызывающему объекту, что она произошла. Такой подход только увеличил бы объем работы. Однако, используя функцию, объявленную в строке 23, и возвращая в ней ложное значение, мы можем напечатать желаемое сообщение и в то же время вернуть указание на сбой. Поэтому все, что будет необходимо, — это использовать что-то похожее на то, что можно увидеть в строке 80. Мы комбинируем вещи таким образом, чтобы сократить себе работу по программированию.

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

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

Поскольку обработчик нам больше не нужен, мы используем строку 35, чтобы избавиться от него. Таким образом, мы вступаем в фазу тестирования и корректировки информации, которая будет передаваться и использоваться. В строке 36 мы проверяем, есть ли у нас какие-либо валидные данные в индикаторе управления. Если индикатор уже содержит такие данные, мы сохраняем сведения о том, находимся ли мы на паузе или в режиме выполнения, иначе говоря, находимся мы или нет в режиме «play» для репликации/моделирования. Это сохраняется в строке 37. Обращаю ваше внимание на то, что это нужно сделать прежде всего. Если вы сделаете это позже, то неизбежно получите доступ к уже измененным данным, что нарушит сохранность информации. Здесь цель заключается в том, чтобы сервис обеспечил последнюю форму индикатора управления — то, что раньше делалось с помощью глобальной переменной терминала.

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

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

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

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

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

Первая функция называется OpenChartReplay, которая начинается со строки 77 и заканчивается в строке 93. Она проверяет целостность информации, собранной системой загрузки. Это необходимо для того, чтобы репликация или моделирование могли быть фактически выполнены. Однако именно в этой функции мы обнаруживаем нечто достаточно сложное, что вместе с функцией InitBaseControl, о которой мы поговорим позже, позволяет нам использовать шаблон.

Вопрос использования шаблона имеет для нас огромное значение. Необходимо, чтобы он был правильно использован и запущен соответствующим образом. Но сделать это не так просто, как многим, включая меня, могло показаться в начале. В строке 87 мы пытаемся добавить шаблон на график, после того, как предварительно открыли его в строке 86. Используемый шаблон указывается, как один из аргументов функции. В любом случае, шаблон будет помещен на график, будь то шаблон, указанный пользователем, или стандартный шаблон MetaTrader 5. Но здесь есть деталь, о которой редко упоминается: шаблон размещается не сразу. Функция ChartApplyTemplate не применяет шаблон немедленно. Эта функция является асинхронной, то есть может быть выполнена через несколько миллисекунд после вызова. И это проблема для нас.

Чтобы понять масштаб проблемы, мы возьмем небольшую паузу в классе C_Replay и рассмотрим код сервиса, который представлен ниже.

01. //+------------------------------------------------------------------+
02. #property service
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property copyright "Daniel Jose"
05. #property version   "1.58"
06. #property description "Replay-Simulator service for MetaTrade 5 platform."
07. #property description "This is dependent on the Market Replay indicator."
08. #property description "For more details on this version see the article."
09. #property link "https://www.mql5.com/pt/articles/"
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Replay.mqh>
12. //+------------------------------------------------------------------+
13. input string            user00 = "Mini Dolar.txt";   //Replay Configuration File.
14. input ENUM_TIMEFRAMES   user01 = PERIOD_M5;          //Initial Graphic Time.
15. input string            user02 = "Default";          //Template File Name
16. //+------------------------------------------------------------------+
17. C_Replay *pReplay;
18. //+------------------------------------------------------------------+
19. void OnStart()
20. {
21.    pReplay = new C_Replay();
22. 
23.    UsingReplay();   
24.    
25.    delete pReplay;
26. }
27. //+------------------------------------------------------------------+
28. void UsingReplay(void)
29. {
30.    if (!(*pReplay).SetSymbolReplay(user00)) return;
31.    if (!(*pReplay).OpenChartReplay(user01, user02)) return;
32.    if (!(*pReplay).InitBaseControl()) return;
33.    Print("Permission granted. Replay service can now be used...");
34.    while ((*pReplay).LoopEventOnTime());
35. }
36. //+------------------------------------------------------------------+

Исходный код сервиса репликации/моделирования

Обратите внимание, что мы выполняем операции в определенной последовательности. Это происходит между строками 30 и 34. Отмечу, что после инициализации значений через конструктор в строке 21, мы переходим к строке 30, где проверяется, все ли в порядке с загрузкой. Затем, в строке 31, мы пытаемся открыть график, и только после этого, в строке 32, загружаем элементы, необходимые для управления сервисом. Если все пройдет хорошо, в строке 33 мы выведем сообщение на терминал, а в строке 34 войдем в цикл выполнения.

Глядя на этот код, мы не можем не заметить, что между открытием графика (строка 31) и добавлением элементов управления (строка 32) могут происходить странные вещи. Это связано с использованием шаблона, загружаемого в классе C_Replay. Так что давайте вернемся к классу, чтобы понять, в чем заключается настоящая проблема использования шаблона.

После того, как мы попросили MetaTrader 5 использовать шаблон в строке 87 класса C_Replay, код может выполняться намного быстрее, чем это необходимо. По этой причине в строке 99 мы информируем пользователя, что сервис ожидает наличия индикатора мыши. Если этот индикатор есть в шаблоне, он будет загружен автоматически; в противном случае, пользователю придется добавить его вручную.

Здесь кроется проблема, и эта проблема связана с тем, что функция добавления шаблона выполняется асинхронно. Чтобы минимизировать ущерб, мы используем строку 100, где останавливаем сервис на некоторое время, чтобы он ничего не выполнял, ожидая стабилизации графика и эффективного применения функции размещения шаблона. И лишь только после этого в строке 101 проверяем, присутствует индикатор мыши или нет. Этот цикл будет выполняться до тех пор, пока индикатор не появится на графике, или пока пользователь не закроет график.

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


Заключение

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

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

Демонстрационное видео

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
От начального до среднего уровня: Переменные (I) От начального до среднего уровня: Переменные (I)
Многим начинающим программистам тяжело понять почему их код работает не так, как они ожидают. Существует множество моментов, которые делают код действительно функциональным. Это не просто набор разных функций и операций, который заставляет код работать. Сегодня я предлагаю вам научиться правильно создавать настоящий код, а не копировать и вставлять его фрагменты. Представленные здесь материалы имеют исключительно дидактический характер. Ни в коем случае нельзя рассматривать приложение ни с какой иной целью, кроме как для изучения и освоения представленных концепций.
Использование JSON Data API в MQL-проектах Использование JSON Data API в MQL-проектах
Представьте, что вы можете использовать данные, которых нет в MetaTrader. Обычно вы получаете информацию только от индикаторов, основанных на анализе цен и техническом анализе. Теперь представьте, что у вас есть доступ к данным, которые выведут ваши торговые возможности на новый уровень. Вы можете значительно увеличить мощность платформы MetaTrader, если объедините её возможности с результатами работы других программ, методов макроанализа и ультрасовременных инструментов через API. В этой статье мы расскажем, как использовать API, и представим полезные и ценные API-сервисы.
Нейросети в трейдинге: Многоагентная система с концептуальным подтверждением (Окончание) Нейросети в трейдинге: Многоагентная система с концептуальным подтверждением (Окончание)
Продолжаем реализацию подходов, предложенных авторами фреймворка FinCon. FinCon является многоагентной системой, основанной на больших языковых моделях (LLM). Сегодня мы реализуем необходимые модули и проведем комплексное тестирование модели на реальных исторических данных.
Нейросети в трейдинге: Многоагентная система с концептуальным подтверждением (FinCon) Нейросети в трейдинге: Многоагентная система с концептуальным подтверждением (FinCon)
Предлагаем познакомиться с фреймворком FinCon, который представляет собой многоагентную систему на основе больших языковых моделей (LLM). Фреймворк использует концептуальное вербальное подкрепление для улучшения принятия решений и управления рисками, что позволяет эффективно выполнять разнообразные финансовые задачи.