English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de un EA comercial desde cero (Parte 30): ¿¡El CHART TRADE ahora como indicador?!

Desarrollo de un EA comercial desde cero (Parte 30): ¿¡El CHART TRADE ahora como indicador?!

MetaTrader 5Ejemplos | 18 octubre 2022, 09:59
354 0
Daniel Jose
Daniel Jose

1.0 - Introducción

En el artículo anterior Desarrollo de un EA comercial desde cero (Parte 29) eliminamos el Chart Trade del interior del EA, ya lo habíamos hecho con otras cosas como el Volume At Price y el Times & Trade, para mejorar el rendimiento y la robustez de nuestro EA. Pues bien, al quitar el Chart Trade de dentro del EA, nos quedamos sólo con el sistema básico de órdenes. A pesar de que él parezca insuficiente para algunos, en realidad puede encargarse de todo el trabajo, no obstante existen personas a las que les gusta entrar y salir de las operaciones en el mercado, no les gusta quedarse posicionándolas para que queden pendientes, a la espera de que el precio llegue a un determinado nivel para entrar o salir de la operación.

Cuando estamos utilizando la plataforma MetaTrader 5 en el activo que estamos comerciando (digo esto porque se puede estar utilizando un sistema de órdenes cruzadas, y vimos cómo hacerlo en la (Parte 11) de esta secuencia), tendremos acceso al QUICK TRADING que son dos botones que nos permiten enviar órdenes de mercado, los podemos observar en la esquina superior izquierda y tienen una apariencia mas o menos como se muestra a continuación:

Estos botones funcionan como un Chart Trading básico, pero no serán adecuados o, mejor, ni siquiera aparecerán si se utiliza un sistema de órdenes cruzadas, en este caso tenemos que volver con nuestro Chart Trade, pero ya no lo utilizaremos dentro del EA, no formará parte del código del EA, será a partir de este momento un mero indicador.

Pero, ¿por qué convertir el Chart Trading en un indicador? La razón de esto es que un EA sólo debe ser responsable del sistema comercial, todo lo que no es parte de este sistema debe ser eliminado de alguna manera del código del EA. Tal vez esto no tenga sentido para ustedes en este momento, pero pronto lo entenderán con más detalle, ya que estoy preparando una secuela para explicar exactamente el motivo de esto.

Incluso se podría hacer que el Chart Trading se utilizara como un script, pero esto nos traería un inconveniente: cada vez que se modificara la hora del gráfico, el script se finalizaría obligándonos a ponerlo de nuevo en el gráfico manualmente.

Sin embargo, con el uso de un indicador, ya no tendremos este inconveniente, además porque el Chart Trade no afectará en nada al thread de ejecución de los indicadores, y de esta manera el EA estará libre pudiendo tener su código enfocado sólo y únicamente en lo necesario para gestionar órdenes y posiciones.

Es muy cierto que no tendremos todos esos controles e información de la versión original, este Chart Trade en forma de indicador es mucho más sencillo, pero sigue siendo adecuado, incluso porque el sistema de MetaTrader 5 es funcional y muy sencillo, y no tenemos acceso a cierta información que sí tendremos en nuestro Chart Trade.

Así que vamos a trabajar, porque las cosas van a ser interesantes...


2.0 - Elaboración del Indicador Chart Trade

Los cambios necesarios para hacer esto no son pocos, de hecho se irán acumulando hasta que volvamos a tener el Chart Trade en el gráfico, y no formarán parte del código del EA.

A primera vista se podría pensar que algo como lo que se muestra en el fragmento de abajo sería suficiente para que el Chart Trade se compilara, pero no es así, porque está intrínsecamente ligado a varias cosas que deberíamos desactivar, pero no cancelar, ya que podríamos querer que él volviera al interior del EA.

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh>
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_Terminal                      Terminal;
//+------------------------------------------------------------------+
int OnInit()
{
        Chart.AddThese("IDE(,,170, 240)");

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Al tratar de compilar este código obtendremos una gran cantidad de errores y fallos, pero vamos a arreglarlos uno por uno con el fin de mantener el código compatible con el EA, al mismo tiempo que lo ajustamos para ser utilizado como un indicador.

Lo primero que hay que hacer es aislar el sistema que crea o gestiona las subventanas. Como el indicador Chart Trade no utilizará estas subventanas, no necesitamos este código incrustado en el indicador. Esto es bastante sencillo de hacer. En el archivo C_Chart_IDE.mqh, hacemos la siguiente modificación para que ahora el código quede como se muestra a continuación:

#ifdef def_INTEGRATION_CHART_TRADER
        #include <NanoEA-SIMD\SubWindow\C_SubWindow.mqh>
        #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#else
        #include <NanoEA-SIMD\SubWindow\C_ChartFloating.mqh>
        #include <NanoEA-SIMD\Auxiliar\C_Terminal.mqh>
#endif  
//+------------------------------------------------------------------+
#ifdef def_INTEGRATION_CHART_TRADER
        class C_Chart_IDE : public C_SubWindow
#else 
        class C_Chart_IDE : public C_ChartFloating
#endif 

De esta forma aislamos completamente el sistema de subventanas, a la vez que evitamos cualquier conexión del Chart Trade con el sistema indicador de órdenes del EA. Tengan en cuenta que la definición def_INTEGRATION_CHART_TRADER controlará esto, pero dado que esta definición sólo se utiliza en el EA, cualquier cosa dentro de ella no será compilada en el indicador.

Como el indicador no utilizará subventanas, tenemos que evitar algunas cosas y una de ellas se ve justo debajo:

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
                                m_szLine = "";
                                while (m_szLine != "</chart>")

// ... Restante do código ....

La función GetIdSubWinEA devolverá el número de la ventana en la que se encuentra el indicador, y esta llamada se ejecuta en muchos puntos diferentes. Tenemos dos soluciones para esto, la primera sería que en cada punto que se produzca esta llamada pondríamos algo similar a la modificación, que se ve a continuación, de la misma función vista anteriormente.

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
#ifdef def_INTEGRATION_CHART_TRADER
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
#else 
                                m_SubWindow = 0;
#endif 

// ... Restante do código ...

Esto solucionaría el problema, pero tendríamos que hacer tantos cambios que haría que el código fuera demasiado complicado de entender después de un tiempo, ya que habría demasiadas de estas directivas condicionales de compilación. Así que tengo una solución considerablemente más simple pero igualmente efectiva: Para "emular" esta y cualquier otra llamada por medio de la definición, sólo tendríamos que añadir el siguiente fragmento al código del sistema.

#ifndef def_INTEGRATION_CHART_TRADER
        #define GetIdSubWinEA() 0
        #define ExistSubWin() false
#endif 

Este sencillo código resolverá nuestro problema respecto a las llamadas. Con esto será suficiente, con unos pocos detalles más, para compilar nuestro indicador.

Nuestro segundo gran problema es con las rutinas involucradas con los eventos del ratón, no el ratón en sí, sino la clase C_Mouse.

Entendamos lo siguiente: cuando el Chart Trade formaba parte del EA, era posible acceder a las posiciones del ratón (entre otras cosas), pero si simplemente añadimos la clase C_Mouse en el indicador tendremos un conflicto de intereses entre el indicador y el EA. De momento no voy a mostrar cómo se resuelve este conflicto, pero vamos a tomar otra dirección, sólo temporalmente, hasta que se resuelvan las cosas y se consiga tener algo de la funcionalidad que había en el Chart Trade original.

Para ello tendremos que llevar algunas cosas de la clase C_Mouse a nuestro nuevo indicador. Pero no se preocupen es algo pequeño, el primer cambio es en la clase C_TemplateChart, en cuya rutina DispatchMessage haremos el cambio que se ve a continuación:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionDP(dt, p);
#else
                        {
                                int w;
                                ChartXYToTimePrice(Terminal.Get_ID(), (int)lparam, (int)dparam, w, dt, p);
                        }
#endif 
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")

// ... Restante do código ....

Añadiendo estas secciones resaltadas tendremos la misma funcionalidad que si la clase C_Mouse siguiera presente, y el siguiente cambio será en la clase C_ChartFloating donde también haremos algo muy parecido a lo visto anteriormente, pero ahí tenemos un código ligeramente diferente que se puede ver a continuación:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionXY(mx, my);
                        if ((Mouse.GetButtonStatus() & 0x01) == 1)
#else 
                        mx = (int)lparam;
                        my = (int)dparam;
                        if (((uint)sparam & 0x01) == 1)
#endif 
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)

// ... Restante do código ....

Ahora podemos compilar el indicador y tendremos como resultado algo que se ve en el video de abajo:



A pesar de todo aún no es funcional, y tenemos que tomar una decisión antes de hacerlo: dejaremos que el Chart Trade tenga las mismas capacidades que se encuentran en el EA, o podemos reducirlas para tener algo entre lo antiguo y lo que se encuentra en MetaTrader 5. Como soy radical en esto, voy a mantener el Chart Trade con prácticamente las mismas características que se encuentran en el EA. Ustedes pueden reducirlas si lo desean, esto es a su criterio.

Una vez tomada esta decisión, podemos pasar al siguiente tema, ya que el Chart Trade acaba de convertirse en un indicador.


2.1 - Cómo hacer funcional el indicador Chart Trade

Ahora la cosa se va a poner dura, así que pónganse cómodos y vamos a ver cómo hacer que este indicador sea funcional, y nos permita enviar órdenes, cerrar posiciones e incluso informarnos de los resultados de las operaciones. Puede parecer algo extremadamente complicado de hacer, pero no, es algo bastante sencillo, ya que el propio MetaTrader 5 nos da el camino a seguir, y lograr esto con el mínimo esfuerzo.

Lo que vamos a hacer aquí es lo mismo que hicimos en el artículo Desarrollo de un EA desde cero (Parte 16), allí utilizamos algunas de las capacidades de MetaTrader 5, para hacer un sistema Cliente-Servidor interno (dentro de la plataforma), con el fin de transferir datos entre diferentes procesos. Aquí haremos algo similar, sólo que el modelado será ligeramente diferente, ya que tendremos que tener una comunicación bidireccional, y ésta debe permanecer invisible para el usuario.

El método que vamos a utilizar no es el único posible, tenemos otras formas de hacerlo, una de ellas sería utilizar una DLL para poder hacer esta transferencia. No obstante vamos a usar las variables de MetaTrader 5, porque son, con diferencia, el camino más fácil de recorrer, mantener y modificar según sea necesario.

Una vez que hemos decidido que vamos a seguir este sendero, tenemos que tomar una decisión bastante importante: ¿Quién hará de servidor y quién, de cliente? Esta decisión implicará cómo debe implementarse realmente el sistema. Pero independientemente de eso, ya tendremos nuestro protocolo de mensajes construido, y se puede ver en el fragmento siguiente:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableLeverage      (_Symbol + "_Leverage")
#define def_GlobalVariableTake          (_Symbol + "_Take")
#define def_GlobalVariableStop          (_Symbol + "_Stop")
#define def_GlobalVariableResult        (_Symbol + "_Result")
#define def_GlobalVariableButton        (_Symbol + "_ButtonState")
//+------------------------------------------------------------------+
#define def_ButtonDTSelect              0x01
#define def_ButtonSWSelect              0x02
#define def_ButtonBuyMarket             0x04
#define def_ButtonSellMarket            0x08
#define def_ButtonClosePosition         0x10
//+------------------------------------------------------------------+

Y ya está, ni siquiera necesitamos definir quién hará de cliente y quién de servidor, pues ya tenemos definido nuestro protocolo de mensajes. Es importante que este protocolo esté definido desde el principio, porque así será más fácil desarrollar todo el resto.

Observen que haremos uso de 5 variables, para cada activo en el que esté presente el conjunto Chart Trade EA. Y los nombres de esas variables dependerán del activo en el que esté vinculado el conjunto, de esta manera podremos utilizar el conjunto en más de un activo al mismo tiempo, sin que nadie interfiera con nadie.

Ahora viene el detalle: ¿quién hará el trabajo del servidor? Ya que éste se encargará de crear este tipo de variables. Personalmente me parece más práctico poner el EA como servidor y dejar el Chart Trade como cliente, ya que la idea será tener siempre el EA en el gráfico y el Chart Trade, en algunos momentos si es necesario. De esta manera entonces el EA será responsable de crear 4 de las 5 variables, ya que una será la responsable de informar qué botón fue presionado, por lo que el Chart Trade es responsable de crear y mantener.

Así, nuestro flujo de datos es el siguiente:

  1. El EA creará las variables globales, que contendrán los valores iniciales que indican el factor de Apalancamiento, la Take Financiero y el Stop Financiero, eventualmente también creará una variable que informe cuál es el resultado del día, para que el Chart Trade pueda mostrarlo al usuario y no tenga que buscar esta información en otro lugar.
  2. El Chart Trade, eventualmente, creará una variable, que representa el valor de los botones presionados, esto le dirá al EA lo que debe hacer, como, por ejemplo, cerrar una posición abierta o hacer una compra o venta de mercado. Esta misma variable existirá, sólo y únicamente, durante este periodo, dejando de existir una vez que la solicitud sea completada por la EA.

Este flujo, a pesar de ser sencillo, ya garantizará que el Chart Trade pueda comunicarse con el EA, hasta el punto de hacer que todo el sistema envíe órdenes o cierre posiciones de la misma forma que se hacía antes.

Para garantizar que el Chart Trade sólo existirá en el gráfico si el EA está presente, tenemos que hacer algunas verificaciones, ya que no tiene sentido mantener el Chart Trade en el gráfico si el EA no está disponible para enviar las órdenes. Estas verificaciones se realizan en 2 momentos:

  • El primero es en la inicialización del indicador;
  •  El segundo es cuando ocurre cualquier evento del gráfico.

Pero veamos esto en el código, así será más sencillo de entender. Durante la inicialización se ejecuta el siguiente código:

#define def_SHORTNAME "CHART TRADE"
//+------------------------------------------------------------------+
int OnInit()
{
        long lparam = 0;
        double dparam = 0.0;
        string sparam = "";
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_SHORTNAME);
        if(!GlobalVariableGet(def_GlobalVariableLeverage, dparam)) return INIT_FAILED;
        Terminal.Init();
        Chart.AddThese("IDE(,,170, 215)");
        Chart.InitilizeChartTrade(dparam * Terminal.GetVolumeMinimal(), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), true);
        OnChartEvent(CHARTEVENT_OBJECT_ENDEDIT, lparam, dparam, sparam);

        return INIT_SUCCEEDED;
}

La línea resaltada evaluará una de las variables globales, en este caso la que indica el nivel de apalancamiento. Si esta variable no está presente, la inicialización fallará. Recordemos que el responsable de crear esta variable es el EA y no el Chart Trade, por ello, el indicador sabrá si el EA está presente o no en el gráfico, y cuando el EA finalice, él mismo removerá esta variable de MetaTrader 5, obligando a que el Chart Trade sea removido también, y esto se hace en un segundo punto, donde se evalúa esta misma condición, referente a la presencia o no de la variable global de apalancamiento.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

El punto indicado anteriormente ejecutará la comprobación con una frecuencia relativamente alta. Aunque es tentador poner un sistema de eventos OnTime en un indicador, esto no es aconsejable, ya que todos los indicadores utilizan el mismo thread de trabajo, y poner un evento OnTime en un indicador afectará a todos los demás si no se hace con extrema atención.

Pero en caso de que piensen que esta comprobación, mostrada arriba, se hace de manera que perturba el rendimiento general de la plataforma, pueden poner esta misma prueba dentro de un evento OnTime, pero sean conscientes de las consecuencias que esto traerá.

Independientemente de quién dispare la eliminación del indicador Chart Trade del gráfico, ésta se producirá en el mismo punto, y esto se ve justo debajo:

void OnDeinit(const int reason) 
{ 
        if (reason == REASON_INITFAILED)
        {
                Print("Não é possivel usar o Chart Trade. EA não esta presente no gráfico deste ativo...");
                if (!ChartIndicatorDelete(0, 0, def_SHORTNAME))
                        Print("Não foi possivel remover o Chart Trade do Gráfico.");
        }
}

La línea resaltada eliminará el indicador del gráfico, pero si no tiene éxito, un mensaje informará de ello. Estos mensajes se pueden ver en la caja de herramientas como se muestra a continuación:

De esta manera, siempre debemos estar al tanto de cualquier información presente en esta ventana.

Bueno, pero falta una última rutina antes de pasar a un análisis más profundo sobre los cambios realizados en la clase C_Chart_IDE, esta rutina se ve a continuación:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        double value;
        
        if (GlobalVariableGet(def_GlobalVariableResult, value))
        {
                GlobalVariableDel(def_GlobalVariableResult);
                Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, value, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        }
   
        return rates_total;
}

Lo que hace esta rutina es quedarse observando las variables globales. De este modo, de vez en cuando, cuando se cierra una posición, el EA creará una variable global, cuyo nombre se definirá en def_GlobalVariableResult. Cuando se crea esta variable y el nombre es el mismo que el Chart Trade está observando, se capturará el valor de esta, y se eliminará inmediatamente la variable, esto se hace para evitar que en caso de que el EA envíe una actualización antes de que se haya efectuado el tratamiento, esta actualización se pierda, pero al tener el valor capturado antes de la eliminación, podemos enviarlo a la clase del Chart Trade encargada de tratar los mensajes, así el valor que informa el EA se mostrará en el Chart Trade lo antes posible.

De esta manera terminamos la parte inicial para conseguir que el indicador Chart Trade sea funcional. Pero aún nos queda una segunda parte: los botones. Tenemos que hacerlos funcionales. Esto es algo sumamente sencillo de lograr en la rutina que maneja los mensajes en la clase C_Chart_IDE vemos el siguiente fragmento:

// ... Código anterior ...

case CHARTEVENT_OBJECT_CLICK:
        if (StringSubstr(sparam, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG)
	{
		Resize(-1);
		return;
	}
	sparam = StringSubstr(sparam, 9, StringLen(sparam));
	StringToUpper(sparam);
#ifdef def_INTEGRATION_CHART_TRADER
	if ((sparam == szMsgIDE[eBTN_SELL]) || (sparam == szMsgIDE[eBTN_BUY]))
		TradeView.ExecuteOrderInMarket(m_BaseFinance.Leverange, m_BaseFinance.FinanceTake, m_BaseFinance.FinanceStop, sparam == szMsgIDE[eBTN_BUY], m_BaseFinance.IsDayTrade);
	if (sparam == szMsgIDE[eBTN_CANCEL])
	{
		TradeView.CloseAllsPosition();
		ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
	}
#else 
	{
		union u00
		{
			double Value;
			ulong c;
		}u_local;
                                                        
		u_local.c = 0;
		if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; else
		if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonSellMarket; else
		if (sparam == szMsgIDE[eBTN_CANCEL])
		{
			u_local.c = def_ButtonClosePosition;
			ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
		}
                if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value);
	}
#endif 
	if (sparam == szMsgIDE[eCHECK_DAYTRADE]) InitilizeChartTrade(0, 0, 0, m_BaseFinance.IsDayTrade ? false : true);
	break;

//... Restante do código ....

Fíjense en que tenemos 2 códigos en este fragmento. Uno en AZUL que se utiliza cuando el Chart Trade está integrado en el EA, y otro en VERDE que es cuando el Chart Trade está presente como indicador.

Lo que nos interesa aquí es el código en VERDE. Esto creará una variable global y pondrá en ella el valor del estado de los botones. Así, si el operador ha cerrado una posición, el valor correspondiente al botón de cierre se colocará en la variable, y sólo este valor. Pero si está enviando una orden de mercado, este valor será diferente, y ahora será una combinación de otros dos valores: uno que indicará si la orden es de compra o de venta, y el otro si queremos hacer un Day Trade o una operación más larga, y eso es todo lo que Chart Trade indicará al EA.

IMPORTANTE: Este sistema es autoexcluyente, es decir, si hemos pulsado el botón de vender y antes de que el EA haga nada, pulsamos el de comprar, el EA realmente comprará, ya que el valor que indicaba que era vender se habrá perdido con la inclusión del valor que indica que debía comprar. Además, si solicitamos vender o comprar y ya tenemos una posición abierta y antes de que el EA realice la compra o venta, pulsamos cancelar, la posición se cerrará.

Hecha esta consideración podemos pasar al código del EA para ver cómo funcionará en este nuevo modelo.


2.2 - Modificamos el EA para que acepte mensajes del indicador Chart Trade

Esta parte será bastante sencilla, ya que todo lo que tendremos que hacer es ajustar algunos pequeños detalles, sin embargo recuerden lo siguiente: una vez cargados el EA y el Chart Trade, no modifique las variables globales ni los datos contenidos en el EA, para ello utilice el sistema de órdenes o el propio Chart Trade, de lo contrario puede tener problemas. Hay soluciones para algunos problemas y no hay solución para otros, pero por si acaso, utilicen las herramientas que están disponibles, no intenten complicar su vida como operador. 

En el artículo anterior Desarrollo de un EA comercial desde cero (Parte 29), cuando promovimos la eliminación del Chart Trade, hicimos algunos cambios, y parte de ellos serán deshechos. No necesitaremos modificar nada más relacionado con este tema. Pero como se dijo anteriormente: «hay cosas que tienen solución y otras que no», así en el siguiente tema de este artículo, arreglaremos algunos pequeños problemas en la relación entre el Chart Trade y el EA.

Pero veamos qué tendremos que deshacer y activar en el EA, para que haya algún nivel de comunicación entre éste último y el Chart Trade.

Primero modificaremos las siguientes entradas:

input int       user20      = 1;        //Fator de alavancagem
input double    user21      = 100;      //Take Profit ( FINANCEIRO )
input double    user22      = 81.74;    //Stop Loss ( FINANCEIRO )
input bool      EA_user23   = true;     //Day Trade ?

El valor que indica si el EA abrirá preferentemente una posición larga o corta no se modificará. Y esto debe hacerse en el Chart Trade o en una orden pendiente, que colocaremos en el gráfico. Ya he mostrado cómo hacerlo en artículos anteriores, así que vamos a continuar el trabajo de codificación. En este primer punto, necesitamos las siguientes modificaciones en el evento OnInit:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound.PlayAlert(C_Sounds::TRADE_ALLOWED);
                return INIT_FAILED;
        }
        
        Terminal.Init();

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Init(user32, user33, user30, user31);
        TimesAndTrade.Init(user41);
        EventSetTimer(1);
#endif 

        Mouse.Init(user50, user51, user52);
        
#ifdef def_INTEGRATION_CHART_TRADER
        static string   memSzUser01 = "";
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23);
        TradeView.Initilize();
   OnTrade();
#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
#endif 
   
        return INIT_SUCCEEDED;
}

Vemos que aquí añadimos las variables globales, que se utilizarán en la comunicación. Como había comentado antes, el EA debe entrar siempre antes del Chart Trade, de lo contrario tendremos el impedimento de la inicialización del indicador. Tengan en cuenta que los valores que serán utilizados inicialmente por Chart Trade están en EA. Y la inicialización estará completa, incluyendo si hubo alguna operación anterior, el valor acumulado también se pasa al Chart Trade de nuevo.

Noten un detalle importante: las variables creadas son de tipo temporal, porque no quiero de ninguna manera que si hacemos un volcado de datos del EA, estas variables se almacenen, puesto que no tienen ninguna utilidad después de un determinado periodo de tiempo, e incluso si llega a pasar algo y se cierra la plataforma, estas variables ya no existirán.

La siguiente adición que tendremos que hacer se ve justo debajo:

void OnDeinit(const int reason)
{
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Aunque las variables sean temporales, le pido al EA que las elimine a la fuerza. De este modo me aseguro de que el Chart Trade tampoco permanecerá en el gráfico. Hay un pequeño problema con este evento OnDeinit, pero hablaré de ello en el siguiente tema, así que veamos otro punto, uno bastante curioso. Puedes hacerlo de 2 maneras y los resultados serán CASI, y tenemos que enfatizar esta palabra, CASI idénticos, porque hay pequeñas diferencias que pueden hacer la diferencia. (Discúlpenme por el juego de palabras, es que no he podido resistirme... 😂👍)

¿Recuerdan que en el Chart Trade hay algunos botones y, que éstos envían mensajes al EA a través de una variable global? Pues bien, la rutina dentro del EA que responde adecuadamente a esos clics se ve a continuación:

inline void ChartTrade_ClickButton(void)
{
        union u00
        {
                double Value;
                ulong c;
        }u_local;
        
        if (GlobalVariableGet(def_GlobalVariableButton, u_local.Value))
        {
                GlobalVariableDel(def_GlobalVariableButton);
                                
                if (u_local.c == def_ButtonClosePosition) TradeView.CloseAllsPosition(); else
                        TradeView.ExecuteOrderInMarket(GlobalVariableGet(def_GlobalVariableLeverage), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), ((u_local.c & def_ButtonBuyMarket) == def_ButtonBuyMarket), ((u_local.c & def_ButtonDTSelect) == def_ButtonDTSelect));
                TradeView.Initilize();
        }
}

Esta rutina se declara como una rutina inline, es decir, debe ser colocada por el compilador en la posición donde se declara, esto hará que se ejecute lo más rápido posible.

El gran detalle es: ¿Dónde ponemos esta rutina? Pensemos por un momento... Vean que tenemos una verificación en ella, para que no se ejecute todo el tiempo, de esta manera tenemos 2 posibilidades. La primera es ponerla dentro del evento OnTick, la segunda es ponerla dentro del evento OnTime. Esta elección tiene que basarse en algún tipo de lógica, de lo contrario podríamos tener problemas.

Así que pensemos: Si ponemos esta rutina dentro del evento OnTick, ella se ejecutará a cada nuevo tick -procedente del servidor comercial- que reciba la plataforma. Esto parece ser bueno y adecuado, ya que esta rutina no se ejecutará muchas veces, solo en algunos momentos. Pero esto nos trae un problema, en caso de que la volatilidad del activo, que estamos operando, sea muy baja, la tasa de eventos OnTick será también muy baja, lo que significa que podemos tener problemas en cuanto al momento de activación del evento a través del clic en el Chart Trade.

Así que pensemos en la segunda opción: El hecho de que pongamos la rutina en el evento OnTime nos dará una buena seguridad respecto al momento en que se ejecutará esta rutina, dado que el evento OnTime se disparará con cierta regularidad. Pero cuando hagamos esto, tendremos que tener en cuenta que no podemos usar el evento OnTime para nada más que para ver las variables globales.

En este punto, hemos tomado una elección muy adecuada, ya que el EA sólo va a vigilar el mercado, y en el transcurso de los próximos artículos verás que esto se hará cada vez más evidente. Es entonces una buena opción poner la rutina dentro del evento OnTime, pero ahora viene una pregunta: El evento OnTime se acciona sólo cada segundo, ¿verdad?

En realidad es más común que se accione cada segundo, pero podemos establecer un tiempo más corto, para ello utilizaremos una función ligeramente diferente, EventSetMillisecondTimer. De esta manera podemos disparar el evento en un tiempo inferior a 1 segundo. Creo que 500 ms es suficiente para un amplio rango de casos, así que en el evento OnInit del EA tendremos que accionar la siguiente línea:

#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
        EventSetMillisecondTimer(500);
#endif 

Y no podemos olvidar terminar también este evento en la rutina OnDeinit, para ello basta con añadir la siguiente línea en el evento:

void OnDeinit(const int reason)
{
        EventKillTimer();

Ahora veamos cómo quedó el evento OnTime, que no es más que un código súper simple, del cual sólo se compilará la línea resaltada de abajo.

void OnTimer()
{
#ifndef def_INTEGRATION_CHART_TRADER
        ChartTrade_ClickButton();
#endif

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
#endif 
}

¡¿Pero eso es todo?! No... tenemos que resolver otro pequeño problema. Recordemos que cambiamos las variables de inicialización del EA y queremos extraer los datos del Chart Trade cada vez que se coloque una nueva orden pendiente en el gráfico. Pues bien para lograrlo, tenemos que cambiar un detalle en el código de la clase C_IndicatorTradeView, y esto se ve justo debajo en el siguiente fragmento:

        case CHARTEVENT_MOUSE_MOVE:
                Mouse.GetPositionDP(dt, price);
                mKeys   = Mouse.GetButtonStatus();
                bEClick  = (mKeys & 0x01) == 0x01;    //Clique esquerdo
                bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT Pressionada
                bKeySell = (mKeys & 0x08) == 0x08;    //CTRL Pressionada
                if (bKeyBuy != bKeySell)
                {
                        if (!bMounting)
                        {
#ifdef def_INTEGRATION_CHART_TRADER
                                m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
#else 
                                m_Selection.vol = GlobalVariableGet(def_GlobalVariableLeverage) * Terminal.GetVolumeMinimal();
                                valueTp = GlobalVariableGet(def_GlobalVariableTake);
                                valueSl = GlobalVariableGet(def_GlobalVariableStop);
                                m_Selection.bIsDayTrade = EA_user23;
#endif 

Ahora los códigos resaltados capturan los valores que se encuentran en las variables globales, de esta manera lo que se encuentre en el Chart Trade se colocará en el sistema de órdenes, De ese modo, podremos colocarlas con el mínimo de trabajo, el único detalle es respecto a que todas las órdenes pendientes seguirán el tiempo que el EA especifique, pero esto puede ser cambiado directamente en el gráfico, para más detalles véase Desarrollo de un EA desde cero (Parte 27), en este artículo se muestra cómo modificar las órdenes pendientes directamente en el gráfico, sin tener que pasar por Chart Trade.

Hay una pequeña modificación en el sistema de sonido, se agregaron sonidos en la clase C_Router, para garantizar que los avisos de entrada y cierre de posición se realicen en caso de que operemos a través del Chart Trade. Es algo sencillo, por eso lo comento, en caso de que se quiera quitar o modificar algo del sistema de sonido, se debe tener en cuenta que existen también otros puntos a considerar.

Antes de que terminemos, se dijo que el evento OnDeinit tiene un problema que vamos a arreglar, así que vamos al siguiente tema y arreglemos ese problema.


2.3 - Cómo evitar que el Chart Trade salga del gráfico prematuramente

Cuando hacemos cualquier cosa, ya sea cambiar el marco temporal del gráfico o los parámetros del EA, no importa, cualquier cosa, MetaTrader 5 generará un evento, el nombre de este evento es OnDeint. El hecho de que se dispare este evento, hace que todas las cosas deban ser reanalizadas, para que todo siga funcionando como se espera.

La mayoría de las veces, la activación de este evento no genera ningún problema, pero desde el momento en que estamos haciendo un sistema cliente-servidor y utilizamos variables globales para saber si el servidor ha dejado de funcionar, en este caso el servidor es EA, tendremos que entender cómo sortear ciertas situaciones.

La función que maneja el evento OnDeinit original tiene el siguiente código:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

La línea resaltada eliminará una variable global, que es precisamente la que utilizamos en el indicador Chart Trade para comprobar si el EA está o no en el gráfico, y esta comprobación se hace en el siguiente código:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

Es decir, cada vez que ocurra algo en el EA y se dispare el evento OnDeinit, dicha variable será eliminada, y antes de que el EA vuelva a crear la variable, puede que el indicador ya haya sido eliminado del gráfico, esto puede ser realmente atípico, y en algunos momentos esto ocurrirá, y en otros no, pero no podemos estar a merced de la suerte, tenemos que garantizar de alguna manera que las cosas funcionen como se espera.

Luego, buscando en la documentación, encontré la solución al problema, y ésta se puede ver en Razones de deinicialización, una vez que veamos estos códigos, podemos ajustar la rutina de manejo de eventos OnDeinit para que las variables no sean eliminadas a menos que de hecho el EA sea eliminado del gráfico.

Así que la solución, y el nuevo código de tratamiento fue como se muestra a continuación:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        break;
                default:                
                        GlobalVariableDel(def_GlobalVariableLeverage);
                        GlobalVariableDel(def_GlobalVariableTake);
                        GlobalVariableDel(def_GlobalVariableStop);
                        GlobalVariableDel(def_GlobalVariableResult);
                        GlobalVariableDel(def_GlobalVariableButton);
        };
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Es decir, ahora si solo estamos cambiando el marco temporal del gráfico o la forma de trazarlo, ya no tendremos el inconveniente de que el indicador Chart Trade se salga del gráfico, en cualquier otra situación puede salirse del gráfico, es que no tenemos la seguridad real de que todo va a resultar bien, por ello una vez cargado el EA y el Chart Trade, ya no tendrá sentido modificar los parámetros del EA.


3.0 - Conclusión

¿Vieron lo que puede hacer un poco de creatividad? A veces tenemos que resolver problemas que parecen insolubles.  Pero estudiando la documentación podemos encontrar la solución, por lo que es primordial consultarla, entenderla y probar las cosas, para que nuestras ideas se pongan en práctica.


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

Archivos adjuntos |
EA_-_p_Parte_30_u.zip (14532.23 KB)
Desarrollo de un EA comercial desde cero (Parte 31): Rumbo al futuro (IV) Desarrollo de un EA comercial desde cero (Parte 31): Rumbo al futuro (IV)
Seguiremos eliminando cosas del interior del EA. Sin embargo, este será el último artículo de esta serie. Lo último que se removerá en esta serie de artículos es el sistema de sonido. Tal vez esto los confunda si no han seguido estos artículos.
Desarrollo de un EA comercial desde cero (Parte 29): Plataforma parlante Desarrollo de un EA comercial desde cero (Parte 29): Plataforma parlante
En este artículo aprenderemos a hacer hablar a la plataforma MT5. ¿Qué tal si hacemos que el EA sea más divertido? Operar en los mercados financieros suele ser una actividad extremadamente aburrida y monótona, pero podemos hacerla un poco menos tediosa. Este proyecto podría ser peligroso en caso de que tengas un problema que te haga adicto, pero en realidad con las modificaciones todo el escenario podría ser más entretenido, menos aburrido.
Redes neuronales: así de sencillo (Parte 20): Autocodificadores Redes neuronales: así de sencillo (Parte 20): Autocodificadores
Continuamos analizando los algoritmos de aprendizaje no supervisado. El lector podría preguntarse sobre la relevancia de las publicaciones recientes en el tema de las redes neuronales. En este nuevo artículo, retomaremos el uso de las redes neuronales.
Desarrollo de un EA comercial desde cero (Parte 28): Rumbo al futuro (III) Desarrollo de un EA comercial desde cero (Parte 28): Rumbo al futuro (III)
Nuestro sistema de órdenes todavía falla en hacer una cosa, pero FINALMENTE lo resolveremos...