Desarrollando un EA comercial desde cero (Parte 12): Time and Trade (I)

27 mayo 2022, 09:49
Daniel Jose
2
712

Introducción

La lectura de flujos, más conocida como Tape Reading, es una modalidad de comercio utilizada por algunos operadores en diversos momentos. Es extremadamente eficiente, y, cuando se utiliza bien, promueve ganancias constantes, de una manera mucho más segura y consistente de lo que se lograría utilizando sólo la conocida Price Action, que es la lectura de las velas pura y dura; pero hacer uso del Tape Reading en la forma en que se presenta es algo muy complicado y agotador, que nos exige mucho en términos de concentración y atención, y a medida que pasa el tiempo, comenzamos a estar sujetos a cometer errores en la lectura, dado el nivel de exigencia durante todo este proceso.

El gran problema de la lectura de flujos es la cantidad de información que tenemos que tener en cuenta durante el periodo que estamos utilizando este método de comercio, veamos a continuación un momento típico del Tape Reading:


El verdadero problema es que en este análisis, tenemos que mirar el precio y lo que pasó con él, y mirar estos números en los mini contratos no es algo muy práctico, por esta razón no solemos mirar el contenido del flujo en los mini contratos, damos preferencia a hacer la lectura en los contratos completos, ya que son los que se mueven y bloquean el mercado. Esto es lo que realmente ocurre, por lo que el sistema es el que se muestra a continuación, algo más fácil de interpretar y seguir.


Pero aun así este sistema es muy agotador de seguir, es algo que exige mucha atención para entender el movimiento, y cuando ocurre la activación de posiciones stop, la cosa se pone mucho más tensa, y podemos perder parte del movimiento, ya que el desplazamiento de la información en la pantalla se hace muy rápido, lo que significa que es algo muy complicado que exige mucha experiencia.


Planificación

Bueno, pero la plataforma MT5 tiene un sistema alternativo, e incluso en los mini contratos, que hace que la lectura sea mucho más efectiva y fácil de seguir, veamos cómo se ven las cosas cuando se usan en los mini contratos:

Veamos que la interpretación es mucho más simple, pero por las razones que hablé antes, es más apropiado usar los contratos completos, así se vería como se muestra a continuación:


Pero veamos que los datos de las operaciones están siendo contaminados con los movimientos de modificación de los valores BID y ASK, las propias operaciones están representadas por bolitas. Las rojas representan ventas, las azules son compras y las verdes son órdenes directas, y además de que tenemos información no necesaria para la lectura en sí, tenemos otro problema, el sistema está separado del gráfico con el que estamos operando realmente, lo que nos obliga a mirar dos pantallas. Esto por un lado es una ventaja, pero en algunos casos puede ser un tormento, por lo que la propuesta aquí es crear un sistema que sea sencillo de leer como esta alternativa, al mismo tiempo que nos permita tener este indicador directamente en el gráfico con el que estemos operando.


Implementación

Lo primero que vamos a hacer es modificar la clase C_Terminal, para que podamos acceder al activo del contrato completo, y esto se hace agregando el siguiente código:

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1, sz2;
        datetime dt = TimeLocal();
                
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
        m_Infos.szFullSymbol = _Symbol;
        m_Infos.TypeSymbol = ((sz0 == "WDO") || (sz0 == "DOL") ? WDO : ((sz0 == "WIN") || (sz0 == "IND") ? WIN : OTHER));
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz2 = (sz0 == "WDO" ? "DOL" : (sz0 == "WIN" ? "IND" : sz0));
        sz1 = (sz2 == "DOL" ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

// ... Código da classe ...

inline string GetFullSymbol(void) const { return m_Infos.szFullSymbol; }

Con la adición de las líneas resaltadas, ya tenemos acceso al activo correcto que usaremos en el Time & Trade que vamos a ensamblar. Una vez terminado esto, podemos empezar a crear la clase objeto que soportará nuestro Time & Trade, esta clase contendrá algunas rutinas muy interesantes, lo primero que hay que hacer es generar una subventana que contendrá el indicador que vamos a construir. Esto es sencillo de hacer, pero por razones de practicidad no vamos a usar el sistema de subventanas que ya estamos usando, tal vez en el futuro lo cambie, pero por ahora, manejaremos el Time & Trade en una ventana separada del sistema de indicadores, lo que significa que tendremos que crear todo un soporte para ello.

Bueno, lo primero que debemos hacer es crear un nuevo archivo de soporte para tener un nombre diferente para el indicador. Y en lugar de crear archivos encima de archivos, hagamos algo un poco más elegante. Vamos a modificar el archivo de soporte para tener más posibilidades. Entonces, el nuevo archivo de soporte se muestra a continuación:

#property copyright "Daniel Jose 07-02-2022 (A)"
#property version   "1.00"
#property description "Este archivo sólo sirve como soporte de indicadores en SubWin"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
input string user01 = "SubSupport";             //Short Name
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, user01);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Los puntos resaltados son las modificaciones que tenemos que hacer en el archivo original, pero ahora tenemos que hacer una modificación en nuestro código del EA. Se creará una nueva clase, que se puede ver en su totalidad a continuación:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_FnSubWin
{
        private :
                string  m_szIndicator;
                int             m_SubWin;
//+------------------------------------------------------------------+
                void Create(const string szIndicator)
                        {
                                int i0;
                                m_szIndicator = szIndicator;
                                if ((i0 = ChartWindowFind(Terminal.Get_ID(), szIndicator)) == -1)
                                        ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource, szIndicator));
                                m_SubWin = i0;
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_FnSubWin()
                        {
                                m_szIndicator = NULL;
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
                ~C_FnSubWin()
                        {
                                Close();
                        }
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                if (m_SubWin >= 0) ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator);
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(const string szIndicator = NULL)
                        {
                                if ((szIndicator != NULL) && (m_SubWin < 0)) Create(szIndicator);
                                return m_SubWin;
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_SubWin >= 0; }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

Lo que hace esta clase es reemplazar a la clase C_SubWindow por lo que ahora esta será la clase que soportará la creación de subventanas en el gráfico, para entender como funciona esta clase tendremos que echar un vistazo rápido a la nueva clase C_SubWindow que también se ve en su totalidad a continuación:

#include "C_ChartFloating.mqh"
#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>
//+------------------------------------------------------------------+
class C_SubWindow : public C_ChartFloating
{
//+------------------------------------------------------------------+
        private :
                C_FnSubWin      m_fnSubWin;
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                ~C_SubWindow()
                        {
                                Close();
                        }       
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                m_fnSubWin.Close();
                                CloseAlls();
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(void)
                        {
                                return m_fnSubWin.GetIdSubWinEA("SubWinSupport");
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_fnSubWin.ExistSubWin(); }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

Observemos que en esta clase tenemos una definición del indicador que se utilizará para soportar los templates. Esto está marcado en el código resaltado, pero ahora viene el salto del gato, si usamos otro nombre en lugar de SubWinSupport, la clase C_FnSubWin buscará un indicador diferente, y este es el truco que usaré para evitar crear archivos de indicadores, simplemente informaré a la clase C_FnSubWin cual debe ser el nombre corto del indicador que debe ser usado para otro propósito, de esta manera no estaré limitado a un número de subventanas o archivos de indicadores innecesarios, usados solo para crear la subventana del EA.

Una vez hecho esto, podemos comenzar a crear la clase C_TimeAndTrade.


La clase C_TimesAndTrade

La clase objeto C_TimesAndTrade se compone de varias piezas pequeñas, cada una responsable de algo específico, el código que se ve a continuación es lo primero que el EA llama para esta clase:

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

Este código examinará si la subventana de soporte ya existe, si no existe, se creará, pero echemos un vistazo más de cerca al siguiente código de soporte inicial de la clase se muestra a continuación:

inline void CreateCustomSymbol(void)
{
        m_szCustomSymbol = "_" + Terminal.GetFullSymbol();
        SymbolSelect(Terminal.GetFullSymbol(), true);
        SymbolSelect(m_szCustomSymbol, false);
        CustomSymbolDelete(m_szCustomSymbol);
        CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());
        CustomRatesDelete(m_szCustomSymbol, 0, LONG_MAX);
        CustomTicksDelete(m_szCustomSymbol, 0, LONG_MAX);
        SymbolSelect(m_szCustomSymbol, true);
};

Este código creará un símbolo personalizado y restablecerá todos los datos dentro de este mismo símbolo, para que el contenido del símbolo aparezca dentro de una ventana que crearemos más adelante, necesitamos que aparezca primero en el Market Watch y esto se hace usando esta línea:

SymbolSelect(m_szCustomSymbol, true);

El símbolo personalizado se creará en la siguiente ubicación Custom\Robot\<Nombre del Símbolo> y sus datos iniciales serán proporcionados por el símbolo original, esto se logra utilizando el siguiente fragmento de código:

CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());

Básicamente esto es todo, y cuando agregamos la clase al EA y lo ejecutamos con el fragmento a continuación:

// ... Código do EA

#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>

// ... Código do EA

input group "Times & Trade"
input   int     user041 = 2;    //Escala
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
C_TimesAndTrade TimesAndTrade;
//+------------------------------------------------------------------+
int OnInit()
{
// ... Código do EA

        TimesAndTrade.Init(user041);
        
        OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}

Obtenemos la siguiente imagen:


Y eso era exactamente lo que se esperaba, ahora vamos a agregar los valores de las transacciones ejecutadas en el gráfico _DOLH22, este gráfico se ensamblará de manera que refleje esto, las transacciones ejecutadas para tener una representación gráfica del Times & Trade, esta representación se realizará utilizando el modelo de velas japonesas, esto se debe a la facilidad de uso de este patrón. Para hacer esto, primero debemos hacer algunas cosas, necesitamos conectar y sincronizar el símbolo, y esto se hace mediante la siguiente rutina:

inline void Connect(void)
{
        switch (m_ConnectionStatus)
        {
                case 0:
                        if (!TerminalInfoInteger(TERMINAL_CONNECTED)) return; else m_ConnectionStatus = 1;
                case 1:
                        if (!SymbolIsSynchronized(Terminal.GetFullSymbol())) return; else m_ConnectionStatus = 2;
                case 2:
                        m_LastTime = TimeLocal();
                        m_MemTickTime = macroMinusMinutes(60, m_LastTime) * 1000;
                        m_ConnectionStatus = 3;
                default:
                        break;
        }
}

Esta rutina hace exactamente lo que propone hacer, es decir, verifica si el terminal está conectado y luego sincroniza el símbolo. Después de eso, podemos comenzar a capturar los valores y presentarlos en la pantalla, pero necesitamos hacer un pequeño cambio en el código de inicialización, el cambio se ve resaltado en el siguiente código:

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
                m_ConnectionStatus = 0;
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

Una vez hecho esto, veamos la rutina de captura.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {
                for (p1 = 0, p2 = 0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++);
                for (int c0 = p1, c1 = 0; c0 < i0; c0++)
                {
                        if (Tick[c0].volume == 0) continue;
                        iflag = 0;
                        iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? 1 : 0);
                        iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? 1 : 0);
                        if (iflag == 0) continue;
                        Rates[c1].high = Tick[c0].ask;
                        Rates[c1].low = Tick[c0].bid;
                        Rates[c1].open = Tick[c0].last;
                        Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
                        Rates[c1].time = m_LastTime;
                        p2++;
                        c1++;
                        m_LastTime += 60;
                }
                CustomRatesUpdate(m_szCustomSymbol, Rates, p2);
                m_MemTickTime = Tick[i0 - 1].time_msc;
        }
}

Lo que hace la rutina anterior es capturar absolutamente todos los ticks negociados, para verificar si son ticks de venta o de compra. Si son ticks de modificaciones de BID o ASK, es decir, sin volumen, no se almacena ninguna información, así como los ticks que son órdenes directas, que no tienen ninguna influencia en el movimiento del precio, a pesar de ser muchas veces la causa del movimiento, ya que hay agentes del mercado que fuerzan el precio a un determinado valor sólo para ejecutar una orden directa para, poco después, dejar que el precio se mueva libremente. Bueno estos ticks, usados para modificar el BID y el ASK, serán usados en otra versión que mostraré en el próximo artículo, ya que tienen una importancia secundaria en el sistema general. Después de probar el tipo de transacción, tenemos una secuencia de líneas que es importante que comprendamos, estas líneas que se encuentran en el fragmento a continuación y construirán 1 vela por tick que haya pasado por el sistema de análisis y que deberá almacenarse.

Rates[c1].high = Tick[c0].ask;
Rates[c1].low = Tick[c0].bid;
Rates[c1].open = Tick[c0].last;
Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
Rates[c1].time = m_LastTime;

El máximo y el mínimo de la vela indican el spread en el momento en el que se negoció el tick, es decir, ese valor que existía entre el BID y el ASK serán las mechas de la vela creada, el valor de apertura de la vela es el precio en el que realmente la operación salió. Ahora observe cuidadosamente la línea resaltada en el fragmento, cuando se negocia un tick, tenemos un volumen, esta línea creará un pequeño ajuste en este volumen, para que la escala no se desborde. Podemos ajustar los valores según nos convenga analizar, dependiendo del activo en cuestión, esto es a discreción de cada uno.

Ahora un último detalle que es el tiempo, cada vela corresponderá a 1 minuto, ya que no es posible graficar valores menores a este. Entonces cada una se mantendrá en su posición correspondiente, cada 1 minuto, esto no es un tiempo real, este tiempo es virtual, no confundamos el tiempo entre las operaciones con el tiempo gráfico, las operaciones pueden pasar en milisegundos, no la información gráfica que se trazará cada 1 minuto en la escala gráfica. Se puede utilizar cualquier otro valor, pero éste por ser el más pequeño posible, facilita mucho la programación. El resultado de este sistema se puede ver a continuación:

Veamos que la lectura es perfectamente posible y la interpretación es bastante sencilla, es cierto que el flujo de órdenes era muy lento en el momento de la captura, pero creo fue suficiente para entender la idea.

Una última información sobre este sistema, veamos las figuras a continuación:

Nótese que hay 4 configuraciones diferentes, que se pueden ver en el sistema, pero ¡¿por qué?! La razón se verá con más detalle en el siguiente artículo, que le ayudará a entender por qué hay 4 configuraciones de Times & Trade, pero aquí ya tenemos un sistema ya funcional, y puede ser que de esta manera ya sea suficiente para que se utilice de forma intensiva, pero si uno entiende lo que está pasando y lo que hace que se generen configuraciones de 4 velas, se podrá sacar un provecho mucho mayor de este sistema, y quién sabe, tal vez este se vuelva nuestro indicador principal...


Conclusión

Aquí creamos un sistema de Times & Trade para utilizarlo en nuestro EA con el fin de analizar el Tape Reading con la misma agilidad que con el sistema alternativo presente en MT5, pero lo hicimos creando un sistema de gráficos, en lugar de leer y tratar de entender una enorme cantidad de números y valores. En el próximo artículo completaremos el sistema con la información faltante, ya que para hacerlo necesitaremos agregar varias cosas nuevas al código de nuestro EA.



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

Archivos adjuntos |
EA_-_Times_e_Trade.zip (5982.95 KB)
Luis Antonio Perdomo Martínez
Luis Antonio Perdomo Martínez #:
Excelente 
Si es exactamente bueno para generar dinero y no tener pérdida, deberían usar ese sistema,  principalmente si es automático. 
Gráficos en la biblioteca DoEasy (Parte 98): Desplazamiento de los puntos de anclaje de los objetos gráficos estándar ampliados Gráficos en la biblioteca DoEasy (Parte 98): Desplazamiento de los puntos de anclaje de los objetos gráficos estándar ampliados
En el presente artículo, continuaremos el desarrollo de los objetos gráficos estándar extendidos, y crearemos la funcionalidad necesaria para desplazar los puntos de pivote de los objetos gráficos compuestos usando los puntos de control para gestionar las coordenadas de los puntos de pivote del objeto gráfico.
Desarrollando un EA comercial desde cero (Parte 11): Sistema de órdenes cruzadas Desarrollando un EA comercial desde cero (Parte 11): Sistema de órdenes cruzadas
Creación de un sistema de órdenes cruzadas. Hay una clase de activos que les hace la vida muy difícil a los comerciantes, estos son los activos de contratos futuros, y ¿por qué le hacen la vida difícil al comerciante?
Gráficos en la biblioteca DoEasy (Parte 99): Desplazando un objeto gráfico extendido con un punto de control Gráficos en la biblioteca DoEasy (Parte 99): Desplazando un objeto gráfico extendido con un punto de control
En el último artículo, implementamos la posibilidad de desplazar los puntos de control de un objeto gráfico extendido usando formularios de gestión. Ahora vamos a desplazar el objeto gráfico compuesto usando un punto (formulario) para gestionar el objeto gráfico.
Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados
¿Cómo acceder a los indicadores personalizados directamente en el EA? A un EA comercial solo se le sacará partido realmente si se puede usar indicadores personalizados en él, de lo contrario, será solo un conjunto de códigos e instrucciones.