Português
preview
Desarrollo de un sistema de repetición (Parte 50): Esto complica las cosas (II)

Desarrollo de un sistema de repetición (Parte 50): Esto complica las cosas (II)

MetaTrader 5Ejemplos | 19 julio 2024, 09:57
24 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Desarrollo de un sistema de repetición (Parte 49): Esto complica las cosas (I), comenzamos a complicar un poco más las cosas dentro del sistema de repetición/simulación. Esta complicación, aunque no es intencionada, tiene como objetivo hacer que el sistema sea más estable y seguro. Esto, en cuanto a la posibilidad de que se pueda utilizar en un modelo completamente modular.

Aunque estos cambios parezcan completamente innecesarios al principio, nos permiten evitar que el usuario haga un mal uso de algunas de las cosas que se están desarrollando y que son necesarias solo y exclusivamente para el sistema de repetición/simulación. Así se evita que el usuario menos experimentado, por casualidad, elimine o desconfigure las partes que el servicio de repetición o simulación necesita en el gráfico. Esto ocurre en el momento en que dicho servicio se encuentra en funcionamiento.

Al final del artículo anterior, informé de que existía un problema que hacía que el indicador de control fuera inestable, hasta el punto de permitir que sucedieran cosas que en realidad no deberían suceder. Estos problemas no son graves, ni pueden hacer que la plataforma se bloquee. Pero pueden hacer que el servicio funcione de manera inesperada en algunos momentos. Debido a esto, no fue posible adjuntar ningún archivo en el artículo anterior, para que tú, querido lector, tuvieras acceso al sistema en la etapa actual de desarrollo.

Pero en este artículo vamos a corregir, o intentar corregir, el problema. Al menos uno de ellos.

 

Nos enfrentamos al primer problema

El primero de los problemas que tenemos es que el servicio de repetición/simulación se implementó para utilizar una plantilla. Esta iba a contener todos los datos necesarios para que el gráfico se abriera y el servicio pudiera ejecutarse. 

Aunque este enfoque sea funcional y se haya mostrado bastante atractivo por un buen tiempo, limita al usuario. O para que lo entiendas mejor: el hecho de que el servicio utilice una plantilla específica, hace que el usuario deje de poder usar métodos propios o una configuración de gráfico que le resulte adecuada. Es mucho más práctico colocar todas las cosas necesarias en una plantilla que el usuario ha creado al configurar un gráfico, que tener que configurar manualmente el gráfico cada vez que vaya a operar con un determinado activo.

Si eres nuevo en el mercado, tal vez esto no tenga mucho sentido. Pero los operadores con más experiencia tienen en su arsenal plantillas previamente configuradas, para poder realizar un análisis estandarizado de un activo específico en un momento determinado. Dichas plantillas se desarrollan a lo largo de varios meses e incluso años para dejar todo previamente configurado. Así, el operador simplemente guarda la plantilla y, cuando la necesita, la aplica al gráfico.

Este tipo de cosas se mostró en el artículo Desarrollo de un sistema de repetición (Parte 48): Conceptos que hay que entender y comprender, que dio inicio a todo este trabajo de modificación del sistema de repetición/simulación. En dicho artículo, mostré cómo se puede configurar un gráfico sin usar una plantilla, sino un servicio o un estándar gráfico, de manera que, independientemente de la plantilla que se esté utilizando, puedas tener ciertas cosas en el gráfico. Esto siempre se mantiene gracias a un servicio que funciona en MetaTrader 5, para estandarizar las cosas.

No obstante, para poder colocar objetos gráficos, como los que necesitamos en el gráfico, para que el indicador de control pueda controlar el servicio, se requiere un dato importante: Este dato es el ID del gráfico que recibirá tales objetos.

Podrías pensar: Pero esto es sencillo de hacer para que el indicador lo sepa: Basta con usar la función ChartID() y el indicador sabrá cuál es el ID del gráfico. De acuerdo, no te quito la razón. Pero como dicen algunos: La ignorancia es una bendición. No me malinterpretes. Yo mismo tuve varios dolores de cabeza para entender por qué las cosas no funcionan adecuadamente en ciertos momentos. Así que no creo que esté equivocado al pensar así.

De hecho, usar la función ChartID() ciertamente devolverá el ID del gráfico para que podamos colocar objetos en él. Recuerda: Necesitamos el ID para indicarle a MetaTrader 5 en qué gráfico se vinculará el objeto.

Sin embargo, la función ChartID no funcionará cuando se abra el gráfico a través del servicio. Es decir, cuando el servicio usa la clase C_Replay.mqh y ejecuta el código presente en la línea 183, se creará un ID diferente. Puedes ver este código consultando el artículo anterior. En esa misma línea 183, se ejecutará una llamada a ChartOpen para crear el gráfico que contendrá el activo que estaremos reproduciendo/simulando.

Si comparas los valores devueltos por ChartOpen, presente en el servicio, con el valor de ChartID presente en el indicador de control, notarás que los valores serán diferentes. Así, la plataforma MetaTrader 5 no sabrá qué ID utilizar. Si usa el ID devuelto por ChartID, pondrá los objetos en una ventana incorrecta o incluso inexistente, pero si usa el ID generado dentro del servicio, en el momento en que ChartOpen crea el ID, podremos utilizar los objetos.

Pues bien, ahora viene el problema: ¿Cuál sería la mejor solución para resolver esta cuestión del ID del gráfico? Tal vez pienses: No importa, simplemente usa el valor del ID que se obtuvo de la llamada ChartOpen y asunto resuelto. Pero aquí es donde reside el problema. Recuerda que en el artículo anterior eliminamos la variable global del terminal que era responsable de informar al indicador de control el ID del gráfico generado en ChartOpen, ¿verdad?

En el momento en que esto se hizo, el código de la clase C_Terminal se encargó de buscar el ID del gráfico. Y esto se hace usando la función ChartID. Si has estado siguiendo esta serie de artículos y actualizando los códigos conforme los explico, el código de la clase C_Terminal que tendrás será similar al que se ve en el fragmento a continuación:

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

Fragmento del código fuente de C_Terminal.mqh

En este código, puedes notar que en la línea 60 tenemos el constructor de la clase C_Terminal. Este recibe un valor por defecto, CERO, de modo que el constructor actúa como un constructor estándar. La cuestión de hecho ocurre en la línea 62, donde al verificar el valor pasado al constructor, definiremos cuál es el ID del gráfico a usar. Pues bien, si este valor es el predeterminado, la clase C_Terminal pedirá a MetaTrader 5 que informe de cuál es el ID del gráfico, usando el valor devuelto por ChartID. Este valor será incorrecto cuando la llamada se derive del hecho de que el servicio creó el gráfico y lanzó el indicador, que hará la llamada a C_Terminal para conocer el valor del ID.

Así, podemos informar al constructor de la clase C_Terminal del valor del ID y, si lo hacemos, la llamada a ChartID se ignorará y el ID comunicado al constructor será el utilizado por el indicador.

Una vez más, debes recordar que no utilizaremos más una variable global del terminal para transmitir este valor al indicador. Podríamos hacer esto de manera temporal, pero la solución será otra. Pasaremos el valor del ID mediante el parámetro de llamada del indicador.


Implementación de la solución

Tal vez te hayas quedado realmente sorprendido y confundido con lo que vamos a hacer. Pero esto se debe a que en el artículo anterior no expliqué cómo implementar la solución y cómo funcionaba antes de aplicarla. Para facilitar las cosas, observa el video 01 sobre cómo se comportaba el sistema. Esto antes de que el ID se pasara como parámetro al indicador de control.


Video 01

Puedes darte cuenta de que se genera un mensaje de error. Este mensaje se debe a que el indicador no puede conocer el ID del gráfico, por lo que la llamada a ChartID devuelve un error y, como el código del indicador verifica esto, puedes notarlo al mirar la línea 25 en el código del indicador presente en el artículo anterior. Pero, al observar ese código, notarás que hay algo diferente entre el código y el video. De hecho, hay algo diferente. Pero no te preocupes, pronto tendrás acceso al código que se muestra en el vídeo, de modo que las cosas serán más auténticas. La diferencia entre lo que se ve en el video y el código del artículo anterior se debe a que en ese momento no entendía por qué el indicador aparecía listado como presente en el gráfico, pero no aparecía en el mismo.

Fue preciso modificar el código para entender por qué las cosas no funcionaban correctamente. Por eso mencioné que no se sintieran ofendidos si decía que la ignorancia es una bendición. Yo mismo no comprendía por qué el código se comportaba de esa manera.

Sin embargo, no diré que las cosas se han corregido por completo, pues eso no sería honesto ni verdadero de mi parte. El hecho de pasar el ID del gráfico al indicador hace que el indicador se presente. Pero... Bien, antes de ver este "pero", vamos ver cómo se modificó el código para que las cosas volvieran a funcionar. Al menos ahora, el indicador de control se presenta en el gráfico.

Para hacer esto, no fue necesario modificar el código fuente del servicio, pero sí fue necesario modificar el código del archivo de encabezado C_Replay.mqh. El archivo modificado puede verse en su totalidad a continuación:

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

Código fuente de la clase C_Replay.mqh

A pesar de haber sido modificado para soportar lo que necesitamos, este código nos permitirá hacer muchas más cosas en breve. Pero primero veamos lo que se añadió. Básicamente, verás que la mayor parte del código sigue siendo bastante similar al del artículo anterior. Como quiero que comprendas perfectamente cómo se está implementando, he puesto el código completo para que no tengas dudas sobre dónde colocar las funciones que se van a usar.

Muy bien, en la línea 116, he añadido un nuevo procedimiento con el propósito de añadir el indicador de control en el gráfico. Este procedimiento se llama en la línea 193, es decir, justo después de que el gráfico haya sido colocado por el servicio y presentado por MetaTrader 5. Pero volvamos a la línea 116. Lo primero que hacemos es crear un handle en la línea 120, que hará referencia al indicador que está presente en el código del servicio. Recuerda que el indicador está incorporado en el ejecutable del servicio como un recurso. Una vez que le hemos dicho a MetaTrader 5 dónde se encuentra el indicador, necesitamos proporcionarle una información. Esta información es el m_IdReplay, es decir, el ID del gráfico generado por la llamada a ChartOpen.

De esta forma, el indicador sabrá cuál es el ID correcto del gráfico en el que se está colocando. Presta atención a esto. Aunque se abra otro gráfico vinculado al activo de repetición, el indicador no aparecerá, sino que solo se mostrará en el gráfico que ha creado el servicio. Esto se impone y se realiza en la línea 121. Luego, en la línea 122, liberamos el handle que se creó, ya que no lo necesitamos más.

Pero lo que acabamos de ver es solo parte de la solución. La otra parte está precisamente en el código fuente del indicador de control. Este puede verse en su totalidad a continuación:

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

Código fuente del Indicador de control

Nota que ahora tenemos en la línea 16 un input, es decir, el indicador recibirá un parámetro. Aquí reside uno de los motivos para no permitir al usuario acceso directo a este indicador, al menos para que no pueda colocarlo manualmente en el gráfico. Este parámetro, que el indicador recibirá en la línea 16, informa cuál es el ID del gráfico donde se colocarán los objetos. Este valor debe ser correctamente llenado. Por defecto, será CERO, es decir, si el servicio intenta colocar el indicador pero no informa el gráfico, se generará un error. El mensaje de error aparece en la línea 27. Esto explica la diferencia entre lo que se esperaba y lo que se presentó en el video 01.

Ahora, observa dónde se usa el valor del parámetro que se introduce en la línea 16. Se utiliza en varios puntos, pero el principal es en la línea 23, donde le decimos a la clase C_Terminal que no debe usar un valor generado al buscar el ID del gráfico. La clase C_Terminal deberá usar el valor informado por el servicio que creó el gráfico. Como puedes observar, otros puntos también utilizan el valor indicado en la línea 16. Sin embargo, al compilar de nuevo el archivo del servicio y ponerlo en funcionamiento en MetaTrader 5, obtendremos el resultado que se ve en el video 02.


Vídeo 02

Observa con atención el video 02. Verás que el sistema actúa de manera bastante extraña cuando intentamos interactuar con él. La pregunta es: ¿por qué está sucediendo esto? No hemos hecho ningún cambio que eventualmente pudiera generar tal comportamiento.

Este es el segundo problema que debemos resolver. Sin embargo, este problema es considerablemente más complejo de resolver. Esto se debe a que la causa no es ni el servicio, ni el indicador de control, ni mucho menos la plataforma MetaTrader 5. Es la interacción o la falta de interacción del puntero del ratón con los objetos presentes en el gráfico.

En ese momento, ya debes estar pensando: "Ahora se pone difícil. ¿Cómo vamos a corregir un fallo que no existía antes de empezar a modificar el código que, en apariencia, ya funcionaba perfectamente?". Esto fue antes de tomar la fatídica decisión de permitir al usuario utilizar una plantilla personal y no una que el sistema de repetición/simulación venía utilizando desde hace mucho tiempo. Pues bien. Esto sí es programación. Resolver los problemas que surgen mientras intentas aplicar nuevas cosas y resolver otros problemas que seguramente surgirán.

Pero antes de abordar esta cuestión y resolver el problema de interacción entre el puntero del ratón y los objetos del gráfico, creados por el indicador de control, vamos a implementar la función que permite al usuario utilizar cualquier plantilla que decida. Esto será útil cuando el usuario ya tenga alguna plantilla montada y destinada a ser utilizada en el activo, ya sea en la cuenta demo o en la cuenta real. Pero ahora, permitiremos que el usuario pueda usar esta misma plantilla en la repetición/simulador.

Hacer esto no es algo complejo, es incluso bastante simple. Sin embargo, estamos tratando con usuarios que tienden a hacer las cosas de manera desordenada y sin seguir una lógica, por lo que debemos garantizar que el indicador de control permanezca en el gráfico. Incluso cuando el usuario insista en usar el sistema de repetición/simulación de una manera completamente diferente a la prevista.

En ese caso, lo primero que hay que hacer es ofrecer al usuario la posibilidad de informar al servicio para que abra el gráfico utilizando una plantilla concreta. Esto se consigue fácilmente con pocas modificaciones. Para no repetir todo el código, voy a colocar solo el fragmento de la función donde debe hacerse el cambio. Primero veamos el código del servicio. Este se puede ver justo abajo:

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

Código fuente del servicio

En este código se puede ver que se añadió la línea 18, donde se ofrece al usuario la posibilidad de indicar qué plantilla se debe utilizar en el momento en que el servicio abra el gráfico del activo que se usará como repetición o simulación. Este valor se pasa a la clase en la línea 25, donde hacemos los ajustes necesarios. Para entender lo que está ocurriendo, veamos el fragmento de esta rutina. Coloco el fragmento, ya que no tiene sentido repetir todo el código.

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

Fragmento del código fuente C_Replay.mqh (Actualización)

Observa que en la línea 176, añadimos un parámetro extra. Esto para que la clase sepa cuál plantilla se cargará. Entonces en la línea 200, intentamos aplicar la plantilla informada por el usuario. Si no es posible, en la línea 201 informamos al usuario y pasamos a usar la plantilla predeterminada. Hagamos una pausa en este punto para explicar algo. Por defecto, MetaTrader 5 siempre usará la plantilla Predeterminada, por lo que no es necesario forzar una nueva llamada para que realmente se cargue y se aplique al gráfico.

En este momento, vale la pena explicar algo que puede ser interesante: Si operas con más de un activo y quieres hacer estudios dirigidos a cada uno utilizando una plantilla específica, puede ser viable pensar en añadir en el archivo de configuración de la simulación el nombre de la plantilla que se va a usar. Así ya quedará configurado, sin necesidad de informar en el momento en que el servicio se ponga en ejecución. Pero esto es solo una idea para quien desee hacerlo. En este sistema no voy a hacer eso. Sobre todo porque hay otro aspecto en esta historia de la plantilla. Precisamente este aspecto es nuestro dolor de cabeza como programadores. El usuario.

¿Por qué el usuario es un problema para nosotros? Quizás esto no tenga mucho sentido. Pero sí, es un gran dolor de cabeza, ya que puede decirle a MetaTrader 5 que ejecute el servicio y, en un momento dado, cambiar la plantilla del gráfico donde se está ejecutando la repetición/simulador. Esto sin reiniciar el servicio para que el servicio haga el cambio. Quizás no hayas entendido el problema de hecho. Pero al hacer esto, las configuraciones de la plantilla se sobrepondrán a lo que está en el gráfico, y el problema es que el indicador de control será removido del gráfico.

Recuerda que el usuario no podrá colocar manualmente el indicador de control en el gráfico. Aunque existan formas de hacer esto. Pero vamos a considerar el hecho de que el usuario no sepa cómo trabajar en MetaTrader 5 y en MQL5, entonces no podrá reponer el indicador de control en el gráfico. Este tipo de situación es precisamente el tipo de falla donde el usuario es el responsable de generarla y el programador el encargado de corregirla.

Al modificar la plantilla, MetaTrader 5 informará a los programas que están en el gráfico de que hubo un cambio de plantilla, para que sepan lo que sucedió y puedan tomar algún tipo de acción al respecto. La forma en que MetaTrader 5 hace esto se puede ver en el fragmento a continuación:

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

Cuando la plantilla se modifica, MetaTrader 5 disparará un evento DeInit, que llamará al procedimiento visto anteriormente. Podemos verificar la condición que hizo que MetaTrader 5 disparara el evento DeInit, y si es el cambio de plantilla, se imprimirá un mensaje en el terminal.

Es decir, podemos saber si hubo un cambio de plantilla. Pero saber esto no hace que el indicador se reponga inmediatamente. Aquí tenemos que tomar una decisión. Forzar la finalización del servicio o forzar al servicio a reponer el indicador de control en el gráfico. Desde mi punto de vista, y esto es una opinión personal, lo que debemos hacer es forzar la finalización del servicio. La razón es simple. Si se permite al usuario configurar la plantilla que se utilizará en el gráfico que contendrá la repetición/simulación, ¿por qué deberíamos permitir que el usuario cambie manualmente la plantilla? No tiene sentido. Entonces, a mi parecer, lo correcto es simplemente finalizar el servicio y hacer que el usuario informe la plantilla en el momento en que el servicio se ponga en ejecución. De lo contrario, ¿cuál sería la necesidad de permitir al usuario informar la plantilla al servicio?

De esta forma, el código del indicador de control pasará por otra actualización simple, que puede verse en el fragmento a continuación:

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

Fragmento del código fuente del Indicador de control (Actualización)

Observa que en la línea 69 añadimos la prueba para verificar el motivo por el cual el indicador está siendo removido del gráfico. Si el motivo es el cambio de plantilla, la línea 70 imprimirá un mensaje en el terminal, y tendremos el mismo resultado que si el gráfico fuera cerrado por el usuario o el indicador fuera removido por el usuario. Es decir, el servicio se finalizará. Tal vez esta solución te parezca demasiado radical. Pero como expliqué, no tiene sentido hacer las cosas de otra manera.

A pesar de haber corregido una de las fallas, tenemos otro problema: El usuario puede simplemente cambiar algún parámetro que está siendo utilizado por el indicador de control y que fue pasado a él por el servicio. Recuerda que aún estamos montando el sistema, por lo que pueden surgir nuevos parámetros. Este tipo de situación es mucho más complicada de manejar. Pero seremos radicales en este tipo de enfoque también. La solución ya se implementó en el propio fragmento que puedes ver arriba.

Presta atención a la línea 71. Esta línea no existía antes. Pero se colocó allí justamente para evitar que el usuario modifique alguno de los parámetros que el servicio informó al indicador de control. Si esto ocurre, MetaTrader 5 generará un evento DeInit, pero el argumento será el cambio de parámetro. No informaremos de ningún tipo de fallo, no en este momento, ya que el fallo ocurrirá cuando el indicador sea recolocado por MetaTrader 5. Pero como puede suceder que algún usuario más astuto informe la ID de un gráfico válido, cerramos el gráfico de la repetición/simulador, en la línea 76. Así, cuando el servicio pruebe si el gráfico está abierto, recibirá un error, y para el servicio esto indica que debe finalizarse. De esta manera, corregimos este fallo también.


Conclusión

En este artículo, corregimos algunas fallas primarias que surgieron debido a que el indicador de control dejó de ser accesible para el usuario. Aunque sigue apareciendo en la ventana de indicadores mediante el atajo CTRL + I, ya no se lista entre otros indicadores que pueden usarse en cualquier gráfico. Este tipo de cambio está provocando muchas modificaciones en el código para hacerlo más estable y consistente y evitar que el usuario haga cosas que no deseamos o esperamos que sean hechas.

Aun así, seguimos teniendo la falla que dificulta la interacción entre el usuario y el indicador, para ajustar y manipular el indicador de control con ayuda del puntero del ratón. Pero esta falla se debe a algo que corregiremos pronto, lo que hará que el sistema sea aún más libre y se pueda utilizar de forma totalmente modular.

En el vídeo 03, justo abajo, puedes ver cómo se comporta el sistema ahora, con las actualizaciones descritas en este artículo. Sin embargo, como el código aún es inestable, no habrá anexos en este artículo.


Vídeo 03

En el próximo artículo, continuaremos resolviendo las cuestiones y problemas aún involucrados en esta interacción entre el usuario y el servicio de repetición/simulación.


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

Archivos adjuntos |
Anexo.zip (420.65 KB)
El tipo de dibujado DRAW_ARROW en indicadores de símbolo y periodo múltiple El tipo de dibujado DRAW_ARROW en indicadores de símbolo y periodo múltiple
En este artículo nos ocuparemos de dibujar los indicadores de símbolo y periodo múltiple. Asimismo, mejoraremos los métodos de la clase para representar correctamente las flechas que muestran los datos de los indicadores de flecha calculados sobre un símbolo/periodo que no se corresponde con el símbolo/periodo del gráfico actual.
Desarrollo de un sistema de repetición (Parte 49): Esto complica las cosas (I) Desarrollo de un sistema de repetición (Parte 49): Esto complica las cosas (I)
En este artículo complicaremos un poco las cosas. Utilizando lo que vimos en los artículos anteriores, comenzaremos a liberar el archivo de plantilla para que el usuario pueda utilizar una plantilla personalizada. Sin embargo, haré los cambios poco a poco, ya que también modificaré el indicador con el fin de reducir la carga de MetaTrader 5.
Introducción a MQL5 (Parte 3): Estudiamos los elementos básicos de MQL5 Introducción a MQL5 (Parte 3): Estudiamos los elementos básicos de MQL5
En este artículo, seguiremos estudiando los fundamentos de la programación MQL5. Hoy veremos los arrays, las funciones definidas por el usuario, los preprocesadores y el procesamiento de eventos. Para una mayor claridad, todos los pasos de cada explicación irán acompañado de un código. Esta serie de artículos sienta las bases para el aprendizaje de MQL5, prestando especial atención a la explicación de cada línea de código.
Desarrollo de un sistema de repetición (Parte 48): Conceptos que hay que entender y comprender Desarrollo de un sistema de repetición (Parte 48): Conceptos que hay que entender y comprender
¿Qué tal aprender algo nuevo? En este artículo, aprenderás cómo transformar scripts y servicios y por qué es útil hacerlo.