Русский Português
preview
Desarrollo de un sistema de repetición (Parte 70): Ajuste del tiempo (III)

Desarrollo de un sistema de repetición (Parte 70): Ajuste del tiempo (III)

MetaTrader 5Ejemplos | 12 diciembre 2024, 16:19
158 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II), mostré cómo mostrar el tiempo restante de la barra incluso cuando el símbolo está en un momento de baja liquidez. Esta baja liquidez debe entenderse como la ausencia de transacciones ejecutadas en un momento dado. Tales situaciones pueden tener diversas causas. Sin embargo, no es relevante explicar aquí las causas específicas de este fenómeno. Lo importante es aprender a manejar estas situaciones de forma adecuada.

Aun así, nos queda un problema por resolver. Se trata de un problema bastante tedioso y complicado de abordar. No se trata de la programación necesaria para su solución, sino de cómo determinar cuándo ocurre y cómo debemos manejarlo. Este problema se conoce como SUBASTA.

Las subastas suelen surgir de situaciones muy específicas. No se producen de manera arbitraria ni aleatoria. En realidad, existen reglas muy claras y estrictas sobre ellas. Pero, para nosotros, en el contexto del desarrollo de un sistema de repetición/simulación, lo que realmente importa es la siguiente cuestión: ¿cómo podemos informar al usuario de que el símbolo ha entrado en subasta? Este es el principal problema que debemos resolver. Pero la solución, como expliqué antes, ya existe y está implementada en el indicador del mouse. Aunque será necesario hacer algunos cambios en el código para ser más flexibles. Esto permitirá procesar correctamente la información de que el símbolo —en este caso, un símbolo personalizado que se utiliza en un sistema de repetición o simulación— se encuentra en subasta.

Muy bien, esta es la parte fácil. La parte complicada es definir cómo el sistema de repetición/simulador debe declarar que el símbolo personalizado ha entrado en un proceso de subasta. Esta es la parte difícil.

En el pasado, cuando esta aplicación de repetición/simulación aún estaba en una etapa inicial de desarrollo, se utilizaba la siguiente regla: si entre ticks negociados había un período igual o superior a 60 segundos, se identificaba como una subasta. Sé que no es la mejor solución posible, ya que hay razones muy específicas por las que se producen las subastas. Sin embargo, no quiero complicar el sistema incorporando métodos que analicen los movimientos de los ticks para determinar si el símbolo debería entrar en un proceso de subasta. Implementar algo así sobrepasaría el nivel de complejidad que considero aceptable y viable para mantener y desarrollar. Si se hiciera, terminaríamos creando una aplicación capaz de «sentir» el mercado para identificar si ocurre algo extraño. Y esa no es mi intención. Podrías usar este sistema de repetición/simulador para estudiar y lograr algo similar, pero no entraré en detalles ni explicaciones sobre cómo podrías hacerlo.

Por lo tanto, mantendremos la idea inicial: si el símbolo permanece 60 segundos o más sin que se realice una transacción, el indicador de mouse debe informar al usuario de que el símbolo ha entrado en subasta. Así de simple. De esta manera, podemos centrarnos en la parte del código. Comencemos entonces con la implementación.


Hay cosas curiosas

Hay aspectos de la programación que solo quienes la desarrollan activamente pueden entender. No sé cómo ni quiero intentar comprenderlas. Después de más de dos décadas como programador profesional, definitivamente he dejado de tratar de comprender ciertos fenómenos. Simplemente asumo que algunas cosas no se hacen como deberían y que no puedo controlar ciertas condiciones. Así que no intentes entender lo que ocurrió. Simplemente reemplaza los archivos con el nuevo código que mostraré a continuación. No me preguntes qué sucedió ni cómo funciona el código, porque si intento explicarlo, pensarás que estoy loco. Por lo tanto, los nuevos códigos que deben usarse están íntegramente a continuación. No habrá anexos. Simplemente reemplaza los antiguos por los nuevos.

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ódigo fuente del archivo C_Terminal.mqh

El código que se muestra arriba debe colocarse en lugar del archivo antiguo C_Terminal.mqh. El código que se muestra a continuación debe reemplazar el archivo antiguo 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ódigo fuente del archivo C_Mouse.mqh

Una vez realizadas estas modificaciones, veamos qué más debemos hacer. Porque, a veces, ocurren cosas extrañas y, como aspirante a programador, debes comprender que, en ocasiones, no será posible entender realmente qué está pasando. Esto se debe a que, como te dije, a veces suceden cosas extrañas. Por esta razón, estoy mostrando cada una de las modificaciones realizadas, para que veas que no todo está bajo tu control.


Un nuevo indicador de mouse emerge

En este primer momento, debemos realizar un cambio que resultará inusual para muchos. Es necesario que el indicador de mouse pueda trabajar con todos los eventos que existirían en un mercado real dentro del contexto de la repetición/simulación. Podrías pensar: «¿No es esto lo que ya está ocurriendo? ¿No?» Lamentablemente, hasta ahora, el sistema de repetición/simulación estaba aislado del sistema conectado al servidor real, ya fuera en una cuenta de demostración o en una cuenta real. Por lo tanto, el indicador de mouse tenía dos modos de operación: uno en el que utilizaba los eventos del libro de órdenes y otro en el que estos eventos eran ignorados.

La gran cuestión es precisamente esta: los eventos del libro de órdenes. Sin embargo, estos eventos son un poco más complicados de lo que cabría imaginar. No basta con usar la función CustomBookAdd, que forma parte de la biblioteca de funciones de MQL5, para resolverlo todo. La situación es algo más compleja para nosotros en el contexto de la repetición/simulación. Pero no necesitas preocuparte ni sentirte inseguro. Llegaremos a ese punto, donde explicaré cómo utilizar la función CustomBookAdd. Pero vamos con calma. Una cosa cada vez.

Como debes haber visto en el tema anterior, tanto el código fuente del archivo de cabecera C_Terminal.mqh como el del archivo C_Mouse.mqh se modificaron. Aunque no entré en detalles sobre los cambios realizados, es importante que sepas que los hubo. Sin embargo, dado que son relativamente fáciles de entender, no considero necesario profundizar en ellas. Al igual que se realizaron cambios en esos archivos, también hay modificaciones en el archivo de cabecera C_Study.mqh. El código completo de este archivo puede verse a continuación.

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ódigo fuente del archivo C_Study.mqh

A diferencia de los otros archivos de cabecera mencionados anteriormente, que no tendrán tanto impacto en lo que se implementará, aquí nos encontramos con una situación algo distinta.

Observa que el constructor de la clase C_Study ha pasado por un proceso de limpieza en su código. En la línea 99, se realizó un cambio en el valor de prueba para evitar que errores no derivados del código provoquen su finalización prematura. Pero hay otro punto importante. Observa que ya no estamos inicializando Rate.close en el constructor. Esta inicialización ahora se realiza en otro punto del código. En la línea 114, verificamos si el valor es cero. Si este es el caso, en la línea 115 capturamos el valor de cierre, como se hacía anteriormente.

Este cambio se debe a que, cuando el indicador se encontraba en una plantilla y se añadía al gráfico a través de esta, a veces ocurría que Rate.close tomaba un valor incorrecto al inicializarse en el constructor. Aunque no logré entender completamente por qué sucedía esto, el hecho es que, después de realizar los cambios en el código, empezamos a observar una inicialización errática y aleatoria del valor de Rate.close. Para reducir esta posibilidad y sus efectos, cambié el punto de inicialización.

Ahora hay un detalle: la verificación de la línea 114 siempre será verdadera cuando se ejecute la línea 100, ya que la línea 100 reinicia todos los valores de la estructura m_Info a cero. Aunque el código completo está disponible, estos son los puntos que, en mi opinión, merecen algún tipo de explicación. El resto del código no sufrió modificaciones. Muy bien. Veamos ahora los cambios realizados en el código del indicador de mouse. A continuación, puede verse el código completo del indicador.

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/pt/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. //+------------------------------------------------------------------+

Código fuente del Indicador de Mouse

Está bien. Fíjate en que el código del indicador de mouse ahora es muy diferente. De hecho, es considerablemente distinto del anterior. Estas modificaciones tienen una razón de ser. Aunque no logres comprenderlas todas, su objetivo es forzar al indicador de mouse a funcionar de una manera más genérica, pero al mismo tiempo uniforme en cualquier tipo de gráfico o símbolo. Veamos qué cambios se han realizado.

En primer lugar, tenemos la función OnInit. Si observas, notarás que es diferente de la versión anterior. Hay algunos detalles importantes que debes tener en cuenta. El primero de ellos es que, en la línea 31, verificamos si la variable de error se ha configurado mediante alguna llamada que hayamos realizado. Anteriormente, cualquier error provocaba que la inicialización informara de INIT_FAILED. Pero, ¿por qué he cambiado esto? El motivo es que, en ocasiones, durante su inicialización, el indicador reportaba un error que no tenía ningún sentido. Como el constructor de la clase no nos permite devolver ningún valor, utilizaba la variable _LastError para este propósito. Sin embargo, esto resultó inviable. Por lo tanto, decidí mantener la misma metodología, pero filtrando los errores para que se pudieran informar mediante la llamada a SetUserError. Este tipo de error no será muy frecuente, pero es importante para verificar si los constructores realizaron su trabajo correctamente.

Una vez hecho esto, introducimos algo que antes no se hacía: indicamos a MetaTrader 5 que deseamos recibir eventos del libro de órdenes. Atención: antes, los eventos del libro solo se producían cuando los datos del gráfico pertenecían a un símbolo real, es decir, cuando estábamos conectados de alguna manera al servidor de trading. Ahora siempre recibiremos eventos del libro, incluso si el símbolo es personalizado. Esto es importante para nosotros. Mi intención es usar el mecanismo ya construido para informar al usuario de que el símbolo se encuentra en subasta. Esto simplifica todo el trabajo, ya que la interfaz será la misma y no será necesario adaptarla de ninguna forma. Todo lo que tendremos que hacer será pasar los datos correctamente al indicado y este se encargará del resto.

Fíjate que, en la línea 34, siempre comenzamos con el valor que indica que el mercado está cerrado. Luego, en las líneas 35 y 36, inicializamos el buffer para los datos del indicador de puntero. Es algo sencillo que ya se hacía anteriormente. La diferencia estriba en que ahora informamos explícitamente a MetaTrader 5 de que deseamos recibir siempre eventos del libro, independientemente del símbolo que esté en el gráfico.

De momento, vamos a omitir la explicación de la función OnCalculate y la dejaremos para el final. Esto se debe a que seguramente ya habrás notado que ya no hace uso de la llamada a iSpread. Avancemos un poco y dirijámonos a la línea 75, donde se encuentra la función OnDeInit. Dado que estaremos recibiendo eventos del libro de órdenes de cualquier símbolo, necesitamos informar a MetaTrader 5 en qué momento dejar de recibirlos. Esto se realiza en la línea 77 y, como estamos eliminando el indicador del gráfico, en la línea 79 utilizamos el operador delete para devolver la memoria al sistema.

A continuación, analicemos el procedimiento para gestionar los mensajes del libro de órdenes. Esto se lleva a cabo en la línea 62, donde se encuentra la rutina OnBookEvent. Este procedimiento tiende a ser lo más sencillo posible. No nos interesa analizar el volumen ni la cantidad de órdenes presentes en el libro. Lo que realmente queremos saber aquí es si el símbolo está en negociación, en subasta o si el mercado está cerrado. Para determinar esto, basta con observar lo que sucede en el libro de órdenes.

En la línea 67, filtramos las llamadas relacionadas con el símbolo donde se encuentra el indicador. Esto se debe a que MetaTrader 5 no realiza esta filtración automáticamente, sino que lanza cualquier evento del libro a todos los gráficos que lo hayan solicitado. Por esta razón, no es conveniente observar múltiples libros de órdenes de símbolos que no nos interesan. Si lo hacemos, MetaTrader 5 dispararía eventos irrelevantes, lo que nos haría perder el tiempo innecesariamente. Una vez aplicado el filtro, en la línea 68 utilizamos una llamada de la biblioteca de MQL5 que captura los datos más recientes del libro. Esto es esencial para que el procedimiento que estamos implementando cumpla su función.

Ahora llegamos a la parte realmente interesante y útil del procedimiento OnBookEvent. Fíjate en la línea 69. Si el array con los datos del libro está vacío, significa que el mercado está cerrado. Sin embargo, si hay alguna información en el array, debemos realizar una nueva verificación. ¿Por qué es necesaria esta nueva verificación? La razón es sencilla: necesitamos determinar el origen de los datos que vamos a analizar. Esto se define en el archivo de cabecera C_Study.mqh. Puedes comprobarlo observando el código fuente de este archivo, específicamente en el procedimiento Update. Fíjate en que, en la línea 124, hacemos uso de una llamada a TimeCurrent si estamos trabajando con un símbolo conectado al servidor de trading. Por otro lado, usamos un valor almacenado en una variable global si estamos en un símbolo utilizado en el sistema de repetición/simulación. Este es el motivo del test de la línea 69 del código fuente del indicador de mouse. Sin esta prueba, que determina el origen de los datos, podríamos terminar utilizando datos incorrectos, lo que desestabilizaría por completo el funcionamiento. Una vez definido el estado primario del símbolo, debemos determinar su estado real. Esto se lleva a cabo en la línea 70, donde entramos en un bucle para buscar una correspondencia con un tipo específico de información dentro del libro de órdenes. La información que estamos buscando es muy sencilla y se verifica en la línea 71, donde se comprueba la presencia de las constantes BOOK_TYPE_BUY_MARKET o BOOK_TYPE_SELL_MARKET. La presencia de cualquiera de estas dos constantes indica que el mercado está abierto y que el símbolo está en subasta.

Como puedes ver, con una medida tan simple implementada en el procedimiento OnInit, al comenzar a observar el libro de órdenes de cualquier símbolo donde se encuentra el indicador, es posible informar sobre el estado actual del símbolo. Este tipo de solución es precisamente lo que hace que todo el trabajo de implementación sea fascinante.

Ahora podemos dirigir nuestra atención al código de la línea 41, es decir, al procedimiento OnCalculate. La primera pregunta que podrías hacerte es: ¿por qué se ha modificado este código una vez más? ¿Se ha producido alguna actualización de MetaTrader 5 entre el artículo anterior y este? En realidad, no. Recuerda, estimado lector, que estos artículos se escribieron hace mucho tiempo. Sin embargo, ahora se hace público su contenido.

El resultado de una ejecución puede verse en el video siguiente, donde muestro cómo se aplica en la práctica. Antes de leer la explicación, te recomiendo que veas el video. Sin duda, esto te ayudará a comprender mejor la explicación que daré a continuación.


Video de demostración.

Si has visto el video, habrás notado que hago las cosas de una manera muy concreta. No te preocupes por el código del servicio por ahora. Eso lo trataremos en otro momento. De momento, mantengamos el enfoque en el indicador de mouse.

Primero, inicialicé el servicio. Este quedó en espera hasta que se detectó que se colocó el indicador de mouse en el gráfico. En ese momento, el mostrador indicaba una cuenta regresiva cuyos valores también se mostraban en la caja de herramientas de MetaTrader 5, lo que permite verificar si el indicador de mouse estaba accediendo efectivamente a los valores proporcionados por el servicio.

Ten en cuenta el siguiente hecho: en el artículo anterior, en un momento dado, el servicio seguía enviando datos al indicador de mouse. Sin embargo, el indicador no podía mostrarnos correctamente la temporización basada en los valores que el servicio enviaba. Esto sucedía porque el indicador simplemente se congelaba. Ahora bien, observa que se congelaba al usar el mismo código que estamos utilizando actualmente en el procedimiento OnCalculate. Para evitar ese congelamiento, anteriormente habíamos utilizado la llamada iSpread. Pero aquí no estamos usando esa llamada y, aun así, la pantalla del indicador no se congeló. ¿Por qué funcionó ahora y antes no? Sinceramente, no tengo respuesta para esta pregunta. Es una de esas cuestiones que solo quienes experimentan y llevan las cosas al límite pueden llegar a comprender.

Te muestro esto, querido lector, para demostrarte que, como programadores, no siempre tenemos la solución a todo. A veces nos encontramos ante resultados que no sabemos exactamente por qué ni cómo se produjeron. En mi opinión, mostrarte este tipo de cosas hace que el aprendizaje y mi propia experiencia sean mucho más agradables. Porque aprenderás que los objetivos no siempre se alcanzan desde el principio. Y para mí, es una oportunidad para entender cómo funcionan realmente las partes más pequeñas de MetaTrader 5 y hasta qué punto puedo intervenir en ellas sin causar un fallo general en la plataforma. Para concluir este artículo, echemos un vistazo al código fuente del servicio que se utilizó en el video.


Servicio de pruebas

Normalmente, las personas tienen dificultades para aprender a programar porque no entienden que: 

EL ORDEN DE LOS FACTORES ALTERA EL RESULTADO

En programación, esto se aplica en la gran mayoría de los casos. Rara vez podemos programar algo sin preocuparnos por el orden en que los eventos ocurren. Y aquí, en el servicio de testeo, esta afirmación es absolutamente cierta. Así que echemos un vistazo al código fuente del servicio, que puede verse íntegramente a continuación.

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. //+------------------------------------------------------------------+

Código fuente del servicio de prueba

Observa que el código es prácticamente igual al del artículo anterior, que estaba diseñado para probar el indicador de mouse. Sin embargo, aquí tenemos algunas novedades que, precisamente, provocaron todo el proceso que puedes ver en el video. Cada una de las nuevas líneas tiene una razón de ser y un significado. La mayoría de las personas que intentan trabajar con el libro de órdenes en un símbolo personalizado fracasan porque no comprenden lo que realmente deben hacer. Esto ocurre porque se centran únicamente en el uso de la llamada CustomBookAdd, como puede observarse en las líneas 47, 62 y 64. Pero no es así como funciona. Intentar pasar datos al libro en un símbolo personalizado usando únicamente la llamada de biblioteca CustomBookAdd no funcionará si no tomamos ciertas medidas previas. Y estas medidas deben planificarse cuidadosamente para evitar errores.

La principal medida que debemos tomar se encuentra en la línea 24. Sin esta línea, la función CustomBookAdd no nos sirve de nada. Pero espera un momento, ¿cómo es eso? ¿La función CustomBookAdd no sirve precisamente para enviar datos al libro, al igual que las funciones Tick y Rate? La respuesta a todas estas preguntas es sí, pero al mismo tiempo no. Parece contradictorio, pero la función CustomBookAdd solo tendrá algún efecto si antes defines un valor para SYMBOL_TICKS_BOOKDEPTH. El valor que hay que usar en SYMBOL_TICKS_BOOKDEPTH dependerá de lo que se desee lograr. En este caso, como nuestro objetivo es únicamente orientar el indicador de mouse para que informe al usuario sobre el estado del símbolo personalizado, solo necesitamos una única posición. Por esta razón, el valor que estoy utilizando en la llamada a la línea 24 para definir SYMBOL_TICKS_BOOKDEPTH es 1. Sin embargo, si deseas crear un libro artificial para usarlo como herramienta de estudio, ya sea en simulaciones o repeticiones, basta con cambiar ese valor por otro que se ajuste a tus necesidades.

En cualquier caso, esta es la parte básica. Solo después de definir SYMBOL_TICKS_BOOKDEPTH podremos recibir realmente los datos que estarán en la estructura MqlBookInfo. Esta cuestión fue algo complicada de entender, ya que no encontré ninguna referencia clara que explicara cómo integrar la estructura MqlBookInfo con un símbolo personalizado. Todas las referencias solo mencionaban CustomBookAdd. Sin embargo, únicamente la documentación mencionaba SYMBOL_TICKS_BOOKDEPTH, pero no establecía claramente la relación entre esta definición y la posibilidad de acceder a los datos de la estructura MqlBookInfo que se publican mediante CustomBookAdd.

En cualquier caso, este servicio solo servirá para probar el envío de datos. No tendrá ninguna otra utilidad. Antes de cerrar este artículo, sin embargo, quiero explicar brevemente lo que ocurre aquí. Entre las líneas 44 y 46, definimos una posición del libro. Lo más importante para nosotros está en la línea 44. En la línea 47, informamos a MetaTrader 5 de que genere un evento y coloque los datos de forma que queden disponibles como parte del libro. Ahora presta atención al siguiente detalle: en la línea 50 capturamos el tiempo. En la línea 56, incrementamos el tiempo en un segundo. Según la definición de la línea 34, el valor inicial de la variable time será 32 400 segundos. No olvides este punto: el tiempo está definido en segundos. Luego, en la línea 58, verificamos nuestra posición en el tiempo. Al avanzar 30 segundos, enviaremos un nuevo valor al libro, lo que ocurre en la línea 62. El valor que se envía es el mostrado en la línea 61. Este valor hará que el indicador del mouse informe de que el símbolo está en subasta. Tras otros 30 segundos, el test hará que la línea 64 sea verdadera y, por lo tanto, el valor enviado al libro será el de la línea 65, que se ejecutará mediante la línea 66. Esto hará que el tiempo vuelva a mostrarse en el indicador de mouse.

Un último detalle: el tiempo no es exactamente de un segundo, ya que en la línea 55 estamos estableciendo un retraso de un cuarto de segundo.


Consideraciones finales

En este artículo, te mostré cómo transferir valores al libro mediante un símbolo personalizado. Aunque el servicio utilizado tiene como único propósito probar este método de transferencia de valores, quedó demostrado que podemos hacerlo de una manera bastante razonable. A pesar de las dificultades iniciales, es realmente posible implementarlo. Por lo tanto, ahora tienes una forma más de transferir información entre el servicio y un indicador o Expert Advisor mediante el libro. Sin embargo, siempre debes recordar que todo tiene un coste y el uso indiscriminado del libro de órdenes conlleva un coste que debe tenerse en cuenta.

En cualquier caso, este servicio de pruebas no refleja exactamente lo que haremos en el próximo artículo. Allí, implementaremos un sistema de repetición/simulación que nos informará si el símbolo está en subasta o no. ¿No te parece un sistema interesante? Entonces, nos vemos en el próximo artículo.

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12326

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollo de un sistema de repetición (Parte 71): Ajuste del tiempo (IV) Desarrollo de un sistema de repetición (Parte 71): Ajuste del tiempo (IV)
En este artículo, mostraré cómo implementar lo presentado en el artículo anterior en el servicio de repetición/simulación. Pero, como suele ocurrir con muchas cosas en la vida, es habitual que surjan problemas. Y este caso no fue una excepción. Sigue leyendo y descubre cuál será el tema del próximo artículo de esta serie. El contenido expuesto aquí tiene como único propósito la enseñanza. En ningún caso debe considerarse una aplicación cuyo objetivo no sea el aprendizaje y el estudio de los conceptos mostrados.
Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II) Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II)
Aquí entenderemos por qué necesitamos utilizar la función iSpread. Al mismo tiempo, comprenderemos cómo el sistema nos informa del tiempo restante de la barra cuando no hay ticks disponibles para hacerlo. El contenido presentado aquí tiene como único propósito la enseñanza y la didáctica. En ningún caso debe considerarse una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos mostrados.
Redes neuronales: así de sencillo (Parte 94): Optimización de la secuencia de entrada Redes neuronales: así de sencillo (Parte 94): Optimización de la secuencia de entrada
Al trabajar con series temporales, siempre utilizamos los datos de origen en su secuencia histórica. Pero, ¿es ésta la mejor opción? Existe la opinión de que cambiar la secuencia de los datos de entrada mejorará la eficacia de los modelos entrenados. En este artículo te invito a conocer uno de los métodos para optimizar la secuencia de entrada.
Cómo ver las transacciones directamente en el gráfico sin tener que perderse en el historial de transacciones Cómo ver las transacciones directamente en el gráfico sin tener que perderse en el historial de transacciones
En este artículo, crearemos una herramienta sencilla para visualizar cómodamente posiciones y transacciones directamente en el gráfico con navegación mediante teclas. Esto permitirá a los operadores examinar visualmente las transacciones individuales y recibir toda la información sobre los resultados comerciales directamente en el momento.