Português
preview
Simulación de mercado: La unión hace la fuerza (II)

Simulación de mercado: La unión hace la fuerza (II)

MetaTrader 5Probador |
57 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a un nuevo artículo de la serie dedicada a la construcción de un sistema de repetición/simulación.

En el artículo anterior, Simulación de mercado: La unión hace la fuerza (I), iniciamos la fase final del desarrollo del sistema de repetición/simulador. Allí expliqué la directiva que utilizaremos para conseguir que todas las aplicaciones desarrolladas funcionen tanto al conectarse al servidor real —ya sea mediante una cuenta de demostración o una cuenta real— como dentro del sistema de repetición/simulación. En este artículo simularemos el comportamiento de todo el proceso de trading.

Probablemente muchos duden de que el sistema pueda cumplir realmente lo que estoy anunciando. Sin embargo, comprobarás que será capaz de alcanzar ese objetivo. A partir de este momento, el desarrollo puede volverse considerablemente más complejo debido a lo que vamos a implementar. Por tanto, si no tienes mucha experiencia en programación con varios archivos o en el uso de distintos lenguajes dentro de un mismo programa, te recomiendo que leas con calma tanto este artículo como los anteriores y los que se publicarán después, porque el contenido será especialmente denso. No explicaré minuciosamente cada elemento que programemos, para evitar que tanto la redacción como la lectura del artículo resulten pesadas y agotadoras.

Aun así, mantendré un enfoque didáctico para que sea posible comprender lo que estamos haciendo. Pido disculpas a quienes estén comenzando a programar, pero, a partir de aquí, el desarrollo será mucho más dinámico. De lo contrario, el sistema tardaría demasiado en alcanzar su etapa final.


Preparativos para simular el servidor de trading

Lo primero que haremos será preparar la base de datos para poder insertar nuevos registros, actualizar los existentes y buscar registros específicos. Este es el punto de partida, ya que la base de datos permitirá la comunicación entre todas las aplicaciones que participarán en la simulación del servidor real. Presta atención a esta distinción: no estoy diciendo que vayamos a construir un servidor simulado. Lo que crearemos será un mecanismo capaz de simular lo que ocurriría en el servidor.

Este mecanismo nos permitirá realizar una simulación con posiciones y órdenes pendientes. Aunque todavía no he explicado cómo funcionará el sistema de órdenes pendientes, prácticamente ya está incluido en el propio código del indicador de posición. Solo es necesario adaptar el código, pero el funcionamiento del sistema de órdenes pendientes se abordará más adelante. Comencemos por crear la base de datos. La siguiente imagen muestra, básicamente, los datos que debemos almacenar en la base de datos.

Evidentemente, cada uno de estos datos corresponderá a una columna, y las columnas podrán distribuirse entre una o varias tablas. Sin embargo, con fines didácticos, puedo reunir todas las columnas en una sola tabla. Antes de reunir todos los datos en una sola tabla, estuve pensando si realmente sería conveniente preparar los valores dentro del Asesor Experto. La razón es que quiero impedir que el usuario envíe una orden de mercado mientras el sistema de repetición/simulador esté pausado, y el lugar más adecuado para comprobar si el sistema de repetición/simulador está pausado es el indicador de control.

No obstante, existe una posibilidad que quizá permita mantener el planteamiento original: leer el búfer del indicador de control para comprobar si el sistema está en modo de pausa o de reproducción. En cualquier caso, dejaré esta cuestión a criterio de cada uno. Si el usuario realmente quiere aprender a operar de manera más coherente, no enviará órdenes de mercado mientras el sistema de repetición/simulador esté pausado. El usuario enviará las órdenes de mercado cuando los datos se estén emitiendo y representando en el gráfico. Por tanto, mantendremos el plan original.

En el artículo anterior ya vimos la clase que dará soporte a la base de datos. Ahora solo debemos crear las sentencias SQL necesarias para crear las columnas. Recuerda que utilizaré la estructura más sencilla posible: una sola tabla y ninguna comprobación destinada a validar los datos. Así evitaremos prolongar innecesariamente esta fase final del desarrollo. Supondré que tú, estimado lector, no intentarás introducir datos no válidos en la base de datos. Comencemos, por tanto, a adaptar el código del Asesor Experto para que construya la base de datos cuando utilicemos el sistema de repetición/simulador. Para adaptar el Asesor Experto, debemos modificar el código original de acuerdo con el fragmento que aparece a continuación.

030. //+------------------------------------------------------------------+
031. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
032. {
033.     int handle;
034.     ulong ul;
035.     
036.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
037.     switch (id)
038.     {
039.         case CHARTEVENT_CHART_CHANGE:
040.             if (Terminal != NULL) break;
041.             else
042.             {            
043.                 Terminal = new C_Terminal(0, 0, user00);
044.                 if (_Symbol != def_SymbolReplay)
045.                 {
046.                     for (int count = PositionsTotal() - 1; count >= 0; count--)
047.                     {
048.                         ul = PositionGetTicket(count);
049.                         if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
050.                         {
051.                             ChartIndicatorDelete(0, 0, IntegerToString(ul));
052.                             continue;
053.                         }
054.                         handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
055.                         ChartIndicatorAdd(0, 0, handle);
056.                         IndicatorRelease(handle);
057.                     }
058.                 } else 
059.                 {
060. //... NEW CODE ...
061.                 }
062.             }
063.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
064.             EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
065.             break;
066.     }
067. }
068. //+------------------------------------------------------------------+
069. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
070. {
071.     if (Terminal == NULL) return;
072.     static ulong ticket = 0;
073.     switch (trans.type)
074.     {
075.         case TRADE_TRANSACTION_HISTORY_ADD:
076.             EventChartCustom(0, evUpdate_Position, trans.position, 0, "");
077.             ticket = (trans.order != trans.position ? trans.position : 0);
078.             break;
079.         case TRADE_TRANSACTION_REQUEST:
080.             if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
081.             {
082.                 case TRADE_ACTION_DEAL:
083.                     if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
084.                     else
085.                     {
086.                         int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
087.                         ChartIndicatorAdd(0, 0, handle);
088.                         IndicatorRelease(handle);
089.                     }
090.                     ticket = 0;
091.                     break;
092.                 case TRADE_ACTION_SLTP:
093.                     EventChartCustom(0, evUpdate_Position, request.position, 0, "");
094.                     break;
095.             }
096.             break;
097.     };
098. }
099. //+------------------------------------------------------------------+
100. void OnDeinit(const int reason)
101. {
102.     ulong ul;
103.     
104.     switch (reason)
105.     {
106.         case REASON_REMOVE:
107.         case REASON_INITFAILED:
108.             EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
109.             break;
110.     }
111.     if (Terminal != NULL)
112.     {
113.         if (_Symbol != def_SymbolReplay)
114.         {
115.             for (int count = PositionsTotal() - 1; count >= 0; count--)
116.             {
117.                 ul = PositionGetTicket(count);
118.                 if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
119.                 ChartIndicatorDelete(0, 0, IntegerToString(ul));
120.             }
121.         } else 
122.         {
123. // ... NEW CODE ...        
124.         }
125.     }
126.     delete Orders;
127.     delete Terminal;
128.     delete DB;
129. }
130. //+------------------------------------------------------------------+

Fragmento del Asesor Experto

Observa que estamos introduciendo algunos cambios menores en el código mostrado en el artículo anterior. Ahora presta atención. Existen dos puntos en los que se incorporará nuevo código al Asesor Experto. Estos dos puntos están en las líneas 60 y 123 del fragmento, para que puedas entenderlo. En la línea 60 añadiremos el código encargado de colocar el indicador de posición en el gráfico. Este código será equivalente al comprendido entre las líneas 46 y 57. En la línea 123 implementaremos el equivalente al código situado entre las líneas 115 y 119. Por tanto, el Asesor Experto sufrirá muy pocos cambios. El código del manejador de eventos OnTradeTransaction, en cambio, permanecerá intacto, tal como expliqué en el artículo anterior. Sin embargo, este manejador de eventos se utilizará ampliamente dentro del Asesor Experto, incluso cuando ejecutemos el Asesor Experto en el sistema de repetición/simulador. No te preocupes. Explicaré detalladamente cómo se utilizará el manejador de eventos OnTradeTransaction.

Muy bien. Visto así, parece que realmente vamos a implementar algo extremadamente complejo. Sin embargo, comprobarás que es mucho más sencillo de lo que probablemente imaginas ahora mismo. El procedimiento solo resultará algo más avanzado si ya tienes buenos conocimientos de MQL5 y SQL, pero no sabes cómo simular el servidor de trading utilizando únicamente MQL5 y SQL.

Ahora bien, si has prestado atención, quizá pienses que falta código o que no estamos aislando la clase C_Orders, como cabría esperar a partir de las explicaciones del artículo anterior. En un principio llegué a plantearme aislar la clase C_Orders. Sin embargo, aislar la clase C_Orders nos obligaría a implementar otra clase equivalente, lo que supondría un trabajo completamente innecesario. Podemos adaptar directamente C_Orders para que sea capaz de simular el servidor. Veamos, por tanto, qué modificaciones realizaremos en esta clase.

Para comenzar, y únicamente con fines de prueba, podemos implementar lo que aparece en el siguiente fragmento.

067. //+------------------------------------------------------------------+
068.         ulong SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult     TradeCheck;
071.                 MqlTradeResult          TradeResult;                
072.                 MqlTradeTransaction     TradeTrans;
073.                                 
074.                 ZeroMemory(TradeCheck);
075.                 ZeroMemory(TradeResult);
076.                 if (_Symbol == def_SymbolReplay)
077.                 {
078.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
079.                     m_Base.TradeRequest.symbol = _Symbol;
080.                     m_Base.TradeRequest.action = TRADE_ACTION_DEAL;
081.                     TradeResult.order = 2048;
082.                     TradeResult.retcode = TRADE_RETCODE_DONE;                    
083.                     OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
084.                     TradeTrans.type = TRADE_TRANSACTION_HISTORY_ADD;
085.                     TradeTrans.order = 0;
086.                     TradeTrans.position = TradeResult.order;
087.                     OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
088.                 } else {
089.                     if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
090.                     {
091.                         PrintFormat("Order System - Check Error: %d", GetLastError());
092.                         return 0;
093.                     }
094.                     m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
095.                 }
096.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
097.                 {
098.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
099.                     return 0;
100.                 };
101.                 
102.                 return TradeResult.order;
103.             }
104. //+------------------------------------------------------------------+

Fragmento de C_Orders

Aquí es donde el proceso empieza a resultar cada vez más interesante. Recuerda que este código solo sirve para probar lo que realmente vamos a implementar. Observa que en la línea 72 añadimos una nueva estructura definida por MQL5. Después, en la línea 76, comprobamos si estamos utilizando el sistema de repetición/simulador mediante el nombre del símbolo. Si utilizamos un símbolo que probablemente esté registrado en el servidor de trading real, pasamos a la línea 89 y el código continúa con el funcionamiento que ya conocemos. En cambio, si utilizamos el símbolo del sistema de repetición/simulador, simularemos una respuesta del servidor. Este proceso se realiza en dos etapas. En la primera simulamos la respuesta correspondiente a la orden de mercado para abrir una posición. Una vez configurados los datos, llamamos por primera vez al manejador de eventos OnTradeTransaction, como se muestra en la línea 83. Después enviamos otra orden de mercado en la línea 87. En ambos casos se ejecutará el manejador de eventos OnTradeTransaction del Asesor Experto, que intentará colocar el indicador de posición en el gráfico.

Este mecanismo funcionará. Sin embargo, como el indicador de posición todavía no dispone de ningún lugar del que obtener los datos de la posición, terminará eliminándose del gráfico. Puedes añadir algunos mensajes al código para seguir este flujo. Dejo la incorporación de estos mensajes como ejercicio para ti, estimado lector. Así podrás comprender todos los pasos del proceso: desde el momento en que utilizamos el indicador de mouse para interactuar con los botones de compra o venta del indicador Chart Trade, pasando por el envío del mensaje del indicador Chart Trade al Asesor Experto, hasta que el Asesor Experto utiliza la clase C_Orders para simular la apertura de una posición.

Cuando esto ocurra, el Asesor Experto solicitará inmediatamente a MetaTrader 5 que cargue el indicador de posición. Una vez añadido al gráfico, el indicador intentará localizar la posición que la clase C_Orders está simulando. Sin embargo, como todavía no utilizamos SQL para proporcionar esos datos, no encontrará los datos necesarios y se eliminará del gráfico. Esta es, precisamente, la belleza de la programación. Qué maravilla.

Aunque el fragmento anterior solo nos permite realizar una única transacción, ya constituye un buen punto de partida, sobre todo teniendo en cuenta la sencillez del enfoque utilizado. Llegados a este punto, quizá te preguntes por qué solo podemos simular una transacción. Es una buena pregunta. Para comprender por qué solo podemos simular una transacción, examinemos el código del manejador de eventos OnTradeTransaction, que aparece en el fragmento anterior. Observa que, cuando la línea 83 del código de SendToPhysicalServer se ejecute por segunda vez, la comprobación de la línea 83 del manejador de eventos OnTradeTransaction se cumplirá. El cumplimiento de esta comprobación impedirá que se ejecute la línea 86, que intentaría colocar el indicador de posición en el gráfico.

Pero espera un momento. Este sistema funciona con una cuenta HEDGING cuando está conectado al servidor real. ¿Por qué no funciona ahora, cuando intentamos simular el servidor? El problema radica en el orden en que enviamos los eventos de simulación. Observa que, en la función SendToPhysicalServer, primero enviamos un evento TRADE_TRANSACTION_REQUEST y después uno de tipo TRADE_TRANSACTION_HISTORY_ADD. Esta secuencia es incorrecta. He utilizado intencionadamente esta secuencia incorrecta para mostrarte que, a partir de ahora, deberás extremar la atención al implementar cada paso.

El orden en que se envían los eventos influirá necesariamente en el comportamiento del código. Aunque el código sea completamente correcto, no funcionará como imaginas y convertirá algo posible en algo imposible. Por eso indiqué en el artículo anterior que, a partir de este punto, cada nuevo paso será considerablemente más exigente. En cualquier caso, esta modificación de la clase C_Orders solo pretendía demostrar cómo funcionaría el mecanismo.

El resultado puede verse en la siguiente imagen.

En la zona delimitada de la imagen anterior puedes observar el intento de simular el servidor. Como se aprecia, el proceso funcionó de la forma esperada y confirmó que es posible simular el comportamiento del servidor real. Solo necesitamos modelar la forma de actuar del servidor. Sin embargo, antes de continuar, comenzaremos a utilizar SQL, ya que SQL es necesario para simular el servidor real. Para separar ambos aspectos, pasemos a un nuevo apartado.


Modelar la base de datos

Modelar una base de datos puede resultar muy interesante e incluso relajante, porque cambiamos momentáneamente el enfoque y nos dedicamos a una tarea especialmente entretenida: programar en SQL. Como indiqué al principio, no explicaré cómo diseñar una estructura compleja en SQL. Utilizaré el modelo más básico posible, pero será suficiente para lo que necesitamos. Comencemos realizando los siguientes cambios en el código.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. #include "..\SQL\C_ReplayDataBase.mqh"
006. //+------------------------------------------------------------------+
007. class C_Orders : public C_ReplayDataBase
008. {
                                   .
                                   .
                                   .
164. //+------------------------------------------------------------------+    
165.     public    :
166. //+------------------------------------------------------------------+
167.         C_Orders(const ulong magic)
168.             :C_ReplayDataBase()
169.             {
170.                 m_Base.MagicNumber = magic;
171.                 ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
172.             }
173. //+------------------------------------------------------------------+ 

Fragmento de C_Orders

Observa que en la línea 5 añadí el archivo de cabecera de la clase C_ReplayDataBase. En la línea 7 indicamos que la clase C_Orders heredará públicamente de C_ReplayDataBase. De este modo, C_Orders incorporará nuevas funcionalidades. Para que todo funcione correctamente, en la línea 168 inicializamos la clase C_ReplayDataBase dentro del constructor de C_Orders. Así necesitaremos menos código en el Asesor Experto y, aun así, podremos implementar lo necesario para simular el servidor de trading. Muy bien, pero necesitamos una tabla en la base de datos. Mediante la instrucción de la línea 171 creamos la tabla que utilizaremos en la simulación. Ahora el sistema empieza a tomar forma.

El siguiente paso consiste en simular el comportamiento descrito en el apartado anterior, pero almacenando ya los datos en la base de datos. Para implementar esta simulación con almacenamiento en la base de datos, añadimos una nueva función a la clase C_Orders.

041. //+------------------------------------------------------------------+
042.         struct stChartTrade
043.         {
044.             struct stEvent
045.             {
046.                 EnumEvents  ev;
047.                 string      szSymbol,
048.                             szContract;
049.                 bool        IsDayTrade;
050.                 ushort      Leverange;
051.                 double      PointsTake,
052.                             PointsStop;
053.             }Data;
054. //---
055.             bool Decode(const EnumEvents ev, const string sparam)
056.                 {
057.                     string Res[];
058.         
059.                     if (StringSplit(sparam, '?', Res) != 7) return false;
060.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
061.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
062.                     else return false;
063.                     
064.                     return true;
065.                 }
066. //---
067.         }m_ChartTrade;
068. //+------------------------------------------------------------------+
069.         ulong SimulateServer(void)
070.         {
071.             MqlTradeResult        TradeResult;
072.             MqlTradeTransaction   TradeTrans;
073.             
074.             ZeroMemory(TradeResult);
075.             ZeroMemory(TradeTrans);
076.             TradeTrans.type = TRADE_TRANSACTION_HISTORY_ADD;
077.             TradeTrans.order = 0;
078.             TradeTrans.position = TradeResult.order;
079.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
080.             TradeTrans.type = TRADE_TRANSACTION_REQUEST;
081.             m_Base.TradeRequest.symbol = _Symbol;
082.             m_Base.TradeRequest.action = TRADE_ACTION_DEAL;
083.             TradeResult.order = 2048;
084.             TradeResult.retcode = TRADE_RETCODE_DONE;                    
085.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
086.             
087.             return TradeResult.order;
088.         }
089. //+------------------------------------------------------------------+
090.         ulong SendToPhysicalServer(void)
091.         {
092.             MqlTradeCheckResult   TradeCheck;
093.             MqlTradeResult        TradeResult;
094.                                 
095.             ZeroMemory(TradeCheck);
096.             ZeroMemory(TradeResult);
097.             if (_Symbol == def_SymbolReplay)
098.                 return SimulateServer();
099.             if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
100.             {
101.                 PrintFormat("Order System - Check Error: %d", GetLastError());
102.                 return 0;
103.             }
104.             m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
105.             if (TradeResult.retcode != TRADE_RETCODE_DONE)
106.             {
107.                 PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
108.                 return 0;
109.             };
110.             
111.             return TradeResult.order;
112.         }
113. //+------------------------------------------------------------------+    

Fragmento de C_Orders

Observa que, en esta función SimulateServer, donde realizaremos toda la lógica de simulación, el orden de los eventos ya es correcto. Por tanto, podemos enviar varias órdenes de compra o venta a mercado mediante el indicador Chart Trade. Se trata del mismo código que vimos antes. En la línea 97 comprobamos si estamos utilizando el sistema de repetición. Si es así, en la línea 98 llamamos al simulador del servidor. A partir de ahora centraremos la atención exclusivamente en esta nueva función. Por el momento, no te preocupes por cómo funcionará todo en el Asesor Experto o en el indicador de posición. Primero debemos dotar a nuestro servidor simulado de alguna funcionalidad realmente práctica.


Comenzar a simular el servidor

Antes de implementar lo que anuncia el título de este apartado, debes comprender qué tendremos que hacer. Simular el servidor será una de las tareas más interesantes de esta serie de artículos, porque te permitirá comprender muchos otros aspectos relacionados con el servidor real. Lo primero que debes saber es que las solicitudes que simularemos ya habrán sido creadas por las demás funciones y procedimientos de la clase C_Orders y estarán almacenadas en la estructura m_Base.TradeRequest.

Por tanto, solo tendremos que interpretar correctamente el contenido de esa estructura. Recuerda que no comprobaré si los datos son válidos. Comprobar la validez de los datos equivaldría a ejecutar OrderCheck, una función de MQL5. Por este motivo, procura no introducir datos incorrectos en m_Base.TradeRequest, ya que el sistema podría devolverte datos erróneos.

Básicamente, tendremos que simular dos tipos de solicitud: TRADE_ACTION_DEAL y TRADE_ACTION_SLTP, que son los únicos utilizados en el código de C_Orders. Por eso fue importante desarrollar primero todo el sistema conectado al servidor real. De este modo, en esta etapa no perderemos tiempo implementando solicitudes innecesarias. Con estos dos tipos de solicitud podremos comprar y vender a mercado, además de cerrar una posición o modificar los niveles de take profit y stop loss.

Probablemente muchos omitirían la etapa de desarrollar el sistema conectado al servidor real para pasar directamente a simular las órdenes y las posiciones. Sin embargo, esto nos traería muchas más dificultades que ventajas. Del mismo modo, ahora implementaremos primero el sistema de compra y venta a mercado, antes de desarrollar las órdenes pendientes. Una vez completadas las operaciones a mercado, implementar las órdenes pendientes será muy sencillo. Un verdadero juego de niños.

Para comenzar la simulación, empezaremos por la operación más sencilla: implementar la simulación de la solicitud TRADE_ACTION_SLTP. La elección de esta operación se debe a que la solicitud es completamente independiente del tipo de cuenta que se esté simulando o, mejor dicho, del símbolo que estemos manipulando en el sistema de repetición/simulador. Básicamente, la solicitud TRADE_ACTION_SLTP tiene un único propósito: cambiar el valor del stop loss o del take profit. Nada más. Como la propia solicitud nos indica qué datos deben utilizarse, solo tenemos que ejecutar una actualización en la base de datos. No tiene mucho misterio. Sin embargo, aquí no simularemos todos los mensajes disparados por el servidor. Simplificaremos las cosas al máximo. Si deseas simular realmente todos los mensajes, podrás implementar la simulación completa de los mensajes. Pero, como el propósito de los artículos es didáctico, realizaremos la simulación de la forma más sencilla posible. Entonces, el nuevo código se muestra en el siguiente fragmento.

068. //+------------------------------------------------------------------+
069.         ulong SimulateServer(void)
070.         {
071.             MqlTradeResult        TradeResult;
072.             MqlTradeTransaction   TradeTrans;
073.             bool                  bResult = false;
074.             
075.             ZeroMemory(TradeResult);
076.             ZeroMemory(TradeTrans);
077.             
078.             switch (m_Base.TradeRequest.action)
079.             {
080.                 case TRADE_ACTION_SLTP:
081.                     bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 
082.                                                                         m_Base.TradeRequest.sl,
083.                                                                         m_Base.TradeRequest.tp,
084.                                                                         m_Base.TradeRequest.position));
085.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
086.                     TradeResult.retcode = TRADE_RETCODE_DONE;
087.                     if (bResult)
088.                     {
089.                         OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
090.                         return m_Base.TradeRequest.position;
091.                     }
092.                     break;
093.                 case TRADE_ACTION_DEAL:
094.                     break;
095.             }
096.             TradeResult.retcode = TRADE_RETCODE_INVALID;
097.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
098.             return 0;                
099.         }
100. //+------------------------------------------------------------------+

Fragmento de C_Orders

Observa que todo es muy sencillo y directo. La línea 80 contiene el evento que capturaremos. En la línea 81 efectuamos la operación en la base de datos. Si tenemos éxito, en la línea 89 simulamos en MetaTrader 5 el efecto de la operación realizada en la base de datos como respuesta del servidor. Asimismo, si no tenemos éxito debido a algún fallo en la base de datos, en la línea 97 simulamos el mismo tipo de respuesta procedente del servidor.

Sin embargo, hay que recordar una cosa. Aunque el código anterior simula, de forma muy resumida, lo que haría el servidor, en este momento la clase C_Orders no podrá construir la solicitud TRADE_ACTION_SLTP. La clase C_Orders todavía está conectada al servidor de trading real. Recuerda que las pruebas mostradas anteriormente simulan la apertura o el cierre de una posición. Para poder procesar adecuadamente la solicitud TRADE_ACTION_SLTP, tendremos que modificar el procedimiento ModifyValueSLTP de la clase C_Orders. Bien, antes de continuar, veamos el código del procedimiento ModifyValueSLTP. Se muestra en el siguiente fragmento.

129. //+------------------------------------------------------------------+    
130.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
131.         {
132.             ZeroMemory(m_Base.TradeRequest);            
133.             MqlTradeRequest TradeRequest[1];
134. 
135.             if ((sl < 0) || (tp < 0)) if (!PositionSelectByTicket(ticket)) return;
136.             m_Base.TradeRequest.magic      = m_Base.MagicNumber;
137.             m_Base.TradeRequest.action     = TRADE_ACTION_SLTP;
138.             m_Base.TradeRequest.symbol     = symbol;
139.             m_Base.TradeRequest.position   = ticket;
140.             m_Base.TradeRequest.sl         = (sl < 0 ? PositionGetDouble(POSITION_SL) : sl);
141.             m_Base.TradeRequest.tp         = (tp < 0 ? PositionGetDouble(POSITION_TP) : tp);
142.                 
143.             TradeRequest[0] = m_Base.TradeRequest;
144.             ArrayPrint(TradeRequest);
145.                 
146.             SendToPhysicalServer();
147.         }
148. //+------------------------------------------------------------------+    

Fragmento de C_Orders

Observa que aquí tenemos algunos problemas. Es el mismo tipo de problema que tendremos que afrontar en el código del indicador de posición, es decir, las llamadas a la biblioteca de MQL5 para leer los datos de la posición. Entonces, adoptaremos un enfoque un poco diferente. Aunque tengamos que cambiar algunas cosas en el código. De este modo, creamos un nuevo archivo de cabecera. Se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\Defines.mqh"
05. #include "..\SQL\C_ReplayDataBase.mqh"
06. //+------------------------------------------------------------------+
07. class C_InServer : public C_ReplayDataBase
08. {
09.     private   :
10.         bool    m_IsReplay;
11.     public    :
12. //+------------------------------------------------------------------+
13.         C_InServer()
14.             :C_ReplayDataBase(),
15.             m_IsReplay(_Symbol == def_SymbolReplay)
16.         {
17.             ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
18.         }
19. //+------------------------------------------------------------------+
20.     inline const double _PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE arg)
21.     {
22.         if (!m_IsReplay)
23.             return PositionGetDouble(arg);
24.         
25.         return 0;
26.     }
27. //+------------------------------------------------------------------+
28.     inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg)
29.     {
30.         if (!m_IsReplay)
31.             return PositionGetInteger(arg);
32.             
33.         return 0;
34.     }
35. //+------------------------------------------------------------------+
36.     inline const bool _PositionSelectByTicket(ulong arg)
37.     {
38.         if (!m_IsReplay)
39.             return PositionSelectByTicket(arg);
40.             
41.         return false;
42.     }
43. //+------------------------------------------------------------------+
44.     inline const string _PositionGetString(ENUM_POSITION_PROPERTY_STRING arg)
45.     {
46.         if (!m_IsReplay)
47.             return PositionGetString(arg);
48.             
49.         return "";
50.     }
51. //+------------------------------------------------------------------+
52.     inline const string _PositionGetSymbol(int arg)
53.     {
54.         return (m_IsReplay ? def_SymbolReplay : PositionGetSymbol(arg));
55.     }
56. //+------------------------------------------------------------------+
57.     inline const int _PositionsTotal(void)
58.     {
59.         if (!m_IsReplay)
60.             return PositionsTotal();
61.             
62.         return 0;
63.     }
64. //+------------------------------------------------------------------+
65. };
66. //+------------------------------------------------------------------+

C_InServer

Ahora resolveremos dos problemas de una sola vez. Al solucionar estas cuestiones en la clase C_Orders, también resolveremos los mismos problemas en el indicador de posición. Por tanto, debemos actualizar el código de la clase C_Orders como se muestra a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_InServer.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders : public C_InServer
007. {
008.     protected:
009. //+------------------------------------------------------------------+
010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; }
011. //+------------------------------------------------------------------+
012.         bool ClosePosition(const ulong ticket)
013.         {
014.             bool     IsBuy;
015.             string szContract;
016.                 
017.             if    (!_PositionSelectByTicket(ticket)) return false;
018.             IsBuy = _PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
019.             szContract = _PositionGetString(POSITION_SYMBOL);
020.             ZeroMemory(m_Base.TradeRequest);
021.             m_Base.TradeRequest.action      = TRADE_ACTION_DEAL;
022.             m_Base.TradeRequest.type        = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
023.             m_Base.TradeRequest.price       = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
024.             m_Base.TradeRequest.position    = ticket;
025.             m_Base.TradeRequest.symbol      = szContract;
026.             m_Base.TradeRequest.volume      = _PositionGetDouble(POSITION_VOLUME);
027.             m_Base.TradeRequest.deviation   = 1000;
028.                 
029.             return SendToPhysicalServer() != 0;
030.         };
031. //+------------------------------------------------------------------+    
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stBase
035.         {
036.             MqlTradeRequest TradeRequest;
037.             ulong           MagicNumber;
038.             bool            bTrash;
039.         }m_Base;
040. //+------------------------------------------------------------------+
041.         struct stChartTrade
042.         {
043.             struct stEvent
044.             {
045.                 EnumEvents  ev;
046.                 string      szSymbol,
047.                             szContract;
048.                 bool        IsDayTrade;
049.                 ushort      Leverange;
050.                 double      PointsTake,
051.                             PointsStop;
052.             }Data;
053. //---
054.             bool Decode(const EnumEvents ev, const string sparam)
055.                 {
056.                     string Res[];
057.         
058.                     if (StringSplit(sparam, '?', Res) != 7) return false;
059.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
060.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
061.                     else return false;
062.                     
063.                     return true;
064.                 }
065. //---
066.         }m_ChartTrade;
067. //+------------------------------------------------------------------+
068.         ulong SimulateServer(void)
069.         {
070.             MqlTradeResult        TradeResult;
071.             MqlTradeTransaction   TradeTrans;
072.             bool bResult = false;
073.             
074.             ZeroMemory(TradeResult);
075.             ZeroMemory(TradeTrans);
076.             
077.             switch (m_Base.TradeRequest.action)
078.             {
079.                 case TRADE_ACTION_SLTP:
080.                     bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 
081.                                                                         m_Base.TradeRequest.sl,
082.                                                                         m_Base.TradeRequest.tp,
083.                                                                         m_Base.TradeRequest.position));
084.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
085.                     TradeResult.retcode = TRADE_RETCODE_DONE;
086.                     if (bResult)
087.                     {
088.                         OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
089.                         return m_Base.TradeRequest.position;
090.                     }
091.                     break;
092.                 case TRADE_ACTION_DEAL:
093.                     break;
094.             }
095.             TradeResult.retcode = TRADE_RETCODE_INVALID;
096.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
097.             return 0;                
098.         }
099. //+------------------------------------------------------------------+
100.         ulong SendToPhysicalServer(void)
101.         {
102.             MqlTradeCheckResult   TradeCheck;
103.             MqlTradeResult        TradeResult;
104.                                 
105.             ZeroMemory(TradeCheck);
106.             ZeroMemory(TradeResult);
107.             if (_Symbol == def_SymbolReplay)
108.                 return SimulateServer();
109.             if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
110.             {
111.                 PrintFormat("Order System - Check Error: %d", GetLastError());
112.                 return 0;
113.             }
114.             m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
115.             if (TradeResult.retcode != TRADE_RETCODE_DONE)
116.             {
117.                 PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
118.                 return 0;
119.             };
120.             
121.             return TradeResult.order;
122.         }
123. //+------------------------------------------------------------------+    
124.         ulong ToMarket(const ENUM_ORDER_TYPE type)
125.         {
126.             double price     = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
127.             double vol       = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
128.             uchar  nDigit    = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
129.             
130.             ZeroMemory(m_Base.TradeRequest);
131.             m_Base.TradeRequest.magic         = m_Base.MagicNumber;
132.             m_Base.TradeRequest.symbol        = m_ChartTrade.Data.szContract;
133.             m_Base.TradeRequest.price         = NormalizeDouble(price, nDigit);
134.             m_Base.TradeRequest.action        = TRADE_ACTION_DEAL;
135.             m_Base.TradeRequest.sl            = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
136.             m_Base.TradeRequest.tp            = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
137.             m_Base.TradeRequest.volume        = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
138.             m_Base.TradeRequest.type          = type;
139.             m_Base.TradeRequest.type_time     = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
140.             m_Base.TradeRequest.stoplimit     = 0;
141.             m_Base.TradeRequest.expiration    = 0;
142.             m_Base.TradeRequest.type_filling  = ORDER_FILLING_RETURN;
143.             m_Base.TradeRequest.deviation     = 1000;
144.             m_Base.TradeRequest.comment       = "Order Generated by Experts Advisor.";
145. 
146.             MqlTradeRequest TradeRequest[1];
147. 
148.             TradeRequest[0] = m_Base.TradeRequest;
149.             ArrayPrint(TradeRequest);
150. 
151.             return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
152.         };
153. //+------------------------------------------------------------------+
154.         void CloseAllsPosition(void)
155.         {
156.             for (int count = _PositionsTotal() - 1; count >= 0; count--)
157.             {
158.                 if (_PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
159.                 if (_PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
160.                 ClosePosition(_PositionGetInteger(POSITION_TICKET));
161.             }
162.         };
163. //+------------------------------------------------------------------+    
164.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
165.         {
166.             ZeroMemory(m_Base.TradeRequest);            
167.             MqlTradeRequest TradeRequest[1];
168. 
169.             if ((sl < 0) || (tp < 0)) if (!_PositionSelectByTicket(ticket)) return;
170.             m_Base.TradeRequest.magic      = m_Base.MagicNumber;
171.             m_Base.TradeRequest.action     = TRADE_ACTION_SLTP;
172.             m_Base.TradeRequest.symbol     = symbol;
173.             m_Base.TradeRequest.position   = ticket;
174.             m_Base.TradeRequest.sl         = (sl < 0 ? _PositionGetDouble(POSITION_SL) : sl);
175.             m_Base.TradeRequest.tp         = (tp < 0 ? _PositionGetDouble(POSITION_TP) : tp);
176.                 
177.             TradeRequest[0] = m_Base.TradeRequest;
178.             ArrayPrint(TradeRequest);
179.                 
180.             SendToPhysicalServer();
181.         }
182. //+------------------------------------------------------------------+    
183.     public    :
184. //+------------------------------------------------------------------+
185.         C_Orders(const ulong magic)
186.             :C_InServer()
187.         {
188.             m_Base.MagicNumber = magic;
189.         }
190. //+------------------------------------------------------------------+    
191.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
192.         {
193.             switch (id)
194.             {
195.                 case CHARTEVENT_CUSTOM + evChartTradeBuy     :
196.                 case CHARTEVENT_CUSTOM + evChartTradeSell    :
197.                 case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
198.                     if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
199.                     {
200.                         case evChartTradeBuy:
201.                             ToMarket(ORDER_TYPE_BUY);
202.                             break;
203.                         case evChartTradeSell:
204.                             ToMarket(ORDER_TYPE_SELL);
205.                             break;
206.                         case evChartTradeCloseAll:
207.                             CloseAllsPosition();
208.                             break;
209.                     }
210.                     break;
211.                 case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
212.                     ClosePosition((ulong)(lparam));
213.                     break;
214.                 case CHARTEVENT_CUSTOM + evMsgCloseTakeProfit:
215.                     ModifyValueSLTP((ulong)(lparam), sparam, dparam, 0);
216.                     break;
217.                 case CHARTEVENT_CUSTOM + evMsgCloseStopLoss:
218.                     ModifyValueSLTP((ulong)(lparam), sparam, 0, dparam);
219.                     break;
220.                 case CHARTEVENT_CUSTOM + evMsgNewTakeProfit:
221.                     ModifyValueSLTP((ulong)(lparam), sparam, -1, dparam);
222.                     break;
223.                 case CHARTEVENT_CUSTOM + evMsgNewStopLoss:
224.                     ModifyValueSLTP((ulong)(lparam), sparam, dparam, -1);
225.                     break;
226.             }
227.         }
228. //+------------------------------------------------------------------+    
229. };
230. //+------------------------------------------------------------------+

C_Orders

Sé que lo que estoy haciendo, y la forma en que lo estoy haciendo, puede parecer una auténtica locura. Sin embargo, quiero demostrar que no existe ninguna caja mágica: podemos crear e implementar cualquier cosa que imaginemos. Todo depende de comprender correctamente lo que queremos conseguir. Precisamente por eso programar resulta tan divertido. Siempre tenemos problemas que resolver. Presta mucha atención al código, porque no explicaré en detalle dónde se realizaron los cambios. Con estas modificaciones, la clase C_Orders queda finalmente actualizada. Volvamos ahora al código del archivo de cabecera C_InServer.mqh para implementar las partes que se utilizarán en el sistema de repetición/simulador. De este modo podremos simular el servidor de trading, al menos para el sistema de órdenes a mercado, que es nuestro objetivo actual.


Simular el servidor de trading

Aquí solo presentaré algunos de los aspectos que analizaremos con mayor profundidad en los próximos artículos. La razón es que este artículo ya contiene bastante información para que tú, estimado lector, puedas asimilarla adecuadamente. Veamos, por tanto, qué aspecto tendrá la clase C_InServer. Comenzaremos por la parte más sencilla y examinaremos cada elemento por separado para que puedas seguir el proceso. El primer fragmento aparece a continuación.

56. //+------------------------------------------------------------------+
57.     inline const int _PositionsTotal(void)
58.     {
59. #define macro_ERROR { Print("Error in accessing the database..."); return 0; }
60.         
61.         struct stLocal
62.         {
63.             int value;
64.         }Info;
65.         
66.         if (!m_IsReplay)
67.             return PositionsTotal();
68. 
69.         if (!ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR
70.         if (!GetRegisterOfRequest(Info)) macro_ERROR
71.             
72.         return Info.value;
73.         
74. #undef macro_ERROR
75.     }
76. //+------------------------------------------------------------------+

Fragmento de C_InServer

Observa lo que hago en este fragmento. En la línea 59 defino una macro que se utilizará exclusivamente aquí. Cada una de las funciones de la clase C_InServer seguirá un patrón muy parecido, con pequeñas variaciones según el tipo de valor de retorno. Sin embargo, la macro es únicamente local, por lo que eliminamos la macro en la línea 74. Observa ahora la línea 61, donde creamos una estructura. Utilizaremos esta estructura para acceder a la base de datos y determinar cuántas posiciones permanecen abiertas, simulando así la función PositionsTotal de la biblioteca de MQL5.

Para realizar el recuento utilizamos la línea 69. Si todo se ejecuta correctamente, en la línea 70 intentamos obtener el valor de retorno de la consulta SQL. Si esta operación también se completa sin problemas, en la línea 72 devolvemos el resultado al autor de llamada. De este modo, el autor de llamada recibirá el valor correspondiente. Desde la perspectiva del autor de llamada, el servidor real y el servidor simulado serán prácticamente idénticos, ya que la base de datos suministrará los datos que normalmente obtendríamos del servidor.

Este mismo planteamiento se repetirá a lo largo de toda la clase C_InServer. Así que, para satisfacción de todos, a continuación se muestra el código completo de la clase C_InServer. Queda como ejercicio estudiar cómo funciona cada una de sus funciones.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. #include "..\SQL\C_ReplayDataBase.mqh"
006. //+------------------------------------------------------------------+
007. class C_InServer : public C_ReplayDataBase
008. {
009.     private    :
010.         bool    m_IsReplay;
011.         struct stLocal
012.         {
013.             ulong   ticket;
014.             int     type;
015.             double  volume,
016.                     price,
017.                     sl,
018.                     tp;
019.         }m_Info;
020.     public    :
021. //+------------------------------------------------------------------+
022.         C_InServer()
023.             :C_ReplayDataBase(),
024.             m_IsReplay(_Symbol == def_SymbolReplay)
025.         {
026.             ZeroMemory(m_Info);
027.             ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
028.         }
029. //+------------------------------------------------------------------+
030.     inline const double _PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE arg)
031.     {    
032.         if (!m_IsReplay)
033.             return PositionGetDouble(arg);
034.             
035.         switch (arg)
036.         {
037.             case POSITION_VOLUME        : return m_Info.volume;
038.             case POSITION_PRICE_OPEN    : return m_Info.price;
039.             case POSITION_SL            : return m_Info.sl;
040.             case POSITION_TP            : return m_Info.tp;
041.             case POSITION_PRICE_CURRENT : break;
042.             case POSITION_SWAP          : break;
043.             case POSITION_PROFIT        : break;
044.         }
045.         
046.         return 0;
047.     }
048. //+------------------------------------------------------------------+
049.     inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg)
050.     {
051.         if (!m_IsReplay)
052.             return PositionGetInteger(arg);
053.             
054.         switch (arg)
055.         {
056.             case POSITION_TICKET            : return (long) m_Info.ticket;
057.             case POSITION_TIME              : break;
058.             case POSITION_TIME_MSC          : break;
059.             case POSITION_TIME_UPDATE       : break;
060.             case POSITION_TIME_UPDATE_MSC   : break;
061.             case POSITION_TYPE              : return (long) m_Info.type;
062.             case POSITION_MAGIC             : break;
063.             case POSITION_IDENTIFIER        : break;
064.         }
065.         
066.         return 0;
067.     }
068. //+------------------------------------------------------------------+
069.     inline const bool _PositionSelectByTicket(ulong arg)
070.     {
071.         bool ret;
072.         
073.         if (!m_IsReplay)
074.             return PositionSelectByTicket(arg);
075. 
076.         ZeroMemory(m_Info);        
077.         if (!ExecRequestOfData(StringFormat("SELECT * FROM tb_Replay AS tb WHERE (tb.history = 0) AND (tb.ticket = %d);", arg)))
078.         {
079.              Print("Error in accessing the database...");
080.              return false;
081.          }
082.         
083.         ret = GetRegisterOfRequest(m_Info);
084. 
085.         return ret;
086.     }
087. //+------------------------------------------------------------------+
088.     inline const string _PositionGetString(ENUM_POSITION_PROPERTY_STRING arg)
089.     {
090.         if (!m_IsReplay)
091.             return PositionGetString(arg);
092.             
093.         switch (arg)
094.         {
095.             case POSITION_SYMBOL     : return def_SymbolReplay;
096.             case POSITION_COMMENT    : break;
097.             case POSITION_EXTERNAL_ID: break;
098.         }
099.         
100.         return "";
101.     }
102. //+------------------------------------------------------------------+
103.     inline const string _PositionGetSymbol(int arg)
104.     {
105.         return (!m_IsReplay ? PositionGetSymbol(arg) : (_PositionGetTicket(arg) > 0 ? def_SymbolReplay : ""));
106.     }
107. //+------------------------------------------------------------------+
108.     inline const int _PositionsTotal(void)
109.     {
110. #define macro_ERROR { Print("Error in accessing the database..."); return 0; }
111.         
112.         struct stLocal
113.         {
114.             int value;
115.         }Info;
116.         
117.         if (!m_IsReplay)
118.             return PositionsTotal();
119. 
120.         if (!ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR
121.         if (!GetRegisterOfRequest(Info)) macro_ERROR
122.             
123.         return Info.value;
124.         
125. #undef macro_ERROR
126.     }
127. //+------------------------------------------------------------------+
128. };
129. //+------------------------------------------------------------------+

C_InServer

Aunque este código pueda parecer bastante extraño y confuso, hace exactamente lo que quienes ya desarrollan Asesores Expertos conocen y utilizan habitualmente. Es decir, recurrimos a una función de la biblioteca de MQL5 para obtener los datos más recientes de una posición determinada. Durante esta consulta podemos comprobar si el ticket de posición es válido. Cuando el ticket de posición es válido, todos los datos de la posición se cargan en algún tipo de array. De este modo, MetaTrader 5 puede proporcionarnos los datos con la mayor rapidez posible, evitando o, al menos, reduciendo el exceso de consultas al servidor de trading.

Es mucho más rápido obtener los datos una sola vez y, mientras esos datos no cambien, recuperar los datos directamente de la memoria. Algunos ejemplos de datos que varían con poca frecuencia son el volumen negociado y los niveles de take profit y stop loss. Sin embargo, el sistema mostrado anteriormente solo almacena un bloque. Aunque no puedo afirmar con certeza cuántos bloques almacena MetaTrader 5, creo que debe almacenar varios bloques, ya que podemos mantener posiciones abiertas en distintos símbolos. En nuestro caso solo utilizaremos un bloque: el correspondiente al símbolo definido para el sistema de repetición/simulador. Por tanto, no veo ningún inconveniente en emplear un único bloque de memoria. Además, antes de realizar cualquier operación, incluso los Asesores Expertos deberían comprobar si el ticket de posición almacenado continúa siendo válido.


Consideraciones finales

En este artículo he mostrado cómo podemos comenzar a simular el servidor de trading. Hasta ahora, la aplicación desarrollada a lo largo de esta serie de artículos se centraba exclusivamente en simular la parte gráfica. Sin embargo, para disponer de un sistema más completo, en el que podamos probar un Asesor Experto dentro del servicio de repetición/simulador, también debemos simular el servidor de trading. Como habrás observado, la simulación incluirá únicamente los elementos imprescindibles. No obstante, tú, estimado lector, podrás completar las partes que falten. Como esos componentes adicionales no influyen en lo que pretendo mostrar, ya contamos con más que suficiente para continuar en el próximo artículo. Por tanto, estudia este contenido con calma y comprende cómo funciona realmente. Nos veremos en el siguiente artículo de esta serie, que ya se acerca a su «gran final».


Archivo Descripción
Experts\Expert Advisor.mq5
Muestra la interacción entre Chart Trade y el Asesor Experto. Es necesario Mouse Study para la interacción.
Indicators\Chart Trade.mq5 Crea la ventana en la que se configura la orden que se enviará. Es necesario Mouse Study para la interacción.
Indicators\Market Replay.mq5 Crea los controles necesarios para interactuar con el servicio de repetición/simulador. Es necesario Mouse Study para la interacción.
Indicators\Mouse Study.mq5 Permite la interacción entre los controles gráficos y el usuario. Es necesario tanto para utilizar el sistema de repetición/simulador como para operar en el mercado real.
Indicators\Order Indicator.mq5 Se encarga de mostrar las órdenes de mercado y permite interactuar con ellas y controlarlas.
Indicators\Position View.mq5 Se encarga de mostrar las posiciones de mercado y permite interactuar con ellas y controlarlas.
Services\Market Replay.mq5 Crea y mantiene el servicio de repetición y simulación de mercado. Es el archivo principal de todo el sistema.

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

Archivos adjuntos |
Anexo.zip (779.24 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Del básico al intermedio: Sobrecarga de operadores (III) Del básico al intermedio: Sobrecarga de operadores (III)
En este artículo veremos cómo implementar la sobrecarga tanto de operadores lógicos como de operadores relacionales. Hacerlo requiere cierto cuidado y una buena dosis de atención. Un simple descuido al implementar la sobrecarga de estos operadores puede hacer que todo el código termine siendo completamente inservible. Si la sobrecarga presenta algún problema, toda una base de datos creada a partir de los resultados generados por el código deberá descartarse por completo o, como mínimo, revisarse íntegramente.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 24): Herramienta de cuantificación de la acción del precio Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 24): Herramienta de cuantificación de la acción del precio
Los patrones de velas japonesas ofrecen información valiosa sobre los posibles movimientos del mercado. Algunas velas individuales señalan la continuación de la tendencia actual, mientras que otras presagian cambios de tendencia, dependiendo de su posición dentro de la acción del precio. Este artículo presenta un Asesor Experto (EA) que identifica automáticamente cuatro formaciones clave de velas japonesas. Explore las siguientes secciones para descubrir cómo esta herramienta puede mejorar su análisis de la evolución de los precios.