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

Разработка системы репликации (Часть 50): Все усложняется (II)

MetaTrader 5Примеры |
799 1
Daniel Jose
Daniel Jose

Введение

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

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

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

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

 

Решаем первую проблему

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

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

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

Подобное было показано в статье Разработка системы репликации (Часть 48): Концепции для понимания и осмысления, с чего и началась вся эта работа по модификации системы репликации/моделирования. В той статье я показал, как можно настроить график без использования шаблона, а с помощью сервиса или стандартного графика, чтобы независимо от используемого шаблона на графике присутствовали определенные элементы. Это всегда поддерживается сервисом, работающим на MetaTrader 5, чтобы стандартизировать работу.

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

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

На самом деле, использование функции ChartID() обязательно вернет идентификатор графика, чтобы мы могли разместить объекты на нем. Запомните: идентификатор нужен нам для того, чтобы указать MetaTrader 5, на каком графике будет привязан объект.

Однако функция ChartID не будет работать, когда график открывается через сервис. То есть, когда сервис использует класс C_Replay.mqh и выполняет код из строки 183, будет создан другой идентификатор. Можно увидеть этот код, обратившись к предыдущей статье. В этой же строке 183 будет выполнен вызов ChartOpen для создания графика, содержащего актив, на котором мы будем проводить репликацию/моделирование.

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

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

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

59. //+------------------------------------------------------------------+              
60.             C_Terminal(const long id = 0)
61.                     {
62.                             m_Infos.ID = (id == 0 ? ChartID() : id);
63.                             m_Mem.AccountLock = false;
64.                             CurrentSymbol();
65.                             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
66.                             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
67.                             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
68.                             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
69.                             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
70.                             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
71.                             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
72.                             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
73.                             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
74.                             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
75.                             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
76.                             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
77.                             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
78.                             m_Infos.ChartMode       = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
79.                             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
80.                             ResetLastError();
81.                     }
82. //+------------------------------------------------------------------+
83. 

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

В этом коде можно заметить, что в строке 60 находится конструктор класса C_Terminal. Он получает значение по умолчанию НОЛЬ, поэтому конструктор действует как обычный конструктор. Фактическая проблема возникает в строке 62, где при проверке переданного конструктору значения, мы определяем, какой идентификатор графика использовать. Если данное значение используется по умолчанию, то класс C_Terminal попросит MetaTrader 5 сообщить ID графика, используя значение, возвращаемое ChartID. Это значение будет неверным, когда вызов будет производится от того, что сервис создал график и запустил индикатор, который сделает вызов C_Terminal, чтобы узнать значение идентификатора.

Таким образом, мы можем сообщить конструктору класса C_Terminal значение идентификатора, и если мы это сделаем, то вызов ChartID будет проигнорирован, а переданный конструктору ID будет тем, который используется индикатором.

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


Реализация решения

Возможно, вы были очень удивлены тем, что мы собираемся делать, но это связано с тем, что в предыдущей статье я не объяснил, как реализовать решение, и как оно работает, прежде чем применить его. Чтобы было проще, посмотрите видео 01 о том, как вела себя система. Это было до того, как ID стал передаваться в качестве параметра индикатору управления.


Видео 01

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

Мне пришлось изменить код, чтобы понять, почему он не работает должным образом. Вот почему я просил не обижаться, если я скажу, что невежество - это блаженство, я и сам не понимал, почему код ведет себя таким образом.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. //+------------------------------------------------------------------+
006. class C_Replay : private C_ConfigService
007. {
008.    private :
009.            long    m_IdReplay;
010.            struct st01
011.            {
012.                    MqlRates Rate[1];
013.                    datetime memDT;
014.            }m_MountBar;
015.            struct st02
016.            {
017.                    bool    bInit;
018.                    double  PointsPerTick;
019.                    MqlTick tick[1];
020.            }m_Infos;
021. //+------------------------------------------------------------------+
022.            void AdjustPositionToReplay(const bool bViewBuider)
023.                    {
024.                            u_Interprocess Info;
025.                            MqlRates       Rate[def_BarsDiary];
026.                            int            iPos, nCount;
027.                            
028.                            Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
029.                            if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
030.                            iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
031.                            Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
032.                            CreateBarInReplay(true);
033.                            if (bViewBuider)
034.                            {
035.                                    Info.s_Infos.isWait = true;
036.                                    GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value);
037.                            }else
038.                            {
039.                                    for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++);
040.                                    for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
041.                                    nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
042.                            }
043.                            for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false);
044.                            CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount);
045.                            Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
046.                            Info.s_Infos.isWait = false;
047.                            GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value);
048.                    }
049. //+------------------------------------------------------------------+
050. inline void CreateBarInReplay(const bool bViewTicks)
051.                    {
052. #define def_Rate m_MountBar.Rate[0]
053. 
054.                            bool    bNew;
055.                            double  dSpread;
056.                            int     iRand = rand();
057.                            
058.                            if (BuildBar1Min(m_ReplayCount, def_Rate, bNew))
059.                            {
060.                                    m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount];
061.                                    if ((!m_Ticks.bTickReal) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
062.                                    {                                               
063.                                            dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
064.                                            if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
065.                                            {
066.                                                    m_Infos.tick[0].ask = m_Infos.tick[0].last;
067.                                                    m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
068.                                            }else   if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
069.                                            {
070.                                                    m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
071.                                                    m_Infos.tick[0].bid = m_Infos.tick[0].last;
072.                                            }
073.                                    }
074.                                    if (bViewTicks) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
075.                                    CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
076.                            }
077.                            m_ReplayCount++;
078. #undef def_Rate
079.                    }
080. //+------------------------------------------------------------------+
081.            void ViewInfos(void)
082.                    {
083.                            MqlRates Rate[1];
084.                            
085.                            ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
086.                            ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
087.                            ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
088.                            m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
089.                            m_MountBar.Rate[0].time = 0;
090.                            m_Infos.bInit = true;
091.                            CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate);
092.                            if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
093.                                    for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
094.                            if (Rate[0].close > 0)
095.                            {
096.                                    if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else
097.                                    {
098.                                            m_Infos.tick[0].bid = Rate[0].close;
099.                                            m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick);
100.                                    }                                       
101.                                    m_Infos.tick[0].time = Rate[0].time;
102.                                    m_Infos.tick[0].time_msc = Rate[0].time * 1000;
103.                            }else
104.                                    m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount];
105.                            CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
106.                            ChartRedraw(m_IdReplay);
107.                    }
108. //+------------------------------------------------------------------+
109.            void CreateGlobalVariable(const string szName, const double value)
110.                    {
111.                            GlobalVariableDel(szName);
112.                            GlobalVariableTemp(szName);     
113.                            GlobalVariableSet(szName, value);
114.                    }
115. //+------------------------------------------------------------------+
116.            void AddIndicatorControl(void)
117.                    {
118.                            int handle;
119.                       
120.                            handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay);
121.                            ChartIndicatorAdd(m_IdReplay, 0, handle);
122.                            IndicatorRelease(handle);
123.                    }
124. //+------------------------------------------------------------------+
125.    public  :
126. //+------------------------------------------------------------------+
127.            C_Replay(const string szFileConfig)
128.                    {
129.                            m_ReplayCount = 0;
130.                            m_dtPrevLoading = 0;
131.                            m_Ticks.nTicks = 0;
132.                            m_Infos.bInit = false;
133.                            Print("************** Market Replay Service **************");
134.                            srand(GetTickCount());
135.                            GlobalVariableDel(def_GlobalVariableReplay);
136.                            SymbolSelect(def_SymbolReplay, false);
137.                            CustomSymbolDelete(def_SymbolReplay);
138.                            CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
139.                            CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
140.                            CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
141.                            CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
142.                            CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
143.                            CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
144.                            CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
145.                            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
146.                            m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
147.                            SymbolSelect(def_SymbolReplay, true);
148.                    }
149. //+------------------------------------------------------------------+
150.            ~C_Replay()
151.                    {
152.                            ArrayFree(m_Ticks.Info);
153.                            ArrayFree(m_Ticks.Rate);
154.                            m_IdReplay = ChartFirst();
155.                            do
156.                            {
157.                                    if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
158.                                            ChartClose(m_IdReplay);
159.                            }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
160.                            for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
161.                            CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
162.                            CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
163.                            CustomSymbolDelete(def_SymbolReplay);
164.                            GlobalVariableDel(def_GlobalVariableReplay);
165.                            GlobalVariableDel(def_GlobalVariableServerTime);
166.                            Print("Finished replay service...");
167.                    }
168. //+------------------------------------------------------------------+
169.            bool ViewReplay(ENUM_TIMEFRAMES arg1)
170.                    {
171. #define macroError(A) { Print(A); return false; }
172.                            u_Interprocess info;
173.                            
174.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
175.                                    macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
176.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
177.                                    macroError("Asset configuration is not complete, need to declare the ticket value.");
178.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
179.                                    macroError("Asset configuration not complete, need to declare the minimum volume.");
180.                            if (m_IdReplay == -1) return false;
181.                            if ((m_IdReplay = ChartFirst()) > 0) do
182.                            {
183.                                    if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
184.                                    {
185.                                            ChartClose(m_IdReplay);
186.                                            ChartRedraw();
187.                                    }
188.                            }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
189.                            Print("Waiting for [Market Replay] indicator permission to start replay ...");
190.                            info.ServerTime = ULONG_MAX;
191.                            CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value);
192.                            m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
193.                            AddIndicatorControl();
194.                            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
195.                            info.s_Infos.isHedging = TypeAccountIsHedging();
196.                            info.s_Infos.isSync = true;
197.                            GlobalVariableSet(def_GlobalVariableReplay, info.df_Value);
198. 
199.                            return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
200. #undef macroError
201.                    }
202. //+------------------------------------------------------------------+
203.            bool LoopEventOnTime(const bool bViewBuider)
204.                    {
205.                            u_Interprocess Info;
206.                            int iPos, iTest, iCount;
207.                            
208.                            if (!m_Infos.bInit) ViewInfos();
209.                            iTest = 0;
210.                            while ((iTest == 0) && (!_StopFlag))
211.                            {
212.                                    iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
213.                                    iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value) ? iTest : -1);
214.                                    iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
215.                                    if (iTest == 0) Sleep(100);
216.                            }
217.                            if ((iTest < 0) || (_StopFlag)) return false;
218.                            AdjustPositionToReplay(bViewBuider);
219.                            Info.ServerTime = m_Ticks.Info[m_ReplayCount].time;
220.                            GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value);
221.                            iPos = iCount = 0;
222.                            while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
223.                            {
224.                                    iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
225.                                    CreateBarInReplay(true);
226.                                    while ((iPos > 200) && (!_StopFlag))
227.                                    {
228.                                            if (ChartSymbol(m_IdReplay) == "") return false;
229.                                            GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value);
230.                                            if (!Info.s_Infos.isPlay) return true;
231.                                            Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
232.                                            GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value);
233.                                            Sleep(195);
234.                                            iPos -= 200;
235.                                            iCount++;
236.                                            if (iCount > 4)
237.                                            {
238.                                                    iCount = 0;
239.                                                    GlobalVariableGet(def_GlobalVariableServerTime, Info.df_Value);
240.                                                    if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else
241.                                                    {
242.                                                            Info.ServerTime += 1;
243.                                                            Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time);
244.                                                    };
245.                                                    GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value);
246.                                            }
247.                                    }
248.                            }                               
249.                            return (m_ReplayCount == m_Ticks.nTicks);
250.                    }                               
251. //+------------------------------------------------------------------+
252. };
253. //+------------------------------------------------------------------+
254. #undef macroRemoveSec
255. #undef def_SymbolReplay
256. //+------------------------------------------------------------------+

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

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

Так, в строке 116 я добавил новую процедуру с целью добавления индикатора управления на график. Данная процедура вызывается в строке 193, то есть сразу после того, как график был размещен сервисом и отображен MetaTrader 5. Но давайте вернемся к строке 116. Первое, что мы делаем, - это создаем хэндл в строке 120, который будет ссылаться на индикатор, присутствующий в служебном коде. Помните, что индикатор встраивается в исполняемый файл сервиса как ресурс. После того, как мы указали MetaTrader 5 место, в котором находится индикатор, нам нужно предоставить ему некоторую информацию. Она представляет собой m_IdReplay, то есть идентификатор графика, созданный вызовом ChartOpen.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.50"
07. #property link "https://www.mql5.com/ru/articles/11871"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Controls.mqh>
12. //+------------------------------------------------------------------+
13. C_Terminal *terminal = NULL;
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;   //ID
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     u_Interprocess Info;
21. 
22.     ResetLastError();
23.     if (CheckPointer(control = new C_Controls(terminal = new C_Terminal(user00))) == POINTER_INVALID)
24.             SetUserError(C_Terminal::ERR_PointerInvalid);
25.     if ((!(*terminal).IndicatorCheckPass("Market Replay Control")) || (_LastError != ERR_SUCCESS))
26.     {
27.             Print("Control indicator failed on initialization.");
28.             return INIT_FAILED;
29.     }       
30.     if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0;
31.     EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, "");
32.     (*control).Init(Info.s_Infos.isPlay);
33.         
34.     return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.     static bool bWait = false;
40.     u_Interprocess Info;
41.     
42.     Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
43.     if (!bWait)
44.     {
45.             if (Info.s_Infos.isWait)
46.             {
47.                     EventChartCustom(user00, C_Controls::ev_WaitOn, 1, 0, "");
48.                     bWait = true;
49.             }
50.     }else if (!Info.s_Infos.isWait)
51.     {
52.             EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, "");
53.             bWait = false;
54.     }
55.     
56.     return rates_total;
57. }
58. //+------------------------------------------------------------------+
59. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
60. {
61.     (*control).DispatchMessage(id, lparam, dparam, sparam);
62. }
63. //+------------------------------------------------------------------+
64. void OnDeinit(const int reason)
65. {
66.     switch (reason)
67.     {
68.             case REASON_REMOVE:
69.             case REASON_CHARTCLOSE:
70.                     if (ChartSymbol(user00) != def_SymbolReplay) break;
71.                     GlobalVariableDel(def_GlobalVariableReplay);
72.                     ChartClose(user00);
73.                     break;
74.     }
75.     delete control;
76.     delete terminal;
77. }
78. //+------------------------------------------------------------------+

Исходный код индикатора управления

Обратите внимание, что теперь в строке 16 у нас есть вход, то есть индикатор будет получать параметр. Это одна из причин, по которой пользователю не предоставляется прямой доступ к этому индикатору, по крайней мере, чтобы его нельзя было вручную поместить на график. Данный параметр, который индикатор получит в строке 16, сообщает, каков ID графика, на котором будут размещены объекты. Данное значение надо правильно заполнить. По умолчанию оно будет равно НУЛЮ, т.е. если сервис попытается разместить индикатор, но не сообщит о графике, то будет сгенерирована ошибка. Сообщение об ошибке появляется в строке 27. Это объясняет разницу между тем, что ожидалось, и тем, что представлено в видео 01.

Теперь обратите внимание, где используется значение параметра, введенного в строке 16. Оно используется в нескольких местах, но главное из них находится в строке 23, где мы сообщаем классу C_Terminal, что он не должен использовать сгенерированное значение при поиске ID графика. Класс C_Terminal должен использовать значение, сообщенное сервисом, которым был создан график. Как видите, в других пунктах также используется значение, указанное в строке 16. Однако, после повторного компилирования служебного файла и его запуска в MetaTrader 5, мы получим результат, показанный в видеоролике 02.


Видео 02

Внимательно посмотрите видеоролик 02. Как видите, система ведет себя довольно странно, когда мы пытаемся с ней взаимодействовать. Вопрос - почему это происходит? Мы не внесли никаких изменений, которые могли бы привести к такому поведению.

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

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

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

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

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

01. //+------------------------------------------------------------------+
02. #property service
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property copyright "Daniel Jose"
05. #property version   "1.50"
06. #property description "Replay-Simulator service for MT5 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/ru/articles/11871"
10. //+------------------------------------------------------------------+
11. #define def_IndicatorControl        "Indicators\\Replay\\Market Replay.ex5"
12. #resource "\\" + def_IndicatorControl
13. //+------------------------------------------------------------------+
14. #include <Market Replay\Service Graphics\C_Replay.mqh>
15. //+------------------------------------------------------------------+
16. input string             user00 = "Forex - EURUSD.txt";  //Replay Configuration File.
17. input ENUM_TIMEFRAMES    user01 = PERIOD_M5;             //Initial Graphic Time.
18. input string             user02 = "Default";             //Template File Name
19. //+------------------------------------------------------------------+
20. void OnStart()
21. {
22.     C_Replay  *pReplay;
23. 
24.     pReplay = new C_Replay(user00);
25.     if ((*pReplay).ViewReplay(user01, user02))
26.     {
27.             Print("Permission granted. Replay service can now be used...");
28.             while ((*pReplay).LoopEventOnTime(false));
29.     }
30.     delete pReplay;
31. }
32. //+------------------------------------------------------------------+

Исходный код сервиса

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

175. //+------------------------------------------------------------------+
176.            bool ViewReplay(ENUM_TIMEFRAMES arg1, const string szNameTemplate)
177.                    {
178. #define macroError(A) { Print(A); return false; }
179.                            u_Interprocess info;
180.                            
181.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
182.                                    macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
183.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
184.                                    macroError("Asset configuration is not complete, need to declare the ticket value.");
185.                            if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
186.                                    macroError("Asset configuration not complete, need to declare the minimum volume.");
187.                            if (m_IdReplay == -1) return false;
188.                            if ((m_IdReplay = ChartFirst()) > 0) do
189.                            {
190.                                    if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
191.                                    {
192.                                            ChartClose(m_IdReplay);
193.                                            ChartRedraw();
194.                                    }
195.                            }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
196.                            Print("Waiting for [Market Replay] indicator permission to start replay ...");
197.                            info.ServerTime = ULONG_MAX;
198.                            CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value);
199.                            m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
200.                            if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl"))
201.                                    Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
202.                            AddIndicatorControl();
203.                            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
204.                            info.s_Infos.isHedging = TypeAccountIsHedging();
205.                            info.s_Infos.isSync = true;
206.                            GlobalVariableSet(def_GlobalVariableReplay, info.df_Value);
207. 
208.                            return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
209. #undef macroError
210.                    }
211. //+------------------------------------------------------------------+

Фрагмент исходного кода C_Replay.mqh (Обновление)

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

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

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

Помните, что пользователь не сможет вручную разместить индикатор управления на графике. Хотя есть способы сделать это. Но давайте учтем тот факт, что если пользователь не умеет работать в MetaTrader 5 и MQL5, то он не сможет заменить индикатор управления на графике. Подобная ситуация - это именно тот вид сбоя, за возникновение которого отвечает пользователь, а за его исправление - программист.

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

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_TEMPLATE:
                        Print("Template change ...");
                        break;

Когда шаблон изменится, в MetaTrader 5 произойдет событие DeInit, которое вызовет процедуру, показанную выше. Мы можем проверить условие, которое заставило MetaTrader 5 вызвать событие DeInit, и если это смена шаблона, то на терминале будет выводиться сообщение.

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

Таким образом, код индикатора управления пройдет через еще одно простое обновление, которое можно увидеть в приведенном ниже фрагменте:

64. //+------------------------------------------------------------------+
65. void OnDeinit(const int reason)
66. {
67.     switch (reason)
68.     {
69.             case REASON_TEMPLATE:
70.                     Print("Modified template. Replay/simulation system shutting down.");
71.             case REASON_PARAMETERS:
72.             case REASON_REMOVE:
73.             case REASON_CHARTCLOSE:
74.                     if (ChartSymbol(user00) != def_SymbolReplay) break;
75.                     GlobalVariableDel(def_GlobalVariableReplay);
76.                     ChartClose(user00);
77.                     break;
78.     }
79.     delete control;
80.     delete terminal;
81. }
82. //+------------------------------------------------------------------+

Фрагмент исходного кода индикатора управления (обновление)

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

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

Обратите внимание на строку 71. Раньше такой линии не было. Но она была установлена именно для того, чтобы пользователь не мог изменить ни один из параметров, о которых сервис сообщал индикатору контроля. Если это произойдет, MetaTrader 5 сгенерирует событие DeInit, но аргументом будет изменение параметра. Мы не будем сообщать о сбоях в данный момент, так как сбой произойдет, когда MetaTrader 5 повторно установит индикатор. Но поскольку может случиться так, что какой-нибудь более проницательный пользователь сообщит идентификатор действительного графика, то мы закрываем график репликации/моделирования в строке 76. Таким образом, когда сервис проверит, открыт ли график, он получит ошибку, а для сервиса это означает, что он должен быть завершен. Таким образом, мы исправляем и этот недостаток.


Заключение

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

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

В видеоролике 03, расположенном чуть ниже, можно увидеть, как теперь ведет себя система с обновлениями, описанными в этой статье. Однако, поскольку код все еще нестабилен, мы не будем добавлять какие- либо приложения к данной статье.


Видеоролик 03

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


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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Уроборос
Уроборос | 25 мая 2024 в 21:29
Будет ли регулироваться скорость воспроизведения?
Высокочастотная арбитражная торговая система на Python с использованием MetaTrader 5 Высокочастотная арбитражная торговая система на Python с использованием MetaTrader 5
Создаем легальную в глазах брокеров арбитражную систему, которая создает тысячи синтетических цен на рынке Форекс, анализирует их, и успешно торгует в прибыль.
Нейросети в трейдинге: Контрастный Трансформер паттернов Нейросети в трейдинге: Контрастный Трансформер паттернов
Контрастный Transformer паттернов осуществляет анализ рыночных ситуаций, как на уровне отдельных свечей, так и целых паттернов. Что способствует повышению качества моделирования рыночных тенденций. А применение контрастного обучения для согласования представлений свечей и паттернов ведет к саморегуляции и повышению точности прогнозов.
Разработка системы репликации (Часть 51): Все усложняется (III) Разработка системы репликации (Часть 51): Все усложняется (III)
В данной статье мы разберемся с одним из самых сложных вопросов сферы программирования на MQL5: как правильно получить ID графика, и почему иногда объекты не строятся на графике. Представленные здесь материалы носят исключительно дидактический характер. Ни в коем случае нельзя рассматривать приложение ни с какой иной целью, кроме как для изучения и освоения представленных концепций.
Разработка системы репликации (Часть 49): Все усложняется (I) Разработка системы репликации (Часть 49): Все усложняется (I)
В этой статье мы немного усложним ситуацию. Используя то, что было показано в предыдущих статьях, мы начнем открывать доступ к файлу шаблона, чтобы пользователь мог использовать свой собственный шаблон. Однако я буду вносить изменения постепенно, так как также буду дорабатывать индикатор, чтобы снизить нагрузку на MetaTrader 5.