Português
preview
Simulación de mercado: Position View (IX)

Simulación de mercado: Position View (IX)

MetaTrader 5Probador |
14 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos, sean bienvenidos a un nuevo artículo de la serie sobre cómo construir un sistema de repetición/simulación.

En el artículo anterior, Simulación de mercado: Position View (VIII), mostré cómo podría implementarse, o mejor dicho, cómo deberíamos modificar el código del indicador de posición para que pudiera, mediante interacción, eliminar las líneas de take profit y stop loss, además de permitirnos cerrar la posición con mucha facilidad. Para quienes vienen siguiendo esta secuencia, creo que no tienen dificultades para entender lo que se implementa. Sin embargo, hasta el momento, todavía no hemos hecho nada relacionado con la cuestión de simular una operación. Pero no se preocupen por esto por ahora. Avanzamos en esa dirección, aunque no lo parezca.

Ahora tenemos un problema que, por un lado, resulta bastante molesto, pero, por otro, es muy interesante de explicar y resolver. El problema es: cómo añadir las líneas de take profit y stop loss después de que fueron eliminadas, y hacerlo sin usar el terminal, sino realizando la operación directamente en el gráfico. Bien, esto, a primera vista, parece algo simple. Sin embargo, hay algunos obstáculos que superar.


Reflexionando sobre una solución para el problema

Básicamente, este problema puede resolverse de la siguiente manera: al hacer clic en la línea de precio en el gráfico, podemos añadir una nueva línea al gráfico. Esta línea podrá arrastrarse hasta el punto deseado y, en el momento en que se suelte, el Asesor Experto deberá entender que ahí es donde queremos la línea de take profit o stop loss. Básicamente, eso es todo. Algo bastante simple, dicho sea de paso.

Pero pensemos un poco más en este enfoque. Bien, primero, necesitamos añadir o implementar un sistema que pueda arrastrar la línea de manera correcta, ya que no se trata solo de arrastrarla de cualquier forma. Recuerda que el precio donde se suelte la línea deberá cumplir algunos criterios para que el servidor de trading no rechace la petición enviada por el Asesor Experto. El problema, en este punto, es que cada clase de activo puede tener un determinado valor de tick. Algunos pueden estar en el orden de $ 0,01, mientras que otros pueden ser de $ 0,5, y también pueden existir casos en los que este valor sea de $ 0,2. Observa que es algo muy complicado a primera vista. Pero, afortunadamente, ya implementamos una solución para este problema. Está en el indicador de mouse. Bien, ya no necesitamos preocuparnos por esta cuestión de ajustar el precio. Lo único que necesitamos hacer es mirar dónde se encuentra el indicador de mouse y usar este punto como valor que se informará al Asesor Experto. Así, el Asesor Experto sabrá cuál es el precio que se enviará al servidor de trading para que se cree la línea de stop loss o take profit. Muy bien, un problema menos.

Pero aún tenemos otros problemas que analizar e intentar resolver. El siguiente problema tiene que ver con qué línea se crearía cuando ambas líneas no estén presentes en la posición. Este problema es uno de los más interesantes en este primer momento. La cuestión es que, si la posición es de compra, al arrastrar la línea hacia un precio más alto, podemos definirla como una línea de take profit. En cambio, si en una posición de venta arrastramos la línea de la misma manera, es decir, hacia un precio más alto, podremos definirla como una línea de stop loss. Lo mismo vale si el movimiento fuera contrario a lo que acabo de mencionar, y definiríamos una línea de stop loss o una línea de take profit. Pero también podríamos usar el precio donde, en ese instante, se ejecutan operaciones. Es decir, si arrastras la línea, en una posición de compra, hacia un punto por encima de donde actualmente se negocia el precio, definiríamos un take profit. Si la posición fuera de venta, definiríamos un stop loss. Ahora, si la línea, incluso en una posición de compra, se arrastrara por debajo del precio actualmente negociado, definiríamos un stop loss. Y, si la posición fuera de venta, definiríamos un take profit.

Tal vez esta explicación te haya confundido un poco, estimado lector. Pero entender lo que intento explicar es primordial para comprender por qué tomo la decisión de implementar el sistema de una forma u otra. No existe una manera correcta. Solo una manera que se adapte a lo que planeo hacer. Sin embargo, si deseas hacer las cosas de otra forma, sea por un motivo u otro, debes entender lo que explico, ya que así podrás implementar las cosas de forma más cohesionada y simple.

Sin embargo, aunque la explicación parezca muy confusa, todavía existe otro pequeño problema dentro de esa misma explicación. Para intentar que la explicación sea un poco menos confusa, intenta imaginar, por un instante, la siguiente situación: tú, como operador, operas con un símbolo cualquiera y decides comprarlo a un precio, por ejemplo: $ 100. Bien, la orden se dio a mercado y usando el indicador Chart Trade. Entonces, el Asesor Experto enviará una solicitud con las líneas de take profit y stop loss ya definidas. Después de un tiempo, decides eliminar estas líneas. Para esto, usas los botones que se implementaron en los artículos anteriores, con el Indicador de posición. Bien, el Asesor Experto entiende tu solicitud y envía al servidor el requerimiento para eliminar tanto el take profit como el stop loss. Ahora tenemos el escenario que queremos. Es decir, una posición abierta, sin las líneas de take profit y stop loss. En este punto, quiero que empieces a pensar e intentes seguir mi razonamiento.

Suponiendo que el precio subió y alcanzó los $ 150, decides que es hora de añadir la línea de stop loss. Esta línea formará parte de la posición, no será una nueva orden que esté en la profundidad de mercado esperando a que el precio vuelva. Esto es importante, ya que son situaciones diferentes. Bien, ¿qué deberías hacer como operador para añadir la línea de stop loss a la posición? La actitud más natural será hacer clic en la línea de la posición y arrastrarla. Perfecto. Pero ¿arrastrarla hacia dónde y hasta qué punto? Aquí está el problema. Pero simplifiquemos un poco las cosas. Tu idea, como operador, es colocar el stop loss en la posición de $ 140. Bien, esto es lo que tú, como operador, deseas hacer. Pero ¿cómo vas a hacerlo? O mejor dicho: ¿cómo deberás programar esto tú, como programador? Y es en este punto donde la cosa se complica. Porque, si tú, como programador, no planificas adecuadamente este tipo de situaciones, incluso tú, al usar un sistema que tú mismo implementaste, podrás acabar cometiendo errores al usar el sistema que tú mismo programaste.

Para simplificar, si implementaste un sistema en el que es necesario hacer clic en la línea horizontal de precio que está en $ 100, arrastrar el mouse hacia un precio, supongamos de $ 90, soltar el mouse para que el Asesor Experto sepa que deberá enviar un requerimiento al servidor de trading para que se cree la línea de stop loss, y solo después tomar esta misma línea que está en $ 90 y arrastrarla hasta los $ 140, podrías acabar cometiendo un error al arrastrar la línea directamente desde los $ 100 hasta los $ 140. Esto se debe a que el Asesor Experto entenderá esto como una solicitud para crear la línea de take profit, y no de stop loss, como podrías esperar.

Aparentemente, esto no sería un gran problema, ya que esa línea de precio no significa absolutamente nada. En este momento, es necesario abrir un paréntesis para explicar una cosa. Algo que muchos operadores imaginan que es verdad, pero que no pasa de ser un gran equívoco. Las líneas de take profit y stop loss NO, y voy a repetirlo, NO SON EL LUGAR DONDE OCURREN LAS COSAS. La posición solo se cerrará si, y solo si, existe alguna operación exactamente en ese precio. Y el número de operaciones que ocurran allí deberá ser suficiente para consumir todos los lotes antes de que el precio se mueva. Si se mueve antes de consumir todos los lotes presentes allí, la orden será omitida. Es decir, la posición continuará abierta. Este es un problema bastante serio y que afecta a muchos operadores con menos experiencia, ya que muchos usan robots e imaginan que las órdenes de take profit y stop loss jamás serán saltadas.

En una secuencia de artículos más antigua, expliqué este tipo de cosas. Si no la viste, te recomiendo encarecidamente que leas esta secuencia. Son 15 artículos bastante simples, donde explico cómo puedes desarrollar un Asesor Experto que funcione de forma 100% automática. Allí, en esa secuencia, muestro cómo resolver justamente este problema de que la orden se omite, y que, en este punto del desarrollo del sistema de repetición/simulador, puede ayudarte a pensar en otras soluciones. Por ejemplo, el uso de una orden contraria, con el mismo volumen, que se ejecutaría de cualquier manera aunque el precio saltara la orden, ya que entraría a mercado. El primer artículo de dicha secuencia puede verse en:

Cómo construir un EA que opere automáticamente (Parte 01): Conceptos y estructuras

Debido a que la orden puede saltarse, dije que no podríamos pensar en una implementación en la que el stop loss o take profit fuera una orden contraria que estuviera en la profundidad de mercado. Bien, ya que esto cambiaría completamente la forma de abordar el problema. De esta forma, no sé si tú, estimado lector y entusiasta, lograste entender la profundidad de lo que debe implementarse. Mi solución para este problema es simple. ¿Por qué complicarlo si podemos darle al operador o usuario la capacidad de indicar anticipadamente si una línea que se va a arrastrar será la de stop loss o take profit? En el pasado, en otra secuencia de artículos, mostré cómo se hizo esto. Sin embargo, allí, en aquel momento, el enfoque de los artículos era otro. No imaginaba que podrían llegar a ser usados por personas que estuvieran empezando en la programación. Entonces, aunque la implementación, en aquel momento, funcionaba, la explicación contenida en los artículos no ayudaba en nada a quien quisiera aprender a desarrollar algo basado en aquel sistema.

Afortunadamente, ahora el enfoque es otro. Aquí quiero que tú, estimado lector, aprendas y entiendas por qué la implementación se hace de esta o aquella manera. Pero, principalmente, quiero que busques estudiar y crees tus propias soluciones, usando para ello un poco del conocimiento que intento transmitirte.

Muy bien, entonces empecemos a hacer las cosas. En todas las situaciones explicadas anteriormente, una cosa quedó bastante clara: necesitamos, de alguna manera, arrastrar las líneas en el gráfico. Incluso porque arrastrarlas nos permitirá cambiar el valor del take profit y stop loss. Entonces, necesitamos comenzar la implementación en este punto. Así, podemos pasar al siguiente tema.


Preparándonos para arrastrar las líneas de precio

Lo primero que necesitamos hacer para poder arrastrar las líneas de precio, y cuando mencione líneas de precio en el indicador de posición me refiero a las líneas de stop loss y take profit, es añadir un nuevo evento al sistema. Esto se hace fácilmente, como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.     #define macro_DEBUG_MODE(A) \
08.                     Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.     #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay          "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService       0xFED00000
16. #define def_IndicatorTimeFrame    (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame        4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.     double   dValue;
22.     long     _long;                                  // 1 Information
23.     datetime _datetime;                              // 1 Information
24.     uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.     ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.     uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents     {
30.             evTicTac,                        //Event of tic-tac
31.             evHideMouse,                     //Hide mouse price line
32.             evShowMouse,                     //Show mouse price line
33.             evHideBarTime,                   //Hide bar time
34.             evShowBarTime,                   //Show bar time
35.             evHideDailyVar,                  //Hide daily variation
36.             evShowDailyVar,                  //Show daily variation
37.             evHidePriceVar,                  //Hide instantaneous variation
38.             evShowPriceVar,                  //Show instantaneous variation
39.             evCtrlReplayInit,                //Initialize replay control
40.             evChartTradeBuy,                 //Market buy event
41.             evChartTradeSell,                //Market sales event 
42.             evChartTradeCloseAll,            //Event to close positions
43.             evChartTrade_At_EA,              //Event to communication
44.             evEA_At_ChartTrade,              //Event to communication
45.             evChatWriteSocket,               //Event to Mini Chat
46.             evChatReadSocket,                //Event To Mini Chat
47.             evUpdate_Position,               //Event to communication
48.             evMsgClosePositionEA,            //Event to communication
49.             evMsgCloseTakeProfit,            //Event to communication
50.             evMsgCloseStopLoss,              //Event to communication
51.             evMsgNewTakeProfit,              //Event to communication
52.             evMsgNewStopLoss                 //Event to communication
53.                         };
54. //+------------------------------------------------------------------+
55. enum EnumPriority {                          //Priority list on objects
56.             ePriorityNull = -1,
57.             ePriorityDefault = 0,
58.             ePriorityChartTrade = 5,
59.             ePriorityOrders = 10
60.                         };
61. //+------------------------------------------------------------------+

Defines.mqh

Observa que se añadieron dos nuevos eventos a la lista. Estos nos ayudarán en la comunicación entre el Indicador de posición y el Asesor Experto. Hecho esto, podemos ir a la clase C_Orders dentro del Asesor Experto e implementar estos dos eventos. Así, el nuevo código de la clase puede verse a continuación:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders
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 SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult  TradeCheck;
071.                 MqlTradeResult       TradeResult;
072.                 
073.                 ZeroMemory(TradeCheck);
074.                 ZeroMemory(TradeResult);
075.                 if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
076.                 {
077.                     PrintFormat("Order System - Check Error: %d", GetLastError());
078.                     return 0;
079.                 }
080.                 m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
081.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
082.                 {
083.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
084.                     return 0;
085.                 };
086.                 
087.                 return TradeResult.order;
088.             }
089. //+------------------------------------------------------------------+    
090.         ulong ToMarket(const ENUM_ORDER_TYPE type)
091.             {
092.                 double price  = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
093.                 double vol    = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
094.                 uchar  nDigit = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
095.                 
096.                 ZeroMemory(m_Base.TradeRequest);
097.                 m_Base.TradeRequest.magic         = m_Base.MagicNumber;
098.                 m_Base.TradeRequest.symbol        = m_ChartTrade.Data.szContract;
099.                 m_Base.TradeRequest.price         = NormalizeDouble(price, nDigit);
100.                 m_Base.TradeRequest.action        = TRADE_ACTION_DEAL;
101.                 m_Base.TradeRequest.sl            = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
102.                 m_Base.TradeRequest.tp            = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
103.                 m_Base.TradeRequest.volume        = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
104.                 m_Base.TradeRequest.type          = type;
105.                 m_Base.TradeRequest.type_time     = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
106.                 m_Base.TradeRequest.stoplimit     = 0;
107.                 m_Base.TradeRequest.expiration    = 0;
108.                 m_Base.TradeRequest.type_filling  = ORDER_FILLING_RETURN;
109.                 m_Base.TradeRequest.deviation     = 1000;
110.                 m_Base.TradeRequest.comment       = "Order Generated by Experts Advisor.";
111. 
112.                 MqlTradeRequest TradeRequest[1];
113. 
114.                 TradeRequest[0] = m_Base.TradeRequest;
115.                 ArrayPrint(TradeRequest);
116. 
117.                 return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
118.             };
119. //+------------------------------------------------------------------+
120.         void CloseAllsPosition(void)
121.             {
122.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
123.                 {
124.                     if (PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
125.                     if (PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
126.                     ClosePosition(PositionGetInteger(POSITION_TICKET));
127.                 }
128.             };
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. //+------------------------------------------------------------------+    
149.     public    :
150. //+------------------------------------------------------------------+
151.         C_Orders(const ulong magic)
152.             {
153.                 m_Base.MagicNumber = magic;
154.             }
155. //+------------------------------------------------------------------+    
156.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
157.             {
158.                 switch (id)
159.                 {
160.                     case CHARTEVENT_CUSTOM + evChartTradeBuy     :
161.                     case CHARTEVENT_CUSTOM + evChartTradeSell    :
162.                     case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
163.                         if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
164.                         {
165.                             case evChartTradeBuy:
166.                                 ToMarket(ORDER_TYPE_BUY);
167.                                 break;
168.                             case evChartTradeSell:
169.                                 ToMarket(ORDER_TYPE_SELL);
170.                                 break;
171.                             case evChartTradeCloseAll:
172.                                 CloseAllsPosition();
173.                                 break;
174.                         }
175.                         break;
176.                     case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
177.                         ClosePosition((ulong)(lparam));
178.                         break;
179.                     case CHARTEVENT_CUSTOM + evMsgCloseTakeProfit:
180.                         ModifyValueSLTP((ulong)(lparam), sparam, dparam, 0);
181.                         break;
182.                     case CHARTEVENT_CUSTOM + evMsgCloseStopLoss:
183.                         ModifyValueSLTP((ulong)(lparam), sparam, 0, dparam);
184.                         break;
185.                     case CHARTEVENT_CUSTOM + evMsgNewTakeProfit:
186.                         ModifyValueSLTP((ulong)(lparam), sparam, -1, dparam);
187.                         break;
188.                     case CHARTEVENT_CUSTOM + evMsgNewStopLoss:
189.                         ModifyValueSLTP((ulong)(lparam), sparam, dparam, -1);
190.                         break;
191.                 }
192.             }
193. //+------------------------------------------------------------------+    
194. };
195. //+------------------------------------------------------------------+

C_Orders.mqh

Tal vez parezca una tontería colocar todo el código de la clase íntegramente, ya que solo una parte sufrió cambios. Pero está bien, veamos los cambios que ocurrieron. Observa que, en las líneas 185 y 188, implementamos precisamente los nuevos eventos para que el Asesor Experto entienda cómo procesarlos. Ahora, presta atención a una cosa. Usamos la misma rutina que se implementó anteriormente. Es decir, ModifyValueSLTP. Pero, a diferencia de lo que hacíamos antes, ahora pasamos un valor negativo en uno de los campos. El nuevo valor del campo del mensaje se informa en el parámetro dparam, igual que antes. Pero ¿y este valor negativo? ¿A qué se debe? Para entenderlo, es necesario mirar el código de la línea 130, que es precisamente donde implementamos la rutina ModifyValueSLTP.

Observa que ahora, dentro de este procedimiento, tenemos algunas pequeñas diferencias. En primer lugar, en la línea 135, hacemos una prueba para comprobar si el valor de sl o tp es negativo. Si este es el caso, tendremos una llamada a PositionSelectByTicket. Esta llamada busca cargar los valores más actuales de la posición. ¿Pero por qué? El motivo se ve en las líneas 140 y 141. Si el valor de alguno de los argumentos es negativo, se usará el valor más actual presente en la posición para que el servidor no elimine el valor existente. Sin embargo, si el valor es cero, como en el caso de la eliminación de la línea, u otro valor cualquiera que no sea negativo, ese valor se usará para que el servidor pueda colocar la línea de stop loss o take profit en el punto deseado por el operador. Con esto, el código del Asesor Experto queda actualizado, sin necesitar ningún otro cambio en su estructura.

Ahora podemos volver nuestra atención al indicador de posición, ya que el código del Asesor Experto ya puede comprender una solicitud de cambio de precio, ya sea del stop loss o del take profit. Pero, antes de empezar a ver el código del indicador, quiero recordarte, estimado lector, que, cuando el precio se actualice, el indicador reflejará esto automáticamente. Es decir, no necesitamos preocuparnos por posicionar la línea de precio en el gráfico. Solo necesitamos crear algún procedimiento que genere un evento para que el Asesor Experto cambie la línea horizontal de precio.

Entonces, lo que voy a mostrar en este momento no es lo que se implementará de forma definitiva. Solo quiero mostrar cómo puedes mover la línea de precio. Para esto, necesitamos hacer algunos pequeños ajustes en el código original. Y, como no quiero dejarte, mi estimado lector y entusiasta, perdido con tantos cambios, vamos a hacerlo poco a poco. Primero, vamos a modificar el código del indicador para que solo el indicador de mouse haga que las cosas ocurran. Para que esto ocurra, es necesario cambiar un poco el código que vimos en los artículos anteriores. El nuevo código puede verse a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine        m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose     m_Info.szPrefixName + "#CLOSE"
006. //+------------------------------------------------------------------+
007. #define def_PathBtns "Images\\Market Replay\\Orders\\"
008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
009. #resource "\\" + def_Btn_Close;
010. //+------------------------------------------------------------------+
011. #include "..\Auxiliar\C_Mouse.mqh"
012. //+------------------------------------------------------------------+
013. class C_ElementsTrade : private C_Mouse
014. {
015.     private    :
016. //+------------------------------------------------------------------+
017.         enum eObjects {LINE_PRICE, BTN_CLOSE, ELEM_NULL};
018. //+------------------------------------------------------------------+
019.         struct st00
020.         {
021.             ulong        ticket;
022.             string       szPrefixName;
023.             EnumEvents   ev;
024.             double       price;
025.             struct st01
026.             {
027.                 short xi, xf, yi, yf;
028.             }Positions[ELEM_NULL];
029.         }m_Info;
030. //+------------------------------------------------------------------+
031.         void UpdateViewPort(void)
032.         {
033.             int x, y;
034.             
035.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);
036.             ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price);
037.             x = 130;
038.             m_Info.Positions[BTN_CLOSE].xi = (short)(x - 8);
039.             m_Info.Positions[BTN_CLOSE].xf = (short)(x + 8);
040.             m_Info.Positions[BTN_CLOSE].yi = (short)(y - 8);
041.             m_Info.Positions[BTN_CLOSE].yf = (short)(y + 8);
042.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
044.         }
045. //+------------------------------------------------------------------+
046.         eObjects CheckMousePosition(double &price)
047.             {
048.                 short x, y;
049.                 st_Mouse loc;
050.                 
051.                 loc = GetPositionsMouse();
052.                 price = loc.Position.Price;
053.                 x = loc.Position.X_Graphics;
054.                 y = loc.Position.Y_Graphics;
055.                 for (eObjects c0 = LINE_PRICE; c0 < ELEM_NULL; c0++)
056.                     if ((x > m_Info.Positions[c0].xi) && (x < m_Info.Positions[c0].xf) &&
057.                         (y > m_Info.Positions[c0].yi) && (y < m_Info.Positions[c0].yf)) return c0;
058.                 return ELEM_NULL;
059.             }
060. //+------------------------------------------------------------------+
061.     public    :
062. //+------------------------------------------------------------------+
063.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
064.             :C_Mouse(0, "")
065.         {
066.             string szObj;
067.             
068.             ZeroMemory(m_Info);
069.             m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
070.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio);
071.             ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2);
072.             ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr);
073.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
074.             ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull);
075.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityOrders + 1));
076.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
077.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
078.         }
079. //+------------------------------------------------------------------+
080.         ~C_ElementsTrade()
081.         {
082.             if (m_Info.szPrefixName != "")
083.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
084.         }
085. //+------------------------------------------------------------------+
086.         inline void UpdatePrice(const double price)
087.         {
088.             m_Info.price = price;
089.             UpdateViewPort();
090.         }
091. //+------------------------------------------------------------------+
092.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
093.         {
094.             double price;
095.             
096.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
097.             switch (id)
098.             {
099.                 case CHARTEVENT_MOUSE_MOVE:
100.                     if (CheckClick(C_Mouse::eClickLeft)) switch(CheckMousePosition(price))
101.                     {
102.                         case LINE_PRICE:
103.                             break;
104.                         case BTN_CLOSE:
105.                             if (PositionSelectByTicket(m_Info.ticket)) switch (m_Info.ev)
106.                             {
107.                                 case evMsgClosePositionEA:
108.                                     EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
109.                                     break;
110.                                 case evMsgCloseTakeProfit:
111.                                     EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
112.                                     break;
113.                                 case evMsgCloseStopLoss:
114.                                     EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
115.                                     break;
116.                             }
117.                             break;
118.                     }
119.                     break;
120.                 case CHARTEVENT_CHART_CHANGE:
121.                     UpdateViewPort();
122.                     break;
123.             }
124.         }
125. //+------------------------------------------------------------------+
126. };
127. //+------------------------------------------------------------------+
128. #undef def_Btn_Close
129. #undef def_PathBtns
130. //+------------------------------------------------------------------+
131. #undef def_NameBtnClose
132. #undef def_NameHLine
133. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

Al mirar este código, debes estar pensando: Vamos, pero no cambió nada en el código. Pero no es exactamente así, mi amigo. El código cambió completamente su comportamiento. Observa que ahora ya no incluimos la clase C_Terminal, sino la clase C_Mouse. Y esto se hace en la línea 11. De la misma manera, la clase ahora ya no hereda de C_Terminal. La clase hereda de C_Mouse y, por extensión, trae también la clase C_Terminal. Con esto, ahora empezamos a usar el indicador de mouse para interactuar con el indicador de posición. Pero solo hacer estos cambios mencionados anteriormente no es suficiente para que esto ocurra. Se necesitan algunos cambios más en el código. Así, surge la enumeración en la línea 17. Esta tiene como objetivo sustituir el trabajo que antes hacía MetaTrader 5. Es decir, ahora controlaremos un poco más el código.

Debido a esta sustitución, en la línea 25 declaramos una nueva estructura. Esta cuenta con cuatro variables, que se declaran en la línea 27. Sin embargo, esta estructura es, en realidad, un array de posiciones, y esto puede observarse en la línea 28, donde decimos que el array tendrá la dimensión de X elementos. En el caso actual, 3 elementos. Muy bien, ahora tenemos que añadir los elementos a este array. Y esto se hace en el procedimiento de la línea 31. Observa que, entre las líneas 38 y 41, incluimos la posición, o caja de colisión, donde el botón de cierre esperará para recibir un clic del mouse. Los valores ocho que pueden verse en estas líneas representan la mitad del tamaño del bitmap. Recuerda: el bitmap tiene 16 x 16 píxeles. Y, como se posiciona de manera centralizada, necesitamos ajustar correctamente esta caja de colisión. Perfecto, ya tenemos la ubicación del botón de cierre.

Pero, antes de proseguir a la línea 46, vamos a saltar al constructor de la clase. Este puede verse en la línea 63. Observa que, en la línea 64, ahora inicializamos la clase C_Mouse. Antes, era la clase C_Terminal. Esta forma en que se inicializa la clase C_Mouse le dice a MetaTrader 5 que no queremos crear el Mouse. Solo queremos poder acceder al indicador de mouse, si está presente en el gráfico.

Ahora podemos volver a la línea 46 y ver qué ocurre allí. Observa que esta función parece mucho más complicada de lo que es. Aquí simplemente capturamos la posición del indicador de mouse en la línea 51. Inmediatamente después, en la línea 52, marcamos el valor de precio para devolverlo al autor de llamada, que veremos en breve. Pero lo que nos interesa son precisamente las siguientes líneas. Estas comprueban la caja de colisión, a fin de devolver cuál fue el objeto que recibió un clic. Si no se identificó ningún objeto, en la línea 58 devolveremos un valor inválido al autor de llamada.

Ahora, finalmente, podemos ver el autor de llamada de esta función CheckMousePosition. Es precisamente el procedimiento DispatchMessage, que aparece en la línea 92. Ahora es cuando llega el verdadero cambio, pues lo primero que hacemos es llamar al manejador de eventos de la clase C_Mouse. Esto en la línea 96. El motivo es que necesitamos garantizar que los datos sean correctos, ya que no usamos un sistema de búfer para obtener los datos del indicador de mouse. Después de manejar los eventos en la clase C_Mouse, el código, cuando ocurra un evento de mouse, ejecutará la línea 99.

Ahora, presta atención. Los eventos del mouse, aquí, no son los mismos que tú esperas si piensas en usar la misma forma de codificar el mouse, como normalmente ocurre cuando lo usamos en MQL5. Aquí, los eventos del mouse se filtran y se manejan de otra manera. Para entender esto, vuelve a esta misma secuencia de artículos sobre el sistema de repetición/simulador y mira cómo se implementó el indicador de mouse. Allí notarás que, cuando usamos el indicador de mouse, algunos eventos de clic pueden ignorarse. Esto se debe a que el indicador se usaba con el propósito de generar un estudio. En esta misma secuencia, mostré cómo puedes modificar el indicador, lo que permite crear un estudio particular en el que puedes interactuar directamente con el gráfico sin necesidad de ajustar ningún objeto de estudio. El propio indicador de mouse hará esto por ti.

Pues bien, por esta razón, la prueba de la línea 100 solo tendrá éxito si el clic del mouse no se realizó durante un estudio. Si, durante un estudio, haces clic accidentalmente en un objeto que observa este código de la clase C_ElementsTrade, el clic será ignorado. Entonces, suponiendo que no estás en un estudio y haces clic en algún punto del gráfico, la prueba pasará y se llamará a la función CheckMousePosition. Ahora, hay un detalle que es importante mencionar. Esta comprobación ignorará por completo el orden de visualización en el gráfico, o el valor de la propiedad ZOrder. Es decir, ten cuidado. Pues, aunque hagas clic en un objeto con un ZOrder más bajo, este podrá tener prioridad, ya que quien determinará el orden aquí será la enumeración de la línea 17. Y dos objetos, aunque sean de indicadores diferentes, pero compartan la misma posición de la caja de colisión, recibirán el evento. No importará quién tenga un ZOrder mayor ni quién esté delante de quién. Ten cuidado con esto hasta que se proporcione la corrección.

Después veremos cómo hacerlo. Pero, por ahora, ten cuidado. Volviendo al código, cuando la función CheckMousePosition retorna, indicará qué objeto recibió un clic. En ese momento, separamos los casos. Observa que, en la línea 102, ya empezamos a indicar que, cuando el objeto LINE_PRICE reciba un clic, ocurrirá algo. En la línea 104, colocamos exactamente el mismo código que existía antes, cuando MetaTrader 5 hacía el trabajo por nosotros. De esta manera, ahora acabamos de sustituir lo que antes gestionaba MetaTrader 5 para que lo gestione nuestro código.

Todo el resto del código del indicador permanecerá intacto, por lo que no es necesario mostrarlo aquí.


Consideraciones finales

En este artículo se abordó un problema con el que todavía lidiaremos con más profundidad en el próximo artículo. Pero, antes de terminar, quiero mostrar una pequeña corrección en el código del Asesor Experto. Esta tiene como objetivo forzar al Asesor Experto a disparar un evento cuando la posición se cierre. Es decir, cuando el precio alcance las líneas de stop loss o take profit. Sin esta corrección, el indicador de posición no será informado de que ya puede retirarse del gráfico. El código corregido puede verse a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
004. #property description "Demo version between interaction"
005. #property description "of Chart Trade and Expert Advisor"
006. #property version   "1.121"
007. #property link "https://www.mql5.com/pt/articles/13265"
008. //+------------------------------------------------------------------+
009. #include <Market Replay\Order System\C_Orders.mqh>
010. #include <Market Replay\Auxiliar\C_Terminal.mqh>
011. //+------------------------------------------------------------------+
012. enum eTypeContract {MINI, FULL};
013. //+------------------------------------------------------------------+
014. input eTypeContract user00 = MINI;         //Cross order in contract
015. //+------------------------------------------------------------------+
016. C_Orders    *Orders;
017. C_Terminal  *Terminal;
018. //+------------------------------------------------------------------+
019. int OnInit()
020. {
021.     Terminal = NULL;
022.     Orders = new C_Orders(0xC0DEDAFE78514269);
023.     
024.     return INIT_SUCCEEDED;
025. }
026. //+------------------------------------------------------------------+
027. void OnTick() {}
028. //+------------------------------------------------------------------+
029. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
030. {
031.     int handle;
032.     
033.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
034.     switch (id)
035.     {
036.         case CHARTEVENT_CHART_CHANGE:
037.             if (Terminal != NULL) break;
038.             else
039.             {
040.                 ulong ul;
041.                 Terminal = new C_Terminal(0, 0, user00);
042.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
043.                 {
044.                     ul = PositionGetTicket(count);
045.                     if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
046.                     {
047.                         ChartIndicatorDelete(0, 0, IntegerToString(ul));
048.                         continue;
049.                     }
050.                     handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
051.                     ChartIndicatorAdd(0, 0, handle);
052.                     IndicatorRelease(handle);
053.                 }
054.             }
055.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
056.             EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
057.             break;
058.     }
059. }
060. //+------------------------------------------------------------------+
061. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
062. {    
063.     static ulong ticket = 0;
064.     
065.     if (Terminal == NULL) return;
066.     switch (trans.type)
067.     {
068.         case TRADE_TRANSACTION_HISTORY_ADD:
069.             EventChartCustom(0, evUpdate_Position, trans.position, 0, "");
070.             ticket = (trans.order != trans.position ? trans.position : 0);
071.             break;
072.         case TRADE_TRANSACTION_REQUEST:
073.             if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
074.             {
075.                 case TRADE_ACTION_DEAL:
076.                     if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
077.                     else
078.                     {
079.                         int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
080.                         ChartIndicatorAdd(0, 0, handle);
081.                         IndicatorRelease(handle);
082.                     }
083.                     ticket = 0;
084.                     break;
085.                 case TRADE_ACTION_SLTP:
086.                     EventChartCustom(0, evUpdate_Position, request.position, 0, "");
087.                     break;
088.             }
089.             break;
090.     };
091. }
092. //+------------------------------------------------------------------+
093. void OnDeinit(const int reason)
094. {
095.     ulong ul;
096.     
097.     switch (reason)
098.     {
099.         case REASON_REMOVE:
100.         case REASON_INITFAILED:
101.             EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
102.             break;
103.     }
104.     if (Terminal != NULL) for (int count = PositionsTotal() - 1; count >= 0; count--)
105.     {
106.         ul = PositionGetTicket(count);
107.         if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
108.         ChartIndicatorDelete(0, 0, IntegerToString(ul));
109.     }
110.     delete Orders;
111.     delete Terminal;
112. }
113. //+------------------------------------------------------------------+

Expert Advisor.mq5

La corrección es precisamente la inclusión de la línea 69 en el código. Sin la presencia de esta línea, el evento de actualización no se disparaba cuando la posición se cerraba al alcanzar una de las líneas de precio. Y, como una pequeña explicación adicional, existe una cuestión que quizá no haya quedado clara durante este artículo. Esta cuestión trata precisamente del movimiento de las líneas de stop loss y take profit. Muy probablemente, incluso puedes preguntar, y no estarás equivocado al hacerlo, por qué me preocupo por crear dicho movimiento si este ocurre cuando intentamos arrastrar las líneas que crea el indicador de posición.

Pero no es exactamente eso lo que ocurre, estimado lector. El detalle es que, si estás usando un símbolo que puede negociarse, muy probablemente lo que ocurre es que estás moviendo las líneas creadas por MetaTrader 5. El movimiento que planeamos crear se produce precisamente cuando dichas líneas creadas por MetaTrader 5 no están presentes. O cuando la configuración de MetaTrader 5 no indica que este pueda mover dichas líneas.

Si no sabes dónde se realiza dicha configuración, basta con mirar la imagen inferior.

De forma predeterminada, MetaTrader 5 tendrá la configuración mostrada en la imagen superior. Sin embargo, si la modificas a lo que se ve en la siguiente imagen, dicho movimiento no ocurrirá.

Es precisamente en este punto donde usaríamos MetaTrader 5 como si el símbolo del gráfico fuera un símbolo no negociable. Es decir, el típico caso que se verá cuando usemos este mismo sistema o indicadores en la repetición/simulación. Por eso, es importante que creemos todos los procesos involucrados en el movimiento de las líneas de precio. No es una cuestión de si necesitamos o no hacer esto. Es una cuestión de cómo y cuándo vamos a hacerlo. Para quienes siguen los artículos, pero no pueden compilar las fuentes, dejo disponibles en el anexo los ejecutables ya modificados en este artículo. Y nos vemos en el próximo artículo, donde continuaremos implementando un poco más el sistema de repetición/simulador.

ArchivoDescripción
Experts\Expert Advisor.mq5
Muestra la interacción entre Chart Trade y el Asesor Experto (Es necesario usar Mouse Study para la interacción)
Indicators\Chart Trade.mq5Crea la ventana para configurar la orden que se va a enviar (Es necesario usar Mouse Study para la interacción)
Indicators\Market Replay.mq5Crea los controles para interactuar con el servicio de repetición/simulador (Es necesario usar Mouse Study para la interacción)
Indicators\Mouse Study.mq5Permite la interacción entre los controles gráficos y el usuario (Necesario tanto para operar el sistema de repetición/simulador como en el mercado real)
Indicators\Order Indicator.mq5Responsable de la indicación de órdenes de mercado, permitiendo su interacción y control
Indicators\Position View.mq5Responsable de la indicación de posiciones de mercado, permitiendo su interacción y control
Services\Market Replay.mq5Crea y mantiene el servicio de repetición y simulación de mercado (Archivo principal de todo el sistema)

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

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: Colas, listas y árboles (II) Del básico al intermedio: Colas, listas y árboles (II)
Este es un artículo que tú, mi estimado lector, deberás estudiar con mucha calma. Esto se debe al tipo de contenido que se explicará en él. Aunque hemos procurado mantener las cosas lo más simples y didácticas posible, el contenido presentado aquí es, sin duda, algo muy complicado para quienes se están iniciando en la programación. Sin embargo, esto no es motivo para que te desanimes o ignores lo que se explica aquí, ya que este artículo establecerá un vínculo entre dos temas completamente diferentes, aunque íntimamente relacionados.
De novato a experto: Noticias animadas utilizando MQL5 (XI): Correlación en el trading con noticias De novato a experto: Noticias animadas utilizando MQL5 (XI): Correlación en el trading con noticias
En este artículo, analizaremos cómo se puede aplicar el concepto de correlación financiera para mejorar la eficiencia en la toma de decisiones a la hora de operar con varios símbolos durante el anuncio de acontecimientos económicos importantes. El objetivo es abordar el desafío que supone la mayor exposición al riesgo provocada por la creciente volatilidad durante la publicación de noticias.
De novato a experto: Supervisión y registro del backend de un EA con MQL5 De novato a experto: Supervisión y registro del backend de un EA con MQL5
Utilizar una solución prefabricada para operar en los mercados financieros sin preocuparse por el funcionamiento interno del sistema puede parecer reconfortante, pero no siempre es así para los desarrolladores. Tarde o temprano, surgirá una actualización, un fallo de rendimiento o un error inesperado, y será fundamental rastrear con exactitud el origen del problema para diagnosticarlo y resolverlo rápidamente. Este artículo se centra en descubrir qué sucede normalmente entre bastidores en un Asesor Experto de trading, y en desarrollar una clase personalizada dedicada a mostrar y registrar los procesos de backend utilizando MQL5. Esto brinda tanto a los desarrolladores como a los traders la capacidad de localizar rápidamente errores, monitorear el comportamiento y acceder a información de diagnóstico específica para cada EA.
Motor de decisión Multi-IA para MQL5 (Parte 2): Voto ponderado que aprende en cuál IA confiar, más gestión de riesgo Motor de decisión Multi-IA para MQL5 (Parte 2): Voto ponderado que aprende en cuál IA confiar, más gestión de riesgo
Esta segunda parte convierte el motor multi-IA en un sistema que aprende en qué modelos confiar. Se registra cada predicción, se evalúa su acierto tras un horizonte y se actualiza un hit-rate por proveedor (EMA), para ponderar el voto por confianza × acierto real. Además, se añade gestión de riesgo: SL/TP basada en ATR con ratio recompensa-riesgo fijado en el código y tamaño de posición escalado por la confianza. Útil para pruebas de demostración.