English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Desarrollando un EA comercial desde cero (Parte 14): Volume at Price (II)

Desarrollando un EA comercial desde cero (Parte 14): Volume at Price (II)

MetaTrader 5Sistemas comerciales | 4 julio 2022, 15:01
291 0
Daniel Jose
Daniel Jose

1.0 - Introducción

Nuestro EA ya tiene varios recursos que nos ayudan durante el trading, ellos se fueron añadiendo en el transcurso de los anteriores artículos hasta llegar aquí. A pesar de todo, este EA tiene pequeños problemas de visualización y de redimensionamiento. No son cosas que de hecho entorpezcan la negociación, sino que en algunos momentos contaminan la pantalla hasta que decidimos forzar una actualización de la misma, bueno más allá de esto nos faltan algunas cosas que serían información que puede ser muy valiosa para nosotros, son cosas puntuales que pueden ser necesarias.

Así que pongamos manos a la obra y empecemos a implementar estas nuevas mejoras, este artículo será muy interesante, puede no solo dirigirte a nuevas ideas y métodos para presentar la información sino que también ayudarte a corregir pequeños fallos en tus proyectos.


2.0 - Planificación e implementación de una nueva función en Volume At Price

2.1 - Planificación

Bueno, hay una cosa curiosa sobre el comercio. Muchas veces vemos que el mercado se acumula en ciertas regiones de precios y cuando se accionan los stops, ya sea del lado de la compra o del lado de la venta, hay un rápido movimiento del precio, que se puede ver en el Times & Trade, lo presenté en los artículos Times & Trade (I) y Times & Trade (II). En ellos mostré cómo crear un sistema gráfico alternativo para leer y analizar el flujo de órdenes ejecutadas, pues bien, si observas te darás cuenta de que en algunos de estos momentos el precio tiende a volver a la región de acumulación, de donde no quiere salir en ese momento. Pero cuando se observa el indicador de Volume at Price es difícil identificar qué tan reciente fue la permanencia del precio en esa región específica. El indicador en cuestión fue implementado en el artículo Adición de Volume at price (I), con ese indicador tenemos la posibilidad de analizar movimientos más o menos recientes, simplemente modificando el punto de partida del análisis, lo que se hace ajustando el valor del objeto que se está indicando en la figura siguiente:

Pero esto no es, de hecho, práctico, ya que nos quedamos con el marco temporal del gráfico principal, es decir, si estás con el gráfico en un marco temporal de 60 minutos, no podrás analizar movimientos por debajo de este tiempo, tendrás que cambiar a un marco temporal inferior para poder ajustar el punto de análisis. Pero en el caso de la negociación de contratos de futuros, normalmente la mayoría de los operadores, de hecho, lo hacen en tiempos inferiores como 5, 10 o incluso 30 minutos, por lo que no parece ser un problema ajustar el punto de análisis. Sin embargo, como expliqué anteriormente, a veces el precio sale de una región de acumulación no porque quiera, sino por haber disparado los stops de las posiciones, y este retorno normalmente ocurre en tiempos inferiores a 5 minutos, y en el gráfico se verá una vela con una sombra larga, ya sea superior o inferior. Es en estos casos que el Price Action dice que lo que ocurrió fue un sondeo del mercado, este tipo de movimiento se puede ver abajo en las velas indicadas por las flechas:

    Movimiento típico de sondeo por parte de los compradores o activación de stop por parte de los vendidos

  Movimiento típico de sondeo por parte de los vendedores o activación de stop por parte de los comprados

Este tipo de movimiento suele ocurrir muchas veces, y analizar el volumen que se ha producido en cada una de las bandas de precios es muy importante para saber si el mercado está tanteando o si en realidad está girando la tendencia en una dirección, y esto no se puede hacer de forma adecuada, o mejor dicho, de forma rápida por el indicador de volumen propuesto anteriormente.

Pero podemos hacer una pequeña modificación en la clase objeto del indicador y así tendremos una indicación más clara de lo que está pasando, y esto se mostrará como un rastro de la negociación que ha ocurrido en un período de tiempo determinado.


2.2 - Aplicación

Bueno lo primero que hay que hacer es analizar cuanto tiempo se quiere tener de rastreo, si será de 60, 45, 30, 19, 7 o 1 minuto, pero independientemente de esto te aconsejo que uses valores que sean lo suficientemente divisibles, para que el sistema de rastro pueda ser realmente útil, bueno por razones prácticas lo implementaré usando 30 minutos de rastro, entonces definimos esto en la siguiente línea de código:

#define def_MaxTrailMinutes     30

¡Y por qué 30 minutos? Bueno, en realidad, el sistema de rastro se hará cada minuto, pero el tiempo máximo que tendremos de rastro será de 30 minutos, es decir, el rastro sobre el volumen será siempre de 30 minutos, cuando hayan pasado 31 minutos, el rastro del primer minuto de negociación ya no será mostrado. ¡Y cómo se consigue esto? Esto se hace mediante el sistema de captura que se muestra a continuación:

inline void SetMatrix(MqlTick &tick)
{
        int pos;
                                
        if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return;
        pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2;
        pos = (pos >= 0 ? pos : (pos * -1) - 1);
        if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else
        if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume;
        m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell);
        m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell;
        m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal);
        m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos));
        m_Infos.Momentum = macroGetMin(tick.time);
        m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
        if (m_Infos.memMomentum != m_Infos.Momentum)
        {
                for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0;
                m_Infos.memMomentum = m_Infos.Momentum;
        }
        m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume;
}

Las líneas resaltadas han sido añadidas al código original de la clase objeto, con esto tenemos la captura de del rastro del volumen, mientras que las líneas en el fragmento de abajo aseguran que el rastro se hará como se espera.

m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);

Bien, ya tenemos el sistema de captura de rastro creado, ahora tenemos que tomar una nueva decisión, recordemos que el rastro se captura cada 1 minuto, por lo que podemos presentarlo de forma que veamos el volumen en cada rango de precios en el periodo de 1 minuto, siempre y cuando hagamos el gráfico de esta forma, se podría pensar en hacer algo como lo que se muestra a continuación:

  Los tonos más claros representan volúmenes más recientes, podría ser una buena idea...

Aunque parece una buena idea, cuando el volumen es bajo o el movimiento es muy rápido, incluso con un volumen expresivo para el momento, puede que de hecho no sea visible, ya que se trazará ajustándose al volumen máximo que se ejecutó hasta ese momento, por lo que puedes decidir hacer un trazado un poco diferente para resolver esto, entonces quedaría como se muestra a continuación:

Cada color representa un periodo concreto, en el rastro del volumen....

Esto puede ayudarte a analizar bandas muy estrechas en el volumen, corrigiendo en algunos momentos el problema que se ve en el primer caso. Pero seguiríamos teniendo el problema de ajuste que se produce cuando el volumen puede no ser tan expresivo en relación al volumen total en otro punto. Además hay que elegir muy bien los colores para cada periodo para que no se haga confuso el análisis en momentos de fuerte negociación.

Por lo tanto, aquí voy a utilizar un modelo más simple, que de nuevo se puede ajustar para analizar los movimientos de diferentes períodos, como he mostrado anteriormente, esto sin ningún problema. Pero tenga en cuenta los problemas que también he dejado claro que va a pasar, esto es a tu discreción, por lo que la imagen de del rastro será como se muestra a continuación:

Aquí tenemos el rastro puro, en él debemos observar tanto el Times & Trade como el Price Action para realmente tener una idea de lo que está sucediendo....

Pues bien, de un modo u otro, la única función que debe modificarse para cambiar la presentación del volumen es la que se muestra a continuación:

void Redraw(void)
{
        uint            x, y, y1, p;
        double  reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0);
        double  desl = Terminal.GetPointPerTick() / 2.0;
        ulong           uValue;
                                
        Erase();
        p = m_WidthMax - 8;
        for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++)
        {
                if (m_InfoAllVaP[c0].nVolTotal == 0) continue;
                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y);
                y1 = y + Terminal.GetHeightBar();
                FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency));
                FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency));
                uValue = 0;
                for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0];
                FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency));
        }
        C_Canvas::Update();
};

Y para ser más exactos, sólo el código resaltado necesita ser modificado. Puedes jugar con esto hasta que obtengas el resultado deseado, nada más en la clase necesita ser modificado aparte de este fragmento resaltado arriba, y al compilar y ejecutar EA en el gráfico obtendrás algo como la imagen de abajo:



3.0 - Solución al problema de Render

Aunque el código no tiene en realidad problemas, hay una falla al redimensionar el gráfico, es decir, al cambiar de grande a cualquier otro tamaño se vuelve a maximizar, algunos objetos se pierden un poco y pueden no tener el comportamiento esperado, posicionándose en lugares equivocados. Entonces, para arreglar esto, no sirve estar rascándose la cabeza y desesperarse, sino que hay que modificar el código del EA que se ve a continuación, este es el código original que se puede ver en artículos anteriores:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        ChartRedraw();
}

La modificación es sencilla, sin embargo puedes pensar: «¡bien, no veo nada malo ahí, el código está correcto?». A primera vista tampoco veo nada de malo. El código se había quedado con un fallo de ejecución, y al ir a añadir algunas funciones extra me di cuenta del problema, que es precisamente el descrito anteriormente. Para solucionarlo hay que cambiar el código de arriba por el de abajo:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

Parece una tontería, pero para entenderlo es necesario mirar el código de la función resaltada. Ahora que el sistema ha sido corregido podemos pasar al siguiente paso.


4.0 - Adición de recursos adicionales

La función que vamos a añadir ahora es algo muy sencillo, y puede que muchos no vean una gran razón para que se implemente, pero su implementación ayudará mucho a la hora de trabajar con órdenes, ya sea al posicionar, mover o simplemente observar el indicador de Volume at Price.

Lo primero que hay que hacer es cambiar la clase de la que formará parte el código de ajuste de la línea de precios, este código sale de la clase C_OrderView y pasa a la clase C_Terminal, pero también sufre estas pequeñas modificaciones por esto, ya que empezará a tratar con las variables de la propia clase, el fragmento de abajo muestra cómo quedará el nuevo código.

double AdjustPrice(const double arg)
{
        double v0, v1;
                                
        if(m_Infos.TypeSymbol == OTHER) return arg;
        v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg));
        v1 = fmod(round(v0), 5.0);
        v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0);
        return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0);
};

Hecho esto podemos crear la nueva clase del EA, esta será la clase C_Mouse, en definitiva esta clase objeto se encargará y será la base de los eventos relacionados con el ratón, así que vamos a ver como queda en este punto del desarrollo. Pero primero veamos la estructura de clases actual de nuestro EA, esto se muestra en la imagen de abajo:

 Se necesita una nueva estructura para el próximo sistema que se va a implementar...

Bien, vista la estructura anterior, vamos a diseccionar el código de la clase objeto C_Mouse empezando por la declaración de las variables que se puede ver a continuación:

class C_Mouse
{
        private  :
                struct st00
                {
                color   cor01,
                        cor02,
                        cor03;
                string  szNameObjH,
                        szNameObjV,
                        szNameObjT,
                        szNameObjI,
                        szNameObjB;
                }m_Infos;
                struct st01
                {
                        int      X,
                                 Y;
                        datetime dt;
                        double   price;
                        uint     ButtonsStatus;
                }Position;

Vean que hay pocas variables necesarias en esta fase de desarrollo, el siguiente punto que merece nuestra atención se ve en el fragmento siguiente:

~C_Mouse()
{
// ... Código interno ...
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, true);
}

Este código restablece tanto el CHART_CROSSHAIR_TOOL como deshabilita el uso de eventos de ratón por parte del gráfico, lo que significa que MT5 ya no se preocupará de enviar eventos de este tipo al gráfico, sino que serán manejados por la propia plataforma.

También tenemos dos rutinas muy comunes cuando tenemos programación en la que vamos a controlar el ratón, se pueden ver a continuación:

inline void Show(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, m_Infos.cor01);
}
//+------------------------------------------------------------------+
inline void Hide(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, clrNONE);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, 0, 0);
}

Una cosa interesante es que de hecho el ratón no desaparece de la pantalla, sólo los objetos que creamos desaparecerán de la pantalla, y cuando "encendemos" el ratón sólo la línea de precios será realmente visible, esto puede parecer curioso, pero tiene sus usos en algunos puntos específicos de EA, uno de estos puntos es la clase de objeto C_OrderView en el pasaje que se muestra resaltado en el código de abajo:

inline void MoveTo(uint Key)
{
        static double local = 0;
        int w = 0;
        datetime dt;
        bool bEClick, bKeyBuy, bKeySell;
        double take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Clique esquerdo
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT Pressionada
        bKeySell = (Key & 0x08) == 0x08;    //CTRL Pressionada
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell) Mouse.Hide(); else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); else local = 0;
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Pero fíjense en la línea inmediatamente superior a la línea resaltada:

Mouse.GetPositionDP(dt, price);

Esta línea capturará los valores de posición del ratón. El código que informará de estos valores se ve a continuación:

inline void GetPositionDP(datetime &dt, double &price)
{
        dt = Position.dt;
        price = Position.price;
}

Pero no sólo eso, en algunos casos necesitamos las coordenadas cartesianas del gráfico, en términos de posición de pantalla. Para obtener estos valores también se implementa otra rutina que se puede ver a continuación:

inline void GetPositionXY(int &X, int &Y)
{
        X = Position.X;
        Y = Position.Y;
}

Pero volviendo a la clase C_OrderView tenemos un punto interesante que también merece atención:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo(Mouse.GetButtonStatus());
                        break;

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

}

La rutina MoveTo se puede ver justo arriba en este mismo artículo, también forma parte de la clase C_OrderView, pero lo importante es la rutina Mouse.GetButtonsStatus, esta rutina devolverá el estado de los botones y teclas vinculados a eventos del ratón.

Esta rutina Mouse.GetButtonStatus se puede ver a continuación:

inline uint GetButtonStatus(void) const
{
        return Position.ButtonsStatus;
}

Es una sola línea que devuelve una variable que contiene los valores registrados del último evento del ratón, y ya llegaremos a este código que registrará este valor, pero antes, veamos el código de inicialización del ratón, porque es precisamente esto, tenemos que decirle al EA que queremos inicializar el ratón y que el EA se encargará de manejar varias cosas relacionadas con el ratón a partir de este momento. El código responsable de esto se ve en el fragmento siguiente:

// ... Outras coisas ....

input group "Mouse"
input color     user50 = clrBlack;      //Linha de Preço
input color     user51 = clrDarkGreen;  //Estudo Positivo
input color     user52 = clrMaroon;     //Estudo Negativo
//+------------------------------------------------------------------+

// ... Informações gerais ....

//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);

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

Vea que definimos 3 colores para ser usados por el sistema, estos colores deben ser elegidos para que los datos sean claros y notorios en el gráfico. Pero echemos un vistazo al código Mouse.Init para entender un poco más, este se ve a continuación.

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        if (m_Infos.szNameObjH != NULL) return;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        m_Infos.szNameObjH = "H" + (string)MathRand();
        m_Infos.szNameObjV = "V" + (string)MathRand();
        m_Infos.szNameObjT = "T" + (string)MathRand();
        m_Infos.szNameObjB = "B" + (string)MathRand();
        m_Infos.szNameObjI = "I" + (string)MathRand();
//---
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjH, OBJ_HLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjV, OBJ_VLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjT, OBJ_TREND, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjB, OBJ_BITMAP, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjI, OBJ_TEXT, 0, 0, 0);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TOOLTIP, "\n");
//---
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_WIDTH, 2);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_BMPFILE, "::" + def_Fillet);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_BACK, false);
        Hide();
        Show();
}

Este código no tiene nada muy anormal, solo estamos creando algunos objetos para ser usados por la clase, pero el fragmento resaltado puede generar dudas, ya que si lo buscas en la clase no encontrarás ningún punto donde él se esté declarando, esto porque en realidad está declarado en el código del archivo del EA junto con las declaraciones de otro recurso, en el futuro agruparé todo esto en un archivo, pero por ahora será así. Así que si miras el código del EA encontrarás las siguientes líneas:

#define def_Resource    "Resources\\SubSupport.ex5"
#define def_Fillet      "Resources\\Fillet.bmp"
//+------------------------------------------------------------------+
#resource def_Resource
#resource def_Fillet

Es decir, ahí está el tal recurso utilizado en la línea que se destacó en el código de inicialización del ratón.

Bueno llegamos a nuestro pináculo dentro de esta clase, esto es llamado por el siguiente fragmento:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

Entonces, la línea resaltada en el código del EA llamará el siguiente código:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Position.X = (int)lparam;
                        Position.Y = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);
                        key = (uint) sparam;
                        if ((key & 0x10) == 0x10)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
                                b1 = 1;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 1))
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
                                b1 = 2;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 2))
                        {
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                                ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                        }
                        if (((key & 0x01) != 0x01) && (b1 == 2))
                        {
                                b1 = 0;
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                Hide();
                                Show();
                        }
                        Position.ButtonsStatus = (b1 == 0 ? key : 0);
                        break;
        }
}

El código de arriba, no, y repito, no es un código de implementación completa, dará soporte y resolverá sólo las cuestiones básicas para el EA hasta esta etapa de desarrollo. Para entenderlo notemos una cosa en el código de inicialización del ratón, ahí tenemos la siguiente línea:

ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);

Lo que hace esta línea es evitar que se muestre la CRUZ de análisis cuando hacemos clic con el botón central del ratón. ¡Pero por qué impedir que se cree la cruz? Para entenderlo, veamos la siguiente animación:

Noten que estamos en el WDO, se mueve de 0,5 en 0.5, pero al intentar hacer un análisis, vemos que no tenemos mucha precisión, y en algunos casos tenemos que tener cierta precisión para realizar un análisis, y esta cruz que nos proporciona MT5 no es lo suficientemente adecuada para casos concretos, tenemos que recurrir a un nuevo sistema, así que hacemos que MT5 deje de crear la cruz por nosotros cuando el EA se está ejecutando, lo que hacemos es crear nuestra propia cruz para hacer el análisis, con esto podemos añadir datos y valores que sean relevantes para nosotros, y presentarlos de una forma que nos parezca más adecuada. Esto se puede ver a continuación, donde se demuestra el resultado de utilizar el sistema de modelado de datos cuando el EA está funcionando.


Vean que los valores indicados corresponden a los valores exactos del movimiento, y tenemos una indicación visual si el valor fue positivo, en cuyo caso la indicación se torna verde, o negativo, que hace que la indicación se torne roja, el rastro se crea y tanto el punto inicial como el final son fácilmente visibles, pero como dije, el sistema no es un sistema completo, todavía, pero puedes implementar mejoras en él si quieres y necesitas, hasta que salga una nueva versión de la clase C_Mouse puedes mejorar esta versión y tener más datos que creas necesarios tener, pero para ello, necesitas entender cómo suceden las cosas, así que vamos a ver más de cerca el código de los mensajes de la clase C_Mouse. 


4.0.1 - Entendiendo el código DispathMessage de la clase C_Mouse

Bien, el código comienza capturando y estableciendo los valores de las variables de posición del ratón, esto lo hace el fragmento de abajo:

Position.X = (int)lparam;
Position.Y = (int)dparam;
ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);

Los valores de posición son informados por la propia plataforma MT5, pero estos valores provienen del sistema operativo, por lo que están en coordenadas de pantalla, es decir X e Y, pero necesitamos convertirlos a coordenadas de gráfico y para ello utilizamos la rutina que nos proporciona MQL5, facilitándonos mucho la vida.

Una vez hecho esto, movemos las líneas de precio y tiempo

ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);

pero la línea de tiempo es inicialmente invisible para nosotros, por lo que no podemos verla en el gráfico inicialmente. Después de esto capturamos el estado del ratón, en este punto

key = (uint) sparam;

Hasta aquí todo bien, ahora vamos a hacer lo siguiente, vamos a probar si el botón del medio fue presionado, y si este es el caso, vamos a la línea de tiempo, que se hará visible en el gráfico, y esto se logra con el código de abajo:

if ((key & 0x10) == 0x10)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
        b1 = 1;
}

Para ello, utilizamos una variable static para almacenar este evento, por lo que a partir de ahora ningún otro evento será aceptado y procesado por el EA, que se encargará del estudio que queramos hacer en el gráfico. Pero el estudio en realidad sólo se inicia cuando pulsamos el botón izquierdo del ratón, es decir, estoy utilizando la misma forma de funcionamiento ya conocida por todos los usuarios de la plataforma MT5 para realizar los estudios, y esto es lo más adecuado, ya que tener que aprender una nueva forma de ejecutar los estudios de líneas puede hacer que abandones el sistema. El EA entonces espera este clic izquierdo y esto se hace mediante el siguiente fragmento

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

cuando se produce el clic, el sistema de movimiento del gráfico se bloquea, entonces presentamos una línea de tendencia para indicar los puntos del estudio de líneas, y el sistema pasa al siguiente paso y esto se informa con un nuevo valor en b1. Ahora es en realidad la parte en la que puedes añadir más información o poner la que consideres más relevante, aquí sólo estoy mostrando el sistema, pero siéntete libre de poner lo que quieras y sientas la necesidad, y esto debe hacerse en el siguiente punto:

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Noten la línea resaltada, porque ahí es exactamente donde se muestra y se calcula el valor que ves en la pantalla del gráfico, ahí puedes añadir varias cosas, siéntete libre de hacerlo. El funcionamiento de esta parte hace que los datos se calculen y presenten mientras se pulsa el botón izquierdo, por lo que tendrá el mismo comportamiento que tiene por defecto la plataforma MT5, pero con los valores ajustados y modelados según tus deseos y necesidades.

Lo siguiente que hay que hacer es una prueba más, que se ve a continuación

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}

Tras soltar el botón izquierdo del ratón, el gráfico se libera y puede ser arrastrado, al igual que se ocultan todos los elementos utilizados para crear el estudio y volvemos a tener visible sólo la línea de precios. Y para terminar tenemos un último fragmento que se puede ver en la línea de abajo:

Position.ButtonsStatus = (b1 == 0 ? key : 0);

Si no se está llamando ningún estudio, el estado de los botones del ratón se almacena y se puede utilizar en cualquier otra parte del EA, pero si se ha llamado un estudio, se utiliza un valor NULL como dato de estado, por lo que no será posible crear órdenes, ni cambiar su posición.

En el video de abajo, se puede notar cómo funciona realmente este rastro y cómo se está montando y ajustando el volumen en la pantalla, este indicador ayuda mucho, es bueno que aprendas a usarlo de la manera correcta, porque junto con Times & Trade forma el dúo del ruido en el tape reading que es una de las formas más avanzadas de operar en el mercado.




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

Archivos adjuntos |
EA_-_Mouse.zip (5986.31 KB)
DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control "Panel", parámetro AutoSize DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control "Panel", parámetro AutoSize
En este artículo, crearemos un objeto básico para todos los objetos de la biblioteca WinForms y comenzaremos a implementar la propiedad AutoSize del objeto WinForms "Panel", es decir, el cambio automático del tamaño para que se ajuste a su contenido interno.
DoEasy. Elementos de control (Parte 4): Elemento de control "Panel", parámetros Padding y Dock DoEasy. Elementos de control (Parte 4): Elemento de control "Panel", parámetros Padding y Dock
En este artículo, implementaremos el funcionamiento de los parámetros de panel Padding (rellenado/márgenes internos en todos los lados del elemento) y Dock (la forma en que el objeto se ubica dentro del contenedor).
Desarrollando un EA comercial desde cero (Parte 15): Acceso a los datos en la web (I) Desarrollando un EA comercial desde cero (Parte 15): Acceso a los datos en la web (I)
Cómo acceder a los datos en la web dentro de MetaTrader 5. En la web tenemos varios sitios y lugares en los que una gran y vasta cantidad de información está disponible y accesible para aquellos que saben dónde buscar y cómo utilizar mejor esta información.
Desarrollando un EA comercial desde cero (Parte 13): Times And Trade (II) Desarrollando un EA comercial desde cero (Parte 13): Times And Trade (II)
Hoy vamos a construir la segunda parte del sistema Times & Trade para analizar el mercado. En el artículo anterior Times & Trade ( I ) presenté un sistema alternativo para organizar un gráfico de manera que tengamos un indicador que nos permita interpretar las operaciones que se han ejecutado en el mercado lo más rápido posible.