English Español Português
preview
Разработка системы репликации (Часть 70): Настройка времени (III)

Разработка системы репликации (Часть 70): Настройка времени (III)

MetaTrader 5Примеры | 20 марта 2025, 09:59
280 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье, "Разработка системы репликации (Часть 69): Настройка времени (II), мы показали, как отображать оставшееся время бара, даже когда символ находится в моменте низкой ликвидности. Под низкой ликвидностью понимается отсутствие сделок, совершаемых в любой момент времени. Такие ситуации могут иметь самые разные причины. Впрочем, объяснять конкретные причины данного явления здесь не имеет смысла. Главное - научиться правильно решать такие ситуации.

Несмотря на это, нам всё еще предстоит решить один вопрос. Это довольно утомительная и сложная задача, ведь речь идет не о программировании, необходимом для ее решения, а о том, как определить, когда она возникает, и как с ней справиться. Эта проблема известна как АУКЦИОН.

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

Хорошо, это самая простая часть. А самая сложная - определить, как система репликации/моделирования должна объявить, что пользовательский символ вошел в процесс аукциона. И это правда сложно.

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

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


Есть что-то интересное

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+
010.    protected:
011.       enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
012. //+------------------------------------------------------------------+
013.       struct st_Terminal
014.       {
015.          ENUM_SYMBOL_CHART_MODE   ChartMode;
016.          ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
017.          long           ID;
018.          string         szSymbol;
019.          int            Width,
020.                         Height,
021.                         nDigits,
022.                         SubWin,
023.                         HeightBar;
024.          double         PointPerTick,
025.                         ValuePerPoint,
026.                         VolumeMinimal,
027.                         AdjustToTrade;
028.       };
029. //+------------------------------------------------------------------+
030.    private   :
031.       st_Terminal m_Infos;
032.       struct mem
033.       {
034.          long  Show_Descr,
035.                Show_Date;
036.          bool  AccountLock;
037.       }m_Mem;
038. //+------------------------------------------------------------------+
039.       void CurrentSymbol(void)
040.          {
041.             MqlDateTime mdt1;
042.             string sz0, sz1;
043.             datetime dt = macroGetDate(TimeCurrent(mdt1));
044.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
045.       
046.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
047.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
048.             switch (eTS)
049.             {
050.                case DOL   :
051.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
052.                case IND   :
053.                case WIN   : sz1 = "GJMQVZ";       break;
054.                default    : return;
055.             }
056.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
057.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
058.          }
059. //+------------------------------------------------------------------+
060. inline void ChartChange(void)
061.          {
062.             int x, y, t;
063.             m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
064.             m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
065.             ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t);
066.             ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y);
067.             m_Infos.HeightBar = (int)((t - y) / 100);
068.          }
069. //+------------------------------------------------------------------+
070.    public   :
071. //+------------------------------------------------------------------+      
072.       C_Terminal(const long id = 0, const uchar sub = 0)
073.          {
074.             m_Infos.ID = (id == 0 ? ChartID() : id);
075.             m_Mem.AccountLock = false;
076.             m_Infos.SubWin = (int) sub;
077.             CurrentSymbol();
078.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
079.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
080.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
081.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
082.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
083.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
084.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
085.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
086.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
087.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
088.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
089.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
090.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
091.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
092.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
093.             ChartChange();
094.          }
095. //+------------------------------------------------------------------+
096.       ~C_Terminal()
097.          {
098.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
099.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
100.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
101.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
102.          }
103. //+------------------------------------------------------------------+
104. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
105.          {
106.             if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
107.             m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
108.          }
109. //+------------------------------------------------------------------+
110. inline const st_Terminal GetInfoTerminal(void) const
111.          {
112.             return m_Infos;
113.          }
114. //+------------------------------------------------------------------+
115. const double AdjustPrice(const double arg) const
116.          {
117.             return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
118.          }
119. //+------------------------------------------------------------------+
120. inline datetime AdjustTime(const datetime arg)
121.          {
122.             int nSeconds= PeriodSeconds();
123.             datetime   dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
124.             
125.             return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
126.          }
127. //+------------------------------------------------------------------+
128. inline double FinanceToPoints(const double Finance, const uint Leverage)
129.          {
130.             double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
131.             
132.             return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
133.          };
134. //+------------------------------------------------------------------+
135.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
136.          {
137.             static string st_str = "";
138.             
139.             switch (id)
140.             {
141.                case CHARTEVENT_CHART_CHANGE:
142.                   m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
143.                   m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
144.                   ChartChange();
145.                   break;
146.                case CHARTEVENT_OBJECT_CLICK:
147.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
148.                   if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
149.                      ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
150.                   break;
151.                case CHARTEVENT_OBJECT_CREATE:
152.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
153.                   st_str = sparam;
154.                   break;
155.             }
156.          }
157. //+------------------------------------------------------------------+
158. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const
159.          {
160.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
161.              ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
162.              ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
163.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
164.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
165.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
166.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
167.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
168.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
169.          }
170. //+------------------------------------------------------------------+
171.       bool IndicatorCheckPass(const string szShortName)
172.          {
173.             string szTmp = szShortName + "_TMP";
174.             
175.             IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
176.             m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin);
177.             if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
178.             {
179.                ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
180.                Print("Only one instance is allowed...");
181.                SetUserError(C_Terminal::ERR_NoMoreInstance);
182.                
183.                return false;
184.             }
185.             IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
186.    
187.             return true;
188.          }
189. //+------------------------------------------------------------------+
190. };

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

Код, который показан выше, должен быть помещен на место старого файла C_Terminal.mqh. Приведенный ниже код должен заменить старый файл C_Mouse.mqh.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014.       struct st_Mouse
015.       {
016.          struct st00
017.          {
018.             short    X_Adjusted,
019.                      Y_Adjusted,
020.                      X_Graphics,
021.                      Y_Graphics;
022.             double   Price;
023.             datetime dt;
024.          }Position;
025.          uchar       ButtonStatus;
026.          bool        ExecStudy;
027.       };
028. //+------------------------------------------------------------------+
029.    protected:
030. //+------------------------------------------------------------------+
031.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
032.          {
033.             if (!m_OK) return;
034.             CreateObjectGraphics(szName, OBJ_BUTTON);
035.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
036.              ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
037.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
038.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
039.             ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
040.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
041.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
042.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
044.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
045.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
046.          }
047. //+------------------------------------------------------------------+
048.    private   :
049.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
050.       struct st01
051.       {
052.          st_Mouse Data;
053.          color    corLineH,
054.                   corTrendP,
055.                   corTrendN;
056.          eStudy   Study;
057.       }m_Info;
058.       struct st_Mem
059.       {
060.          bool     CrossHair,
061.                   IsFull;
062.          datetime dt;
063.          string   szShortName,
064.                   szLineH,
065.                   szLineV,
066.                   szLineT,
067.                   szBtnS;
068.       }m_Mem;
069.       bool m_OK;
070. //+------------------------------------------------------------------+
071.       void GetDimensionText(const string szArg, int &w, int &h)
072.          {
073.             TextSetFont("Lucida Console", -100, FW_NORMAL);
074.             TextGetSize(szArg, w, h);
075.             h += 5;
076.             w += 5;
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateStudy(void)
080.          {
081.             if (m_Mem.IsFull)
082.             {
083.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
084.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
085.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2);
086.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
087.             }
088.             m_Info.Study = eStudyCreate;
089.          }
090. //+------------------------------------------------------------------+
091.       void ExecuteStudy(const double memPrice)
092.          {
093.             double v1 = GetInfoMouse().Position.Price - memPrice;
094.             int w, h;
095.             
096.             if (!CheckClick(eClickLeft))
097.             {
098.                m_Info.Study = eStudyNull;
099.                ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
100.                if (m_Mem.IsFull)   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
101.             }else if (m_Mem.IsFull)
102.             {
103.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
104.                   MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
105.                GetDimensionText(sz1, w, h);
106.                ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
107.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
108.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w);
109.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h);
110.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
111.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
112.                ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
113.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
114.             }
115.             m_Info.Data.ButtonStatus = eKeyNull;
116.          }
117. //+------------------------------------------------------------------+
118. inline void DecodeAlls(int xi, int yi)
119.          {
120.             int w = 0;
121. 
122.             xi = (xi > 0 ? xi : 0);
123.             yi = (yi > 0 ? yi : 0);
124.             ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
125.             m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
126.             m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price);
127.             ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi);
128.             yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin);
129.             m_Info.Data.Position.X_Adjusted = (short) xi;
130.             m_Info.Data.Position.Y_Adjusted = (short) yi;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Mouse(const long id, const string szShortName)
136.          :C_Terminal(id),
137.          m_OK(false)
138.          {
139.             m_Mem.szShortName = szShortName;
140.          }
141. //+------------------------------------------------------------------+
142.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
143.          :C_Terminal(id)
144.          {
145.             if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return;
146.             m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
148.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
149.             ZeroMemory(m_Info);
150.             m_Info.corLineH  = corH;
151.             m_Info.corTrendP = corP;
152.             m_Info.corTrendN = corN;
153.             m_Info.Study = eStudyNull;
154.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
155.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
156.             ChartRedraw(GetInfoTerminal().ID);
157.          }
158. //+------------------------------------------------------------------+
159.       ~C_Mouse()
160.          {
161.             if (!m_OK) return;
162.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
163.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1);
164.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
166.          }
167. //+------------------------------------------------------------------+
168. inline bool CheckClick(const eBtnMouse value) 
169.          {
170.             return (GetInfoMouse().ButtonStatus & value) == value;
171.          }
172. //+------------------------------------------------------------------+
173. inline const st_Mouse GetInfoMouse(void)
174.          {
175.             if (!m_OK)
176.             {
177.                double Buff[];
178.                uCast_Double loc;
179.                int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
180. 
181.                ZeroMemory(m_Info.Data);
182.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
183.                {
184.                   loc.dValue = Buff[0];
185.                   m_Info.Data.ButtonStatus = loc._8b[0];
186.                   DecodeAlls((int)loc._16b[1], (int)loc._16b[2]);
187.                }
188.                IndicatorRelease(handle);
189.             }
190. 
191.             return m_Info.Data;
192.          }
193. //+------------------------------------------------------------------+
194. inline void SetBuffer(const int rates_total, double &Buff[])
195.          {
196.             uCast_Double info;
197.             
198.             info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0);
199.             info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics;
200.             info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics;
201.             Buff[rates_total - 1] = info.dValue;
202.          }
203. //+------------------------------------------------------------------+
204.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
205.          {
206.             int w = 0;
207.             static double memPrice = 0;
208.       
209.             if (m_OK)      
210.             {
211.                C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
212.                switch (id)
213.                {
214.                   case (CHARTEVENT_CUSTOM + evHideMouse):
215.                      if (m_Mem.IsFull)   ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
216.                      break;
217.                   case (CHARTEVENT_CUSTOM + evShowMouse):
218.                      if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
219.                      break;
220.                   case CHARTEVENT_MOUSE_MOVE:
221.                      DecodeAlls((int)lparam, (int)dparam);
222.                      if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
223.                      if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
224.                      m_Info.Data.ButtonStatus = (uchar) sparam;
225.                      if (CheckClick(eClickMiddle))
226.                         if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
227.                      if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
228.                      {
229.                         ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
230.                         if (m_Mem.IsFull)   ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
231.                         m_Info.Study = eStudyExecute;
232.                      }
233.                      if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
234.                      m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
235.                      break;
236.                   case CHARTEVENT_OBJECT_DELETE:
237.                      if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
238.                         CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
239.                      break;
240.                }
241.             }
242.          }
243. //+------------------------------------------------------------------+
244. };
245. //+------------------------------------------------------------------+
246. #undef macro_NameObjectStudy
247. //+------------------------------------------------------------------+

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

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


Появление нового указателя мыши

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

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

Как можно было увидеть в предыдущей теме, изменили как исходный код заголовочного файла C_Terminal.mqh, так и исходный код файла C_Mouse.mqh. Хотя мы не стали подробно рассказывать о внесенных изменениях, важно знать, что они были. Однако, поскольку они достаточно просты, я не считаю нужным подробно останавливаться на них. Так же, как были внесены изменения в эти файлы, были внесены изменения и в заголовочный файл 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.       }m_Info;
025. //+------------------------------------------------------------------+
026.       void Draw(void)
027.          {
028.             double v1;
029.             
030.             if (m_Info.bvT)
031.             {
032.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
033.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
034.             }
035.             if (m_Info.bvD)
036.             {
037.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
038.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
039.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
040.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
041.             }
042.             if (m_Info.bvP)
043.             {
044.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
045.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
046.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
047.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
048.             }
049.          }
050. //+------------------------------------------------------------------+
051. inline void CreateObjInfo(EnumEvents arg)
052.          {
053.             switch (arg)
054.             {
055.                case evShowBarTime:
056.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
057.                   m_Info.bvT = true;
058.                   break;
059.                case evShowDailyVar:
060.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
061.                   m_Info.bvD = true;
062.                   break;
063.                case evShowPriceVar:
064.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
065.                   m_Info.bvP = true;
066.                   break;
067.             }
068.          }
069. //+------------------------------------------------------------------+
070. inline void RemoveObjInfo(EnumEvents arg)
071.          {
072.             string sz;
073.             
074.             switch (arg)
075.             {
076.                case evHideBarTime:
077.                   sz = m_Info.szBtn1;
078.                   m_Info.bvT = false;
079.                   break;
080.                case evHideDailyVar:
081.                   sz = m_Info.szBtn2;
082.                   m_Info.bvD   = false;
083.                   break;
084.                case evHidePriceVar:
085.                   sz = m_Info.szBtn3;
086.                   m_Info.bvP = false;
087.                   break;
088.             }
089.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
090.             ObjectDelete(GetInfoTerminal().ID, sz);
091.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
092.          }
093. //+------------------------------------------------------------------+
094.    public   :
095. //+------------------------------------------------------------------+
096.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
097.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
098.          {
099.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
100.             ZeroMemory(m_Info);
101.             m_Info.corP = corP;
102.             m_Info.corN = corN;
103.             CreateObjInfo(evShowBarTime);
104.             CreateObjInfo(evShowDailyVar);
105.             CreateObjInfo(evShowPriceVar);
106.             ResetLastError();
107.          }
108. //+------------------------------------------------------------------+
109.       void Update(const eStatusMarket arg)
110.          {
111.             int i0;
112.             datetime dt;
113.                
114.             if (m_Info.Rate.close == 0)
115.                m_Info.Rate.close = iClose(NULL, PERIOD_D1, ((_Symbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(NULL, PERIOD_D1, 0))) ? 0 : 1));
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 ? (datetime) GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   if (dt > 0) 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_MOUSE_MOVE:
161.                   Draw();
162.                   break;
163.             }
164.             ChartRedraw(GetInfoTerminal().ID);
165.          }
166. //+------------------------------------------------------------------+
167. };
168. //+------------------------------------------------------------------+
169. #undef def_ExpansionPrefix
170. #undef def_MousePrefixName
171. //+------------------------------------------------------------------+

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

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

Прошу заметить, что конструктор класса C_Study прошел через процесс очистки в своем коде. В строке 99 были внесены изменения в тестовое значение, чтобы ошибки, не связанные с кодом, не приводили к преждевременному завершению теста. Но есть и другой важный момент. Обратите внимание, что мы больше не инициализируем Rate.close в конструкторе, теперь данная инициализация выполняется в другом месте кода. В строке 114 мы проверяем, равно ли значение нулю. Если это так, в строке 115 мы вводим значение закрытия, как и раньше.

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

Теперь есть одна деталь: проверка в строке 114 всегда будет истинной, когда выполняется строка 100, потому что строка 100 сбрасывает все значения структуры m_Info в ноль. Хотя полный код доступен, именно данные моменты, на мой взгляд, заслуживают пояснений. Остальное осталось без изменений. Хорошо. Теперь давайте рассмотрим изменения, которые внесены в код указателя мыши. Полный код указателя можно увидеть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.70"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/ru/articles/12326"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
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.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
31.    if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED;
32.    MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.    OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.    m_Status = C_Study::eCloseMarket;
35.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
36.    ArrayInitialize(m_Buff, EMPTY_VALUE);
37.    
38.    return INIT_SUCCEEDED;
39. }
40. //+------------------------------------------------------------------+
41. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 
42.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
43.                 const long& volume[], const int& spread[]) 
44. {
45.    GL_PriceClose = close[rates_total - 1];
46.    if (_Symbol == def_SymbolReplay)
47.       GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService);
48.    m_posBuff = rates_total;
49.    (*Study).Update(m_Status);   
50.    
51.    return rates_total;
52. }
53. //+------------------------------------------------------------------+
54. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
55. {
56.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
57.    (*Study).SetBuffer(m_posBuff, m_Buff);
58.    
59.    ChartRedraw((*Study).GetInfoTerminal().ID);
60. }
61. //+------------------------------------------------------------------+
62. void OnBookEvent(const string &symbol)
63. {
64.    MqlBookInfo book[];
65.    C_Study::eStatusMarket loc = m_Status;
66.    
67.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
68.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
69.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading));
70.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
71.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
72.    if (loc != m_Status) (*Study).Update(m_Status);
73. }
74. //+------------------------------------------------------------------+
75. void OnDeinit(const int reason)
76. {
77.    MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
78. 
79.    delete Study;
80. }
81. //+------------------------------------------------------------------+

Исходный код указателя мыши

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

Во-первых, у нас есть функция OnInit. Если внимательно посмотреть, то заметно, что она отличается от предыдущей версии. Следует помнить о некоторых важных моментах. Первый заключается в том, что в строке 31 мы проверяем, не была ли переменная error установлена каким-либо вызовом, который мы сделали. Раньше любая ошибка приводила к сообщению со стороны инициализации INIT_FAILED. Но почему я это изменил? Я так решил, потому что иногда при инициализации индикатор выдавал ошибку, которая не имела никакого смысла. Поскольку конструктор класса не позволяет нам возвращать какое-либо значение, мы использовали для этого переменную _LastError. Однако это оказалось невыполнимой задачей. Поэтому было решено сохранить ту же методологию, но отфильтровать ошибки так, чтобы о них можно было сообщить с помощью вызова SetUserError. Подобные ошибки встречаются нечасто, но важно проверить, правильно ли конструкторы выполнили свою работу.

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

Прошу заметить, что в строке 34 мы всегда начинаем со значения, указывающего на то, что рынок закрыт. Затем, в строках 35 и 36, мы инициализируем буфер для данных индикатора-указателя. Это простой процесс, который осуществлялся и раньше. Разница в том, что теперь мы явно сообщаем MetaTrader 5, что всегда хотим получать события от стакана цен, независимо от символа на графике.

На данный момент мы пропустим объяснение функции OnCalculate и оставим его на конец. Это связано с тем, что функция больше не использует вызов iSpread. Давайте немного продвинемся вперед и перейдем к строке 75, где находится функция OnDeInit. Поскольку мы будем получать события стакана цен для любого символа, нам нужно сообщить MetaTrader 5 в какой момент нужно прекратить их получать. Это делается в строке 77, а поскольку мы удаляем индикатор из графика, то в строке 79 мы используем оператор delete, чтобы вернуть память системе.

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

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

Теперь мы переходим к очень интересной и полезной части процедуры OnBookEvent. Обратите внимание на строки 69. Если массив с данными стакана пуст, это означает, что рынок закрыт. Однако если в массиве есть какая-то информация, мы должны провести новую проверку. Почему необходима новая проверка? Причина проста: нам нужно определить происхождение данных, которые мы собираемся анализировать, и это определено в заголовочном файле C_Study.mqh. Можно проверить это, заглянув в исходный код данного файла, в частности в процедуру Update. Прошу заметить, что в строке 124 мы используем вызов TimeCurrent, если работаем с символом, подключенным к торговому серверу. С другой стороны, мы используем значение, хранящееся в глобальной переменной, если находимся в символе, используемом в системе репликации/моделирования. Именно поэтому в строке 69 исходного кода указателя мыши видим тест. Без данного теста, определяющего происхождение данных, мы могли бы в итоге использовать неверные данные, что полностью дестабилизировало бы работу. После того как определили первичное состояние символа, необходимо определить его фактическое состояние. Это делается в строке 70, где мы вводим цикл для поиска совпадения с определенным типом информации в стакане цен. Информация, которую мы ищем, довольно проста и проверяется в строке 71, где мы проверяем наличие констант BOOK_TYPE_BUY_MARKET или BOOK_TYPE_SELL_MARKET. Наличие любой из этих двух констант указывает на то, что рынок открыт и символ находится на аукционе.

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

Теперь мы можем обратить внимание на код в строке 41, т.е. на процедуру OnCalculate. Первый вопрос, который можно задать себе: почему этот код изменился еще раз? Было ли какое-либо обновление MetaTrader 5 между предыдущей статьей и этой? На самом деле нет. Помните, что эти статьи были написаны очень давно. Однако теперь их содержание стало достоянием общественности.

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

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

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

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

Обратите внимание: в предыдущей статье в какой-то момент времени сервис всё еще отправлял данные на указатель мыши. Однако указатель не смог правильно показать нам время, основанное на значениях, отправленных сервисом. Это произошло так, потому что указатель просто застыл. Теперь прошу заметить, что он застыл при использовании того же кода, который мы сейчас используем в процедуре OnCalculate. Чтобы избежать такого замораживания, мы ранее использовали так называемый iSpread. Но здесь мы не используем данный вызов, и при этом экран индикатора не зависает. Почему он сработал сейчас, а не раньше? Честно говоря, у меня нет ответа на этот вопрос. Это один из тех вопросов, которые могут понять только те, кто экспериментирует до предела.

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


Тестовый сервис

Обычно людям трудно научиться программированию, потому что они не понимают следующее: 

ПОРЯДОК РАСПОЛОЖЕНИЯ ФАКТОРОВ ИЗМЕНЯЕТ РЕЗУЛЬТАТ

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

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. #include <Market Replay\Defines.mqh>
07. #include <Market Replay\Auxiliar\Macros.mqh>
08. //+------------------------------------------------------------------+
09. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
10. //+------------------------------------------------------------------+
11. void OnStart()
12. {
13.    long id;
14.    int time;
15.    MqlRates Rate[1];
16.    MqlBookInfo book[1];
17.    
18.    Print("Starting Test Service...");
19.    SymbolSelect(def_SymbolReplay, false);
20.    CustomSymbolDelete(def_SymbolReplay);
21.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
22.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
23.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
24.    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1);
25.    Rate[0].close = 105;
26.    Rate[0].open = 100;
27.    Rate[0].high = 110;
28.    Rate[0].low = 95;
29.    Rate[0].tick_volume = 5;
30.    Rate[0].spread = 1;
31.    Rate[0].real_volume = 10;
32.    Rate[0].time = D'14.03.2023 08:30';
33.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
34.    Rate[0].time = D'14.03.2023 09:00';
35.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
36.    SymbolSelect(def_SymbolReplay, true);
37.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);
38. 
39.    Sleep(1000);
40. 
41.    Print("Waiting for Mouse Indicator...");
42.    while ((def_Loop) && (ChartIndicatorGet(id, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
43. 
44.    book[0].type = BOOK_TYPE_BUY;
45.    book[0].price = Rate[0].close;
46.    book[0].volume = 1;
47.    CustomBookAdd(def_SymbolReplay, book, 1);
48. 
49.    Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
50.    time = (int)macroGetTime(Rate[0].time);   
51.    while (def_Loop)
52.    {
53.       Rate[0].spread = (int)(def_MaskTimeService | time);
54.       CustomRatesUpdate(def_SymbolReplay, Rate, 1);
55.       Sleep(250);
56.       time++;
57.       Print(TimeToString(time, TIME_SECONDS), " >> (int):", time);      
58.       switch (time)
59.       {
60.          case 32430:
61.             book[0].type = BOOK_TYPE_BUY_MARKET;
62.             CustomBookAdd(def_SymbolReplay, book, 1);
63.             break;
64.          case 32460:
65.             book[0].type = BOOK_TYPE_BUY;
66.             CustomBookAdd(def_SymbolReplay, book, 1);
67.             break;
68.       }
69.    }
70.    ChartClose(id);
71.    SymbolSelect(def_SymbolReplay, false);
72.    CustomSymbolDelete(def_SymbolReplay);
73.    Print("Finished Test Service...");   
74. }
75. //+------------------------------------------------------------------+

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

Обратите внимание, что код практически такой же, как и в предыдущей статье, который был предназначен для тестирования указателя мыши. Однако есть несколько новых разработок, которые как раз и породили весь этот процесс, и его можно посмотреть на видео. У каждой из новых строк есть смысл существования и значение. Большинство людей, которые пытаются работать со стаканом цен в пользовательском символе, терпят неудачу, потому что не понимают, что им нужно делать на самом деле. Это связано с тем, что они сосредоточены исключительно на использовании вызова CustomBookAdd, как видно из строк 47, 62 и 64. Но это не так. Попытка передать данные стакана в пользовательском символе, используя только вызов библиотеки CustomBookAdd, не сработает, если мы не предпримем определенные шаги. И, во избежании ошибок, эти шаги должны быть тщательно спланированы.

Основное действие, которое нам нужно осуществить, находится в строке 24. Без этой строки функция CustomBookAdd бесполезна. Но подождите, разве функция CustomBookAdd не используется для отправки данных в стакан, как и функции Tick и Rate? Ответ на все эти вопросы - да, но в то же время и нет. Это кажется противоречивым, но функция CustomBookAdd будет иметь эффект только в том случае, если предварительно задать значение SYMBOL_TICKS_BOOKDEPTH. Значение SYMBOL_TICKS_BOOKDEPTH зависит от того, чего мы хотим добиться. В данном случае, поскольку нашей целью является только направить указатель мыши для информирования пользователя о состоянии пользовательского символа, нам нужна только одна позиция. Поэтому значение, которое мы используем в вызове строки 24 для определения SYMBOL_TICKS_BOOKDEPTH, равно 1. Но если мы хотим создать искусственный стакан для использования в качестве учебного пособия при моделировании или репликации, мы просто должны изменить это значение на то, которое соответствует нашим потребностям.

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

В любом случае, данный сервис будет служить только для проверки отправки данных. У него не будет другого применения. Однако прежде, чем данная статья будет завершена, я хочу вкратце объяснить, что здесь происходит. Между строками 44 и 46 мы определяем положение стакана. Самое важное для нас находится в строке 44. В строке 47 мы сообщаем MetaTrader 5, что нужно сгенерировать событие и сделать данные доступными как часть стакана. Теперь прошу обратить внимание: в строке 50 мы фиксируем время. В строке 56 мы увеличиваем время на одну секунду. Согласно определению в строке 34, начальное значение переменной time должно составлять 32400 секунд. Не забывайте об этом: время определяется в секундах. Затем, в строке 58, мы проверяем наше место во времени. Через 30 секунд мы отправим новое значение в стакан, что произойдет в строке 62. Отправляемое значение - то, которое показывается в строке 61. При этом значении указатель мыши будет сообщать, что символ находится на аукционе. Еще через 30 секунд тест сделает строку 64 равной true, поэтому отправляемым в стакан значением будет строка 65, которая выполнится через строку 66. Это приведет к тому, что время снова будет отображаться на указателе мыши.

И последний момент: время не равняется исключительно одной секунде, потому что в строке 55 мы устанавливаем задержку в четверть секунды.


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

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

В любом случае, этот тестовый сервис не совсем точно отражает то, что мы будем делать в следующей статье. Там мы внедрим систему репликации/моделирования, которая будет сообщать нам, находится ли символ на аукционе или нет. Вам не кажется, что это интересная система? Увидимся в следующей статье!

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Оптимизация нейробоидами — Neuroboids Optimization Algorithm (NOA) Оптимизация нейробоидами — Neuroboids Optimization Algorithm (NOA)
Новая авторская биоинспирированная метаэвристика оптимизации — NOA (Neuroboids Optimization Algorithm), объединяющая принципы коллективного интеллекта и нейронных сетей. В отличие от классических методов, алгоритм использует популяцию самообучающихся "нейробоидов", каждый с собственной нейросетью, адаптирующей стратегию поиска в реальном времени. Статья раскрывает архитектуру алгоритма, механизмы самообучения агентов и перспективы применения этого гибридного подхода в сложных задачах оптимизации.
Арбитражный трейдинг Forex: Простой бот-маркетмейкер синтетиков для старта Арбитражный трейдинг Forex: Простой бот-маркетмейкер синтетиков для старта
Сегодня разберем моего первого робота в сфере арбитража — поставщика ликвидности (если его можно так назвать) на синетических активах. Сегодня данный бот успешно работает как модуль в большой системе на машинном обучении, но я поднял старый арбитражный робот на Форекс из облака, и давайте посмотрим на него, и подумаем, что мы можем с ним сделать сегодня?
Построение модели для ограничения диапазона сигналов по тренду (Часть 8): Разработка советника (II) Построение модели для ограничения диапазона сигналов по тренду (Часть 8): Разработка советника (II)
Ранее мы обсуждали советник на основе индикатора, который также работал в паре с независимым скриптом для построения структуры риска и вознаграждения. Сегодня мы обсудим архитектуру MQL5-советника, объединяющего все функции в одной программе.
Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (DADA) Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (DADA)
Предлагаем познакомиться с фреймворком DADA — инновационным методом выявления аномалий во временных рядах. Он помогает отличить случайные колебания от подозрительных отклонений. В отличие от традиционных методов, DADA гибко подстраивается под разные данные. Вместо фиксированного уровня сжатия он использует несколько вариантов и выбирает наиболее подходящий для каждого случая.