Português
preview
Desarrollo de un sistema de repetición (Parte 52): Esto complica las cosas (IV)

Desarrollo de un sistema de repetición (Parte 52): Esto complica las cosas (IV)

MetaTrader 5Ejemplos | 24 julio 2024, 14:07
90 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Desarrollo de un sistema de repetición (Parte 51): Esto complica las cosas (III), hicimos algunos cambios en nuestro antiguo indicador de mouse para que fuera utilizado de manera adecuada en nuestro sistema de repetición/simulación. En ese mismo artículo, demostré de manera práctica las diferencias entre obtener el ID de un gráfico mediante ChartOpen, una función que utilizan los programas para indicar a MetaTrader 5 que abra un gráfico, y obtener el mismo ID de un gráfico ya abierto, pero usando la función ChartID.

Creo que quedó bastante clara esa diferencia, y las posibilidades a largo plazo, para poder hacer un uso mucho más amplio de la programación mediante MQL5.

Pues bien, a pesar de todos esos cambios realizados en el indicador de mouse y en el indicador de control, surgieron algunas dificultades para integrar, o mejor dicho, para hacer que el indicador de mouse y el indicador de control tuvieran una interacción adecuada. El motivo fue un fallo por mi parte, ya que durante el proceso de desarrollo del indicador de mouse ignoré algunos detalles que hicieron que la interacción no se produjera de forma adecuada.

Por lo tanto, en este artículo corregiremos eso, pero no te preocupes, es algo bastante simple de hacer, aunque muy necesario. Una vez realizada esta corrección, también podrás utilizar este mismo indicador de mouse en tus proyectos personales. Así, tendrás un sistema de estudio personalizado y una forma segura de interactuar con el gráfico. Una vez que el indicador de mouse esté en modo estudio, no permitirá que se interactúe con otros objetos presentes en el gráfico. Pero para esto será necesario que lo integres en tu código localmente.

Para ilustrar cómo debe realizarse esta integración, ya que haremos lo mismo con otras partes del sistema de repetición/simulación, utilizaremos el indicador de control. No obstante, este indicador de control ha sido modificado para permitir un sistema más adecuado, ya que anteriormente estábamos utilizando el sistema base del MQL5. Pero usaremos el MQL5 de una manera un poco más profunda, así tendremos algunas posibilidades que antes no nos era posible obtener.

La interacción que lograremos en este artículo aún será básica, pero será suficiente para que comprendas cómo integrar el indicador de mouse en tus programas y así tener un sistema de estudios personalizado que, al mismo tiempo, no interactúe de forma errática con los objetos colocados en el gráfico. Empecemos, pues, este artículo viendo el primer tema, en el que abordaremos las modificaciones que debemos hacer en el indicador de mouse.


Mejoras sobre el indicador de Mouse

Muy bien, como todo el código (y mantendré las cosas así por ahora) se colocará en el artículo y completamente en su totalidad. Quizás imagines que pueda estar siendo replicado en exceso. No obstante, de esta manera, como el código se está colocando, es más fácil explicar los detalles involucrados. Así, si el código hace referencia a algo que no esté presente aquí, deberás buscar las referencias en los artículos anteriores.

De esta manera, tendré completa y absoluta certeza de que realmente estás siguiendo las explicaciones y comprendiendo cómo se desarrolla el código. Dada esta breve explicación, veamos el código del indicador de mouse. Puedes verlo justo abajo:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.52"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/es/articles/11925"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. //+------------------------------------------------------------------+
015. C_Study *Study     = NULL;
016. //+------------------------------------------------------------------+
017. input long user00  = 0;                                    //ID
018. input C_Study::eStatusMarket user01 = C_Study::eAuction;   //Market Status
019. input color user02 = clrBlack;                             //Price Line
020. input color user03 = clrPaleGreen;                         //Positive Study
021. input color user04 = clrLightCoral;                        //Negative Study
022. //+------------------------------------------------------------------+
023. C_Study::eStatusMarket m_Status;
024. int m_posBuff = 0;
025. double m_Buff[];
026. //+------------------------------------------------------------------+
027. int OnInit()
028. {
029.    ResetLastError();
030.    Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04);
031.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
032.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
033.    {
034.            MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
035.            OnBookEvent((*Study).GetInfoTerminal().szSymbol);
036.            m_Status = C_Study::eCloseMarket;
037.    }else
038.            m_Status = user01;
039.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
040.    ArrayInitialize(m_Buff, EMPTY_VALUE);
041.    
042.    return INIT_SUCCEEDED;
043. }
044. //+------------------------------------------------------------------+
045. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
046. {
047.    m_posBuff = rates_total - 5;
048.    (*Study).Update(m_Status);      
049.    
050.    return rates_total;
051. }
052. //+------------------------------------------------------------------+
053. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
054. {
055.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
056.    SetBuffer();
057.    
058.    ChartRedraw((*Study).GetInfoTerminal().ID);
059. }
060. //+------------------------------------------------------------------+
061. void OnBookEvent(const string &symbol)
062. {
063.    MqlBookInfo book[];
064.    C_Study::eStatusMarket loc = m_Status;
065.    
066.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
067.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
068.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
069.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
070.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
071.    if (loc != m_Status) (*Study).Update(m_Status);
072. }
073. //+------------------------------------------------------------------+
074. void OnDeinit(const int reason)
075. {
076.    if (reason != REASON_INITFAILED)
077.    {
078.            if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
079.                    MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
080.    }
081.    delete Study;
082. }
083. //+------------------------------------------------------------------+
084. inline void SetBuffer(void)
085. {
086.    uCast_Double Info;
087.    
088.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
089.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
090.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
091.    m_Buff[m_posBuff + 1] = Info.dValue;
092.    Info._int[0] = (*Study).GetInfoMouse().Position.X_Adjusted;
093.    Info._int[1] = (*Study).GetInfoMouse().Position.Y_Adjusted;
094.    m_Buff[m_posBuff + 2] = Info.dValue;
095.    Info._int[0] = (*Study).GetInfoMouse().Position.X_Graphics;
096.    Info._int[1] = (*Study).GetInfoMouse().Position.Y_Graphics;
097.    m_Buff[m_posBuff + 3] = Info.dValue;
098.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
099.    m_Buff[m_posBuff + 4] = Info.dValue;
100. }
101. //+------------------------------------------------------------------+

Código fuente del Indicador de Mouse

Probablemente no notes las diferencias entre este código y los anteriores. Como ya dije, los cambios son muy simples y sutiles, pero están ahí.

El primero de ellos, y tal vez el único, es en el índice del buffer que se va a escribir desde el indicador. Puedes notarlo al observar en la línea 47 del código que el valor de desplazamiento, que antes era de cuatro, ahora es de cinco. ¿Por qué este cambio? Este incremento en una unidad es necesario para adaptarse a una cobertura más amplia. Pero para comprender mejor, veamos la función responsable de montar el buffer del indicador en la línea 84.

Probablemente, no notarás diferencia en esta función con respecto a las vistas en el pasado. De hecho, una vez más, los cambios son muy sutiles. Pero es necesario que los comprendas para poder sacar provecho de ellos en tus proyectos personales, en los que tal vez desees integrar este indicador en tu modelo específico.

Específicamente, a partir de la línea 92 las cosas empiezan a ser diferentes, aunque no tanto. ¿Por qué estoy declarando dos variables X y dos variables Y? ¿Y por qué una se llama Adjusted y la otra Graphics? Pues bien, este es precisamente el punto. Antes el indicador solo devolvía una variable gráfica ajustada en términos de tiempo y precio. Pero esto no es adecuado en todos los casos. Existen situaciones en las que necesitamos de hecho el valor de la coordenada gráfica y no el valor ajustado. Para satisfacer ambas situaciones, el indicador devolverá ambos valores, así podrás hacer el mejor uso posible de ellos.

Ten en cuenta también que en la posición cero del buffer tendremos el valor del precio y en la posición uno, el valor del tiempo. Decidí mantener esto por motivos de retrocompatibilidad con otras cosas que ya uso personalmente. Sin embargo, no es necesario que leas el buffer tal y como fue construido. Recuerda que existe una forma más sencilla en un nivel más alto que consiste en usar la clase del Mouse, pero si aún así deseas leer los datos del buffer directamente, debes entender cómo se construyó. Por tanto, comprender esta función presente en la línea 84 es primordial.

Muy bien, pero los cambios no se quedan solo ahí. Debes comprender que al hacer un cambio en el código, este podría afectar a gran parte del resto. A pesar de todo, en la clase responsable de crear y realizar los estudios, el cambio no fue tan drástico, pero aún así merece algún tipo de explicación. El código de la clase de estudios puede verse justo a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
010. //+------------------------------------------------------------------+
011. class C_Study : public C_Mouse
012. {
013.    private :
014. //+------------------------------------------------------------------+
015.            struct st00
016.            {
017.                    eStatusMarket Status;
018.                    MqlRates      Rate;
019.                    string        szInfo;
020.                    color         corP,
021.                                  corN;
022.                    int           HeightText;
023.            }m_Info;
024. //+------------------------------------------------------------------+
025.            const datetime GetBarTime(void)
026.                    {
027.                            datetime dt;
028.                            u_Interprocess info;
029.                            int i0 = PeriodSeconds();
030.                            
031.                            if (m_Info.Status == eInReplay)
032.                            {
033.                                    if (!GlobalVariableGet(def_GlobalVariableServerTime, info.df_Value)) return ULONG_MAX;
034.                                    if ((dt = info.ServerTime) == ULONG_MAX) return ULONG_MAX;
035.                            }else dt = TimeCurrent();
036.                            if (m_Info.Rate.time <= dt)
037.                                    m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0;
038. 
039.                            return m_Info.Rate.time - dt;
040.                    }
041. //+------------------------------------------------------------------+
042.            void Draw(void)
043.                    {
044.                            double v1;
045.                            
046.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
047.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
048.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
049.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
050.                            v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2);
051.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
052.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
053.                            v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2);
054.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
055.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
056.                    }
057. //+------------------------------------------------------------------+
058.    public  :
059. //+------------------------------------------------------------------+
060.            C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
061.                    :C_Mouse(IdParam, szShortName, corH, corP, corN)
062.                    {
063.                            if (_LastError != ERR_SUCCESS) return;
064.                            ZeroMemory(m_Info);
065.                            m_Info.Status = eCloseMarket;
066.                            m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
067.                            m_Info.corP = corP;
068.                            m_Info.corN = corN;
069.                            CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise);
070.                            CreateObjectInfo(2, 53, def_ExpansionBtn2);
071.                            CreateObjectInfo(58, 53, def_ExpansionBtn3);
072.                    }
073. //+------------------------------------------------------------------+
074.            void Update(const eStatusMarket arg)
075.                    {
076.                            datetime dt;
077.                            
078.                            switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
079.                            {
080.                                    case eCloseMarket : m_Info.szInfo = "Closed Market";
081.                                            break;
082.                                    case eInReplay    :
083.                                    case eInTrading   :
084.                                            if ((dt = GetBarTime()) < ULONG_MAX)
085.                                            {
086.                                                    m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
087.                                                    break;
088.                                            }
089.                                    case eAuction     : m_Info.szInfo = "Auction";
090.                                            break;
091.                                    default           : m_Info.szInfo = "ERROR";
092.                            }
093.                            Draw();
094.                    }
095. //+------------------------------------------------------------------+
096. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
097.                    {
098.                            C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
099.                            if (id == CHARTEVENT_MOUSE_MOVE) Draw();
100.                    }
101. //+------------------------------------------------------------------+
102. };
103. //+------------------------------------------------------------------+
104. #undef def_ExpansionBtn3
105. #undef def_ExpansionBtn2
106. #undef def_ExpansionBtn1
107. #undef def_ExpansionPrefix
108. #undef def_MousePrefixName
109. //+------------------------------------------------------------------+

Código fuente de la clase C_Study

Básicamente, en esta clase los únicos cambios reales ocurrieron en la función Draw, que se encuentra en la línea 42. Dichos cambios buscan mantener la lógica de estudios basada en el tiempo y el precio. Sin embargo, los objetos cuyo sistema de coordenadas es del tipo gráfico provocan este error. Este tipo de objeto se explicó cuando se creó este indicador de ratón hace algunos artículos, en esta misma secuencia. En ese momento, expliqué el motivo y cómo trabajar con coordenadas de precio-tiempo y coordenadas gráficas del tipo X-Y.

Para mantener la retrocompatibilidad, utilizamos las coordenadas gráficas ajustadas. Esto se puede ver en las líneas 46 a 48. Sin embargo, si estás usando otros objetos que necesitan usar coordenadas gráficas y quieres que sigan las coordenadas de Precio-Tiempo, simplemente deberás hacer uso de las coordenadas gráficas ajustadas, de la misma forma que se muestra aquí. Si cambias este sistema ajustado por el sistema gráfico, que no estará ajustado, el estudio puede quedar ligeramente diferente.

Tal vez sea adecuado que experimentes esto, así quedará más claro lo que acabo de decir.

Pero para terminar esta parte referente al indicador de mouse, veamos la clase responsable de mantener el sistema base. Así comprenderás mejor la diferencia entre las coordenadas gráficas ajustadas y las no ajustadas. Después pasaremos al código del indicador de control para entender el motivo de todos estos cambios. Puedes ver el código de la clase del mouse completo justo abajo. Espero que no sea necesario modificarlo una vez más.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. #include "Interprocess.mqh"
006. //+------------------------------------------------------------------+
007. #define def_MousePrefixName "MouseBase_"
008. #define def_NameObjectLineH def_MousePrefixName + "H"
009. #define def_NameObjectLineV def_MousePrefixName + "TV"
010. #define def_NameObjectLineT def_MousePrefixName + "TT"
011. #define def_NameObjectStudy def_MousePrefixName + "TB"
012. //+------------------------------------------------------------------+
013. class C_Mouse : public C_Terminal
014. {
015.    public  :
016.            enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
017.            enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
018.            struct st_Mouse
019.            {
020.                    struct st00
021.                    {
022.                            int      X_Adjusted,
023.                                     Y_Adjusted,
024.                                     X_Graphics,
025.                                     Y_Graphics;
026.                            double   Price;
027.                            datetime dt;
028.                    }Position;
029.                    uint    ButtonStatus;
030.                    bool    ExecStudy;
031.            };
032. //+------------------------------------------------------------------+
033.    protected:
034.            enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
035. //+------------------------------------------------------------------+
036.            void CreateObjectInfo(int x, int w, string szName, color backColor = clrNONE) const
037.                    {
038.                            if (m_Mem.szShortName != NULL) return;
039.                            CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE);
040.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
041.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
042.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
043.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
044.                            ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
045.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
046.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
047.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
048.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
049.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
050.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
051.                    }
052. //+------------------------------------------------------------------+
053.    private :
054.            enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
055.            struct st01
056.            {
057.                    st_Mouse Data;
058.                    color    corLineH,
059.                             corTrendP,
060.                             corTrendN;
061.                    eStudy   Study;
062.            }m_Info;
063.            struct st_Mem
064.            {
065.                    bool     CrossHair,
066.                             IsFull;
067.                    datetime dt;
068.                    string   szShortName;
069.            }m_Mem;
070.            bool m_OK;
071. //+------------------------------------------------------------------+
072.            void GetDimensionText(const string szArg, int &w, int &h)
073.                    {
074.                            TextSetFont("Lucida Console", -100, FW_NORMAL);
075.                            TextGetSize(szArg, w, h);
076.                            h += 5;
077.                            w += 5;
078.                    }
079. //+------------------------------------------------------------------+
080.            void CreateStudy(void)
081.                    {
082.                            if (m_Mem.IsFull)
083.                            {
084.                                    CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
085.                                    CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
086.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
087.                                    CreateObjectInfo(0, 0, def_NameObjectStudy);
088.                            }
089.                            m_Info.Study = eStudyCreate;
090.                    }
091. //+------------------------------------------------------------------+
092.            void ExecuteStudy(const double memPrice)
093.                    {
094.                            double v1 = GetInfoMouse().Position.Price - memPrice;
095.                            int w, h;
096.                            
097.                            if (!CheckClick(eClickLeft))
098.                            {
099.                                    m_Info.Study = eStudyNull;
100.                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
101.                                    if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
102.                            }else if (m_Mem.IsFull)
103.                            {
104.                                    string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
105.                                            MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)));
106.                                    GetDimensionText(sz1, w, h);
107.                                    ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                          
108.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
109.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
110.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
111.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
112.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));                          
113.                                    ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
114.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
115.                            }
116.                            m_Info.Data.ButtonStatus = eKeyNull;
117.                    }
118. //+------------------------------------------------------------------+
119.    public  :
120. //+------------------------------------------------------------------+
121.            C_Mouse(const long id, const string szShortName)
122.                    :C_Terminal(id),
123.                    m_OK(false)
124.                    {
125.                            m_Mem.szShortName = szShortName;
126.                    }
127. //+------------------------------------------------------------------+
128.            C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
129.                    :C_Terminal(id)
130.                    {
131.                            if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown);
132.                            if (_LastError != ERR_SUCCESS) return;
133.                            m_Mem.szShortName = NULL;
134.                            m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
135.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
136.                            ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
137.                            ZeroMemory(m_Info);
138.                            m_Info.corLineH  = corH;
139.                            m_Info.corTrendP = corP;
140.                            m_Info.corTrendN = corN;
141.                            m_Info.Study = eStudyNull;
142.                            if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
143.                                    CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
144.                    }
145. //+------------------------------------------------------------------+
146.            ~C_Mouse()
147.                    {
148.                            if (!m_OK) return;
149.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false);
150.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false);
151.                            ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
152.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
153.                    }
154. //+------------------------------------------------------------------+
155. inline bool CheckClick(const eBtnMouse value) 
156.                    {
157.                            return (GetInfoMouse().ButtonStatus & value) == value;
158.                    }
159. //+------------------------------------------------------------------+
160. inline const st_Mouse GetInfoMouse(void)
161.                    {
162.                            if (m_Mem.szShortName != NULL)
163.                            {
164.                                    double Buff[];
165.                                    uCast_Double loc;
166.                                    int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
167.                                    
168.                                    ZeroMemory(m_Info.Data);
169.                                    if (CopyBuffer(handle, 0, 0, 5, Buff) == 5)
170.                                    {
171.                                            m_Info.Data.Position.Price = Buff[0];
172.                                            loc.dValue = Buff[1];
173.                                            m_Info.Data.Position.dt = loc._datetime;
174.                                            loc.dValue = Buff[2];
175.                                            m_Info.Data.Position.X_Adjusted = loc._int[0];
176.                                            m_Info.Data.Position.Y_Adjusted = loc._int[1];
177.                                            loc.dValue = Buff[3];
178.                                            m_Info.Data.Position.X_Graphics = loc._int[0];
179.                                            m_Info.Data.Position.Y_Graphics = loc._int[1];
180.                                            loc.dValue = Buff[4];
181.                                            m_Info.Data.ButtonStatus = loc._char[0];
182.                                            IndicatorRelease(handle);
183.                                    }
184.                            }
185. 
186.                            return m_Info.Data;
187.                    }
188. //+------------------------------------------------------------------+
189. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
190.                    {
191.                            int w = 0;
192.                            static double memPrice = 0;
193.                            
194.                            if (m_Mem.szShortName == NULL) switch (id)
195.                            {
196.                                    case (CHARTEVENT_CUSTOM + ev_HideMouse):
197.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
198.                                            break;
199.                                    case (CHARTEVENT_CUSTOM + ev_ShowMouse):
200.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
201.                                            break;
202.                                    case CHARTEVENT_MOUSE_MOVE:
203.                                            ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (int)lparam, m_Info.Data.Position.Y_Graphics = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
204.                                            if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
205.                                            m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
206.                                            ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X_Adjusted, m_Info.Data.Position.Y_Adjusted);
207.                                            if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
208.                                            m_Info.Data.ButtonStatus = (uint) sparam;
209.                                            if (CheckClick(eClickMiddle))
210.                                                    if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
211.                                            if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
212.                                            {
213.                                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
214.                                                    if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
215.                                                    m_Info.Study = eStudyExecute;
216.                                            }
217.                                            if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
218.                                            m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
219.                                            break;
220.                                    case CHARTEVENT_OBJECT_DELETE:
221.                                            if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
222.                                            break;
223.                            }
224.                    }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef def_NameObjectLineV
229. #undef def_NameObjectLineH
230. #undef def_NameObjectLineT
231. #undef def_NameObjectStudy
232. //+------------------------------------------------------------------+

Código fuente de la clase C_Mouse

Puedes notar que la cosa aquí puede parecer tensa. Pero no te dejes llevar por la primera impresión. Vamos a los detalles iniciales. Bien, los includes presentes en las líneas 4 y 5 pueden ser obtenidos en los artículos pasados. Ya que los mismos no sufrieron ningún cambio, no tiene sentido replicarlos aquí nuevamente.

Si descendemos un poco más, en las líneas 22 a 25 encontramos las declaraciones de las variables que hemos estado observando en los códigos anteriores. Hay que prestar atención a que forman parte de una estructura y que esta es pública. Al ser así, podremos utilizar esta misma estructura más adelante, lo que hará que el código sea más legible y de mayor nivel. Estas mismas variables solo se utilizan nuevamente en las líneas 110 y 111 con el fin de promover un posicionamiento adecuado para algunos objetos.

Pero es en la línea 157 donde la cosa se pone de hecho interesante. Pero es en la línea 157 donde de hecho se pone interesante. ¿Recuerdas cuando mencioné, en este mismo artículo, que no necesitas saber cómo se montó el buffer, que podemos usar la clase de mouse para obtener los valores que queremos? Pues bien, es precisamente usando esta función de la línea 157 que lo hace posible. Esta función es capaz de adecuarse y responder correctamente a una solicitud de lectura del buffer, retornando la información a través de la estructura que se mencionó hace poco. De esta forma, la programación se vuelve mucho más sencilla.

Sin embargo, observa que, de la misma manera que se creó el buffer, aquí debemos ir en la dirección inversa con el fin de obtener la información que está presente en el buffer. Entonces, si todo ocurre de manera correcta, la función GetInfoMouse devolverá los datos relativos al posicionamiento y los clics del mouse. Estos datos se pueden utilizar en cualquier programa que estés desarrollando o en el propio indicador de mouse. La interfaz es la misma, lo que facilita mucho el trabajo de mantenimiento y comprensión del código.

Después de esta función de retorno de información del mouse, tenemos otra, quizá la más importante de todas, ya que responderá a las interacciones que ocurran con el mouse. Esta función, DispatchMessage, se encuentra en la línea 186. Pero básicamente es igual que antes, con solo algunas mejoras que hacen justo lo que necesitamos.

Vamos a entender algo: MetaTrader 5 utiliza un sistema de mensajes muy parecido al de Windows. Por esta razón, los mensajes se transmiten a nuestro programa casi de la misma forma que se haría si se estuviera ejecutando en Windows. Saber programar en Windows en estos casos ayuda bastante. Pero aunque la información se repase de la manera necesaria, muchas veces está en el tipo incorrecto o en un formato extraño para quienes no están familiarizados con la programación.

Así, cuando MetaTrader 5 envía un evento de mouse a nuestro programa, compacta los datos del mouse en el propio mensaje. Esto se puede ver en la línea 200, donde realmente ocurre que MetaTrader 5 repasa exactamente lo que Windows le informa. Así, MetaTrader 5 no nos informa de las coordenadas del mouse basadas en el precio y el tiempo, sino en términos gráficos. En este momento, tenemos las coordenadas gráficas que necesitamos, que no están ajustadas y representan la posición del mouse en la ventana. No en el gráfico, sino en la ventana. Sin embargo, es preciso explicar algo aquí. Cuando digo "ventana", me refiero a las ventanas gráficas. Para comprender esto, es necesario saber programación Windows, y no es el objetivo de este artículo.

Una vez que tenemos esta posición, que se nos informa, podemos usar MQL5 para convertirla en posiciones en términos de precio y tiempo. Por esta razón, los objetos que usan estas coordenadas para ser posicionados pueden quedar ligeramente fuera del punto, sin tener así una conexión directa con las barras presentes en el gráfico. Para corregir esto, usamos las líneas 201, que ajusta el precio, y la línea 202, que ajusta el tiempo. De esta manera, establecemos la correspondencia entre la posición y la barra del gráfico.

Pero, como en algunos momentos necesitamos que los valores X e Y estén ajustados al precio y tiempo, usamos la línea 203. De esta forma, ahora tendremos los valores ajustados. Antes no se hacía esta distinción entre el valor gráfico ajustado y el valor no ajustado. Sin embargo, al intentar manipular las cosas e integrar el indicador de mouse con el indicador de control, fue necesario hacer esta distinción. Con esto cerramos el indicador de mouse y podemos volver nuestra atención al indicador de control.


Un nuevo indicador de control

Como mencioné al principio de este artículo, vamos a hacer un uso más intenso de MQL5 con el fin de promover algunas mejoras en el indicador de control. Estas mejoras nos ayudarán a hacer las cosas un poco más agradables. En el anexo, tendrás acceso a las nuevas figuras que usaremos. Esto nos permitirá generar la transparencia. Para dejar bastante claro cómo proceder para conseguir esto, se usarán nuevos bitmaps.

Como ya puedes estar pensando. Sí, fue necesario crear una nueva clase para esto. Pero no te preocupes, prometo que hasta el final del artículo mostraré la interacción básica.

El código fuente de la clase puede verse justo abajo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. #define def_MaxImages 2
07. //+------------------------------------------------------------------+
08. class C_DrawImage : protected C_Terminal
09. {
10. //+------------------------------------------------------------------+
11.     private :
12.             struct st_00
13.             {
14.                     int  widthMap,
15.                          heightMap;
16.                     uint Map[];
17.             }m_InfoImage[def_MaxImages];
18.             uint    m_Pixels[];
19.             string  m_szObjName,
20.                     m_szRecName;
21. //+------------------------------------------------------------------+
22.             void ReSizeImage(const int w, const int h, const uchar v, const int what)
23.                     {
24. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
25.                             double fx = (w * 1.0) / m_InfoImage[what].widthMap;
26.                             double fy = (h * 1.0) / m_InfoImage[what].heightMap;
27.                             uint pyi, pyf, pxi, pxf, tmp;
28.                             uint uc;
29. 
30.                             ArrayResize(m_Pixels, w * h);
31.                             for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap)
32.                             {
33.                                     pyf = (uint)(fy * cy) * w;
34.                                     tmp = pyi = (uint)(fy * (cy - 1)) * w;
35.                                     for (int x = 0; x < m_InfoImage[what].widthMap; x++)
36.                                     {
37.                                             pxf = (uint)(fx * x);
38.                                             pxi = (uint)(fx * (x - 1));
39.                                             uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF;
40.                                             m_Pixels[pxf + pyf] = uc;
41.                                             for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc;
42.                                     }
43.                                     for (pyi += w; pyi < pyf; pyi += w) 
44.                                             for (int x = 0; x < w; x++)
45.                                                     m_Pixels[x + pyi] = m_Pixels[x + tmp];
46.                             }
47. #undef _Transparency
48.                     }
49. //+------------------------------------------------------------------+
50.     public  :
51. //+------------------------------------------------------------------+
52.             C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL)
53.                     :C_Terminal(id),
54.                     m_szObjName(NULL),
55.                     m_szRecName(NULL)
56.                     {
57.                             if (!ObjectCreate(GetInfoTerminal().ID, m_szObjName = szObjName, OBJ_BITMAP_LABEL, sub, 0, 0)) SetUserError(C_Terminal::ERR_Unknown);
58.                             m_szRecName = "::" + m_szObjName;                               
59.                             for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++)
60.                             {
61.                                     ResourceReadImage((c0 == 0 ? szFile1 : (szFile2 == NULL ? szFile1 : szFile2)), m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap);
62.                                     ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap);
63.                                     ArrayInitialize(m_Pixels, 0);
64.                                     for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--)
65.                                             if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1];
66.                                     ArraySwap(m_InfoImage[c0].Map, m_Pixels);
67.                             }
68.                             ArrayResize(m_Pixels, 1);
69.                     }
70. //+------------------------------------------------------------------+
71.             ~C_DrawImage()
72.                     {
73.                             for (int c0 = 0; c0 < def_MaxImages; c0++)
74.                                     ArrayFree(m_InfoImage[c0].Map);
75.                             ArrayFree(m_Pixels);
76.                             ObjectDelete(GetInfoTerminal().ID, m_szObjName);
77.                             ResourceFree(m_szRecName);
78.                     }
79. //+------------------------------------------------------------------+
80.             void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
81.                     {
82.                             
83.                             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
84.                             ReSizeImage(w, h, cView, what);
85.                             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
86.                             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
87.                             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
88.                             {
89.                                     ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
90.                                     ChartRedraw(GetInfoTerminal().ID);
91.                             }
92.                     }
93. //+------------------------------------------------------------------+
94. };
95. //+------------------------------------------------------------------+
96. #undef def_MaxImages
97. //+------------------------------------------------------------------+

Código fuente de la clase C_DrawImage

Observa que el código es muy compacto. Esto se debe a que no utilizaremos recursos externos. Vamos a utilizar MQL5 y todo lo que puede ofrecernos de ayuda. Básicamente, esta clase promueve una forma alternativa de utilizar los objetos OBJ_BITMAP_LABEL u OBJ_BITMAP. Ambos tipos forman parte de la biblioteca estándar de MQL5, pero no tienen una forma para usar transparencia ni puntos invisibles en la imagen. Por lo tanto, hemos creado la clase que puedes ver arriba, y de esta forma podremos extender las cosas a nuestro antojo.

Esta clase es tan simple que notas que hacemos uso de solo cuatro procedimientos, y todos ellos bastante simples. Bien, pero vamos a una explicación rápida de cómo funcionan las cosas aquí.

En la línea 6 declaramos el número máximo de imágenes que usaremos. Puedes aumentar este número, pero será necesario modificar un poco el código. Pronto mostraré dónde deberás modificar. Entonces entramos en el código de la clase, esto en la línea 8. Atención al hecho de que está heredando la clase de terminal. Esta puede verse en artículos anteriores.

Justo después, en la línea 11, usamos una cláusula privada para indicar que los datos declarados serán privados para el código interno de la clase. No necesitas, de hecho, modificar esta parte donde se declaran dichos datos. Sin embargo, en la línea 22 entramos en el procedimiento de pre renderización, donde ajustamos el tamaño de la imagen y el nivel de transparencia. Este nivel va del 0 % al 100 % y se modifica en pasos enteros, es decir, de 1 en 1. La definición de la línea 24 garantiza que la conversión de la transparencia de la imagen será la correcta.

Aquí ampliamos o reducimos la resolución de la imagen. Como se trata de imágenes pequeñas, no vi la necesidad de agregar un sistema de antialiasing. Por tanto, si la ampliación es muy grande, puede ocurrir que la imagen quede muy pixelada. Si deseas hacer la ampliación de imágenes, por cualquier motivo, debes considerar agregar un cálculo para promover el antialiasing de la imagen, pero para el propósito que vamos a usar, este sistema es adecuado.

En la línea 52 tenemos el constructor de la clase. Aquí, en este punto, es donde deberás aumentar el número de parámetros, si quieres trabajar con más imágenes. Pero nuevamente, la idea aquí es solo extender lo que es posible hacer dentro del MQL5. Por lo tanto, dos imágenes serán suficientes.

En este constructor accederemos a las imágenes en la línea 61, estas serán referenciadas como recursos. Recuerda que nuestro sistema no necesitará recursos externos, por lo que solo los ejecutables podrán transportarse. La línea 63 garantiza que la imagen es totalmente transparente, pero en la línea 64 entramos en un bucle que recorrerá la imagen cargada y, en cada interacción, en la línea 65, probaremos si se ha encontrado el color indicado para ser transparente. Si es verdadero, el color no se agregará; si es falso, se agregará.

En la línea 66, hacemos uso de una función del MQL5, para cambiar la imagen original por la imagen modificada. Así hacemos las cosas de la manera más rápida posible. Ya en la línea 68, dejamos el sistema limpio.

El destructor que se encuentra al principio de la línea 71 sirve para devolver todos los recursos, en este caso la memoria, al sistema, liberándolos para que otros programas puedan utilizarlos. No son necesarios muchos detalles ni explicaciones.

En la línea 80 tenemos el procedimiento de pintura. Este procedimiento hará que se reproduzca la imagen en el gráfico en la posición y dimensiones indicadas, tomando como referencia los primeros cuatro parámetros. El quinto parámetro de la llamada debe ser un valor entre 0 y 100, donde 0 es sin transparencia y 100 es transparencia total. El sexto parámetro indicará qué imagen se presentará de hecho. El valor debe comenzar siempre en 0, siendo este el índice de la primera imagen, y un valor máximo menos 1; es decir, utilizamos el mismo sistema que el de los objetos OBJ_BITMAP_LABEL y OBJ_BITMAP, en los que indicamos el índice de la imagen, solo que aquí podemos tener más de 2, siempre y cuando modifiques los puntos que indiqué. Esta función de pintura es autoexplicativa y no necesita ningún cambio extra si pretendes ampliar aún más las cosas.

Los únicos lugares que necesitarán cambios son el constructor y la definición presente en la línea 6. Ahora, si deseas cargar una imagen directamente de un archivo, será necesario implementar esto también. Pero para lo que queremos hacer, esta clase ya es de excelente tamaño.

Muy bien, ahora que ya vimos esto, podemos pasar al código del indicador y de la clase de control. Pero antes de ver el código de la clase de control, vamos a detenernos rápidamente en el código del indicador. Este puede ser visto justo abajo en su totalidad.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.52"
07. #property link "https://www.mql5.com/es/articles/11925"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Controls.mqh>
12. //+------------------------------------------------------------------+
13. C_Controls *control = NULL;
14. //+------------------------------------------------------------------+
15. input long user00 = 0;    //ID
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     u_Interprocess Info;
20. 
21.     ResetLastError();       
22.     if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
23.             SetUserError(C_Terminal::ERR_PointerInvalid);
24.     if (_LastError != ERR_SUCCESS)
25.     {
26.             Print("Control indicator failed on initialization.");
27.             return INIT_FAILED;
28.     }       
29.     if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0;
30.     EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, "");
31.     GlobalVariableTemp(def_GlobalVariableReplay);
32.     
33.     return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     return rates_total;
39. }
40. //+------------------------------------------------------------------+
41. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
42. {
43.     (*control).DispatchMessage(id, lparam, dparam, sparam);
44. }
45. //+------------------------------------------------------------------+
46. void OnDeinit(const int reason)
47. {
48.     switch (reason)
49.     {
50.             case REASON_TEMPLATE:
51.                     Print("Modified template. Replay/simulation system shutting down.");
52.             case REASON_PARAMETERS:
53.             case REASON_REMOVE:
54.             case REASON_CHARTCLOSE:
55.                     if (ChartSymbol(user00) != def_SymbolReplay) break;
56.                     GlobalVariableDel(def_GlobalVariableReplay);
57.                     ChartClose(user00);
58.                     break;
59.     }
60.     delete control;
61. }
62. //+------------------------------------------------------------------+

Código fuente del indicador de control

Se puede notar que este código pasó por una liposucción. Pero esto es temporal, ya que necesitamos un código más ligero para probarlo correctamente. Básicamente, puedes ver que tenemos muchas menos llamadas y referencias a la clase de control. Pero, ¿por qué? El motivo es el cambio en la forma de organizar las cosas. Se decidió que deberíamos tener el mínimo posible de puntos de acceso. Fíjate en que el código OnInit es muy diferente. Ahora tenemos algo muy peculiar en la línea 30. En este punto inicializaremos los datos de la clase de control, después del constructor. Sin embargo, para el constructor que se refiere en la línea 22, se decidió que solo se encargaría de inicializar los punteros que se van a utilizar. El manejo de eventos se encargará del resto de las tareas. Por esta razón vemos lo que se muestra en la línea 30, pero ahí te puedes llenar de dudas. Entonces, para reducir un poco el número de dudas, ¿qué tal echar un vistazo al código base de la clase de control?

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\Interprocess.mqh"
005. #include "..\Auxiliar\C_DrawImage.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP                "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay             def_PathBMP + "Play.bmp"
009. #define def_ButtonPause            def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft             def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock        def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight            def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock       def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin              def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_PrefixCtrlName         "MarketReplayCTRL_"
024. #define def_PosXObjects            120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons            32
027. #define def_ColorFilter            0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.            enum EventCustom {evInit};
036.    private :
037. //+------------------------------------------------------------------+
038.            enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull};
039. //+------------------------------------------------------------------+
040.            struct st_00
041.            {
042.                    string  szBarSlider,
043.                            szBarSliderBlock;
044.                    int     Minimal;
045.            }m_Slider;
046.            struct st_01
047.            {
048.                    C_DrawImage *Btn;
049.                    bool         state;
050.                    int          x, y, w, h;
051.            }m_Section[eObjectControl::eNull];
052.            C_Mouse *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(int x, int size)
055.                    {
056.                            ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.                            ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.                    }
072. //+------------------------------------------------------------------+
073.            void SetPlay(bool state)
074.                    {
075.                            if (m_Section[ePlay].Btn == NULL)
076.                                    m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause);
077.                            m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1));
078.                            ChartRedraw(GetInfoTerminal().ID);
079.                    }
080. //+------------------------------------------------------------------+
081.            void CreateCtrlSlider(void)
082.                    {
083.                            CreteBarSlider(77, 436);
084.                            m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.                            m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.                            m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.                            PositionPinSlider(m_Slider.Minimal);
088.                    }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.                    {                       
092.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.                            for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.                            {
095.                                    delete m_Section[c0].Btn;
096.                                    m_Section[c0].Btn = NULL;
097.                            }
098.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B");
099.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.                    }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(int p)
103.                    {
104.                            int iL, iR;
105.                            
106.                            m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.                            iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.                            iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.                            m_Section[ePin].x += def_PosXObjects;
110.                            m_Section[ePin].x += 95 - (m_Section[ePin].w / 2);
111.                            for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                                    m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.                    }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(void)
117.                    {
118.                            C_Mouse::st_Mouse InfoMouse;
119.                            int x, y;
120.                            
121.                            InfoMouse = (*m_MousePtr).GetInfoMouse();
122.                            x = InfoMouse.Position.X_Graphics;
123.                            y = InfoMouse.Position.Y_Graphics;
124.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
125.                            {
126.                                    if ((m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
127.                                            return c0;
128.                            }
129.                            
130.                            return eNull;
131.                    }
132. //+------------------------------------------------------------------+
133.    public  :
134. //+------------------------------------------------------------------+
135.            C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
136.                    :C_Terminal(Arg0),
137.                     m_MousePtr(MousePtr)
138.                    {
139.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
140.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
142.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.                            {
145.                                    m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                                    m_Section[c0].y = 25;
147.                                    m_Section[c0].Btn = NULL;
148.                            }
149.                            m_Section[ePlay].x = def_PosXObjects;
150.                            m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.                            m_Section[eRight].x = m_Section[ePlay].x + 511;
152.                    }
153. //+------------------------------------------------------------------+
154.            ~C_Controls()
155.                    {
156.                            delete m_MousePtr;
157.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
159.                    }
160. //+------------------------------------------------------------------+
161.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
162.                    {
163.                            u_Interprocess Info;
164.                            
165.                            switch (id)
166.                            {
167.                                    case CHARTEVENT_CUSTOM + C_Controls::evInit:
168.                                            Info.df_Value = dparam;
169.                                            m_Slider.Minimal = Info.s_Infos.iPosShift;
170.                                            SetPlay(Info.s_Infos.isPlay);
171.                                            if (!Info.s_Infos.isPlay) CreateCtrlSlider();
172.                                            break;
173.                                    case CHARTEVENT_OBJECT_DELETE:
174.                                            if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName)
175.                                            {
176.                                                    if (sparam == (def_PrefixCtrlName + EnumToString(ePlay)))
177.                                                    {
178.                                                            delete m_Section[ePlay].Btn;
179.                                                            m_Section[ePlay].Btn = NULL;
180.                                                            SetPlay(false);
181.                                                    }else
182.                                                    {
183.                                                            RemoveCtrlSlider();
184.                                                            CreateCtrlSlider();
185.                                                    }
186.                                            }
187.                                            break;
188.                                    case CHARTEVENT_MOUSE_MOVE:
189.                                            if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick())
190.                                            {
191.                                                    case ePlay:
192.                                                            SetPlay(!m_Section[ePlay].state);
193.                                                            if (m_Section[ePlay].state) RemoveCtrlSlider();
194.                                                            else CreateCtrlSlider();
195.                                                            break;
196.                                                    case eLeft:
197.                                                            break;
198.                                                    case eRight:
199.                                                            break;
200.                                                    case ePin:
201.                                                            break;
202.                                            }
203.                                            break;
204.                            }
205.                            ChartRedraw(GetInfoTerminal().ID);
206.                    }
207. //+------------------------------------------------------------------+
208. };
209. //+------------------------------------------------------------------+
210. #undef def_PosXObjects
211. #undef def_ButtonPlay
212. #undef def_ButtonPause
213. #undef def_ButtonLeft
214. #undef def_ButtonRight
215. #undef def_ButtonPin
216. #undef def_PrefixCtrlName
217. #undef def_PathBMP
218. //+------------------------------------------------------------------+

Código fuente de la clase C_Controls

Digo "código base" porque, de hecho, no hace absolutamente nada útil. Solo permite y demuestra el uso de la clase de dibujo que se vio más arriba y una interacción muy simple con el indicador de mouse. Veamos entonces lo que tenemos aquí. En la línea 26, definimos el tamaño que tendrán los botones de interacción. En la línea 27, indicamos el color que se utilizará para indicar la transparencia total del área de la imagen. Puedes ver este color en las imágenes anexas que serán embutidas en el ejecutable en forma de recursos.

Ahora, atención, en la línea 38 indicamos cuáles serán los controles básicos y, entre las líneas 46 y 51, tenemos una estructura que quedará contenida en una matriz que acomodará los controles. Presta atención a esto, pues ser capaces de percibir este tipo de construcción será importante para los próximos pasos.

Si observas el código de la clase de control, verás que ya no estamos utilizando todas aquellas llamadas que había antes. De hecho, el código ha experimentado una remodelación bastante extensa. En este artículo no entraré en todos los detalles, ya que aún no está terminado y nos permite tener un control total. Pero, dado lo que se implementó... Voy a explicar el procedimiento SetPlay, que se encuentra en la línea 73. No estoy seguro de si este será el nombre final de este procedimiento, pero de momento será este.

En este procedimiento, en la línea 75, verificaremos si se ha creado el puntero para el botón de reproducción y pausa. Si no se ha creado, en la línea 76 se ejecutará el constructor de la clase de dibujo. De esta forma, indicamos qué imágenes se van a utilizar. De la misma forma que se haría si fuéramos a usar la función ObjectCreate de la biblioteca del MQL5. Luego, en la línea 77, reproduciremos la imagen en la posición que nos indique el control del botón y decimos cuál será la imagen que se trazará. Finalmente, en la línea 78, solicitamos que se actualice el gráfico para que se trace la imagen.

No explicaré los demás eventos aún, ya que se tratarán en otro momento. Pero quiero que prestes atención a la línea 188. Observa que en esta línea interceptamos los eventos de ratón que nos repasa MetaTrader 5. Explicaré rápidamente lo que está ocurriendo, solo para que entiendas lo básico. Más adelante, cuando el código esté más avanzado, explicaré en detalle lo que realmente sucede aquí.

Cuando MetaTrader 5 nos envíe un evento de mouse, preguntaremos al indicador de mouse si se ha producido un clic con el botón izquierdo. Esto solo se confirmará si el indicador de mouse no está en modo de estudio. Si se ha producido un clic, haremos que la clase de control verifique quién ha sido el clicado. Hay un error que luego resolveremos. Pero, cuando se verifica que se ha hecho clic en el botón de reproducción/pausa, se ejecutará el código entre las líneas 191 y 195, para que tengamos una noción de la interacción que está ocurriendo entre el usuario y todo el sistema.

Puedes ver cómo se están mostrando las cosas en el vídeo que está justo abajo. Ten en cuenta que aún no tenemos algo funcional. Pero la idea era intentar ajustar las cosas para tener precisamente esta interacción entre el indicador del mouse y el indicador de control.




Conclusión

En este artículo, expliqué cómo modificar el sistema para hacerlo más agradable y útil. A largo plazo, esto hará que dejemos de programar tan a menudo y que el sistema sea cada vez más estable y fiable. Sin embargo, también mostré que, en muchas ocasiones, tenemos que profundizar en el uso del MQL5 para conseguir de hecho producir algo que tenga un mejor resultado.

Aunque no he incluido los ejecutables como anexo, es porque no están operativos. Encontrarás las imágenes que deberás sustituir por las antiguas. Si quieres obtener el mismo sistema que estoy desarrollando. Al mismo tiempo, manteniendo el mismo código que se muestra en los artículos. No obstante, siéntete libre de modificar o ajustar las cosas a lo que realmente necesites.

En el próximo artículo, continuaremos desarrollando este indicador de control. Pues ahora la cosa empezará a expandirse de forma geométrica.

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollamos un Asesor Experto multidivisas (Parte 2): Transición a posiciones virtuales de estrategias comerciales Desarrollamos un Asesor Experto multidivisas (Parte 2): Transición a posiciones virtuales de estrategias comerciales
Hoy continuaremos con el desarrollo de un asesor multidivisa con varias estrategias funcionando en paralelo. Intentaremos transferir todo el trabajo relacionado con la apertura de posiciones de mercado desde el nivel de las estrategias al nivel de un experto que gestiona estas. Las propias estrategias solo negociarán virtualmente, sin abrir posiciones de mercado.
Desarrollo de un sistema de repetición (Parte 51): Esto complica las cosas (III) Desarrollo de un sistema de repetición (Parte 51): Esto complica las cosas (III)
En este artículo comprenderás una de las cosas más complejas que existen en la programación MQL5: la forma correcta de obtener el ID del gráfico y por qué a veces los objetos no se trazan en él. El contenido expuesto aquí tiene como objetivo, pura y simplemente, ser didáctico. En ningún caso debe considerarse como una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos mostrados.
Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX
El proyecto consiste en utilizar Python para realizar previsiones basadas en el aprendizaje profundo en los mercados financieros. Exploraremos los entresijos de la comprobación del rendimiento del modelo utilizando métricas clave como el error medio absoluto (MAE, Mean Absolute Error), el error medio cuadrático (MSE, Mean Squared Error) y R-cuadrado (R2), y aprenderemos a envolverlo todo en un ejecutable. También haremos un fichero modelo ONNX con su EA.
Redes neuronales: así de sencillo (Parte 72): Predicción de trayectorias en entornos ruidosos Redes neuronales: así de sencillo (Parte 72): Predicción de trayectorias en entornos ruidosos
La calidad de las predicciones de los estados futuros desempeña un papel importante en el método Goal-Conditioned Predictive Coding, del que hablamos en el artículo anterior. En este artículo quiero presentarte un algoritmo que puede mejorar significativamente la calidad de la predicción en entornos estocásticos, como los mercados financieros.