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

Desarrollando un EA comercial desde cero (Parte 09): Un salto conceptual (II)

MetaTrader 5Indicadores | 18 mayo 2022, 08:34
516 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior creamos el sistema base para utilizar templates dentro de una ventana flotante. A pesar de haber hecho muchos cambios en el código, no lo terminé totalmente para no complicar la explicación, ya que usar templates en ventanas flotantes es algo bastante sencillo, pero usar objetos en una ventana flotante es otra historia. Así que hay que prepararse porque aquí la cosa va a ser muy llamativa.

La mayor complicación encontrada, de hecho, en el uso de objetos que utilizamos para hacer nuestra interfaz CHART TRADE en una ventana flotante es el hecho de que MetaTrader 5 no fue realmente desarrollado para este propósito. Muchos argumentarán que podríamos usar la librería estándar y así crear nuestra ventana CHART TRADE, pero me gusta complicar las cosas y quiero permitir que cualquiera pueda crear su propia interfaz de la misma manera que se mostró hace unos artículos, en los que mostré cómo hacerlo. El hecho es que las cosas eran simples allí, pero aquí tenemos que entender las limitaciones de MetaTrader 5 para sortear estas limitaciones.


Planificación

Comencemos con lo básico, el siguiente código se comportará como se espera y se puede ver en la siguiente imagen:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID();
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand();
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+


Esto no parece un gran problema, ya que el efecto era exactamente el esperado, pero MQL5 nos permite ir un poco más allá, aunque hay complicaciones cuando se intenta hacer algo más allá de lo que el sistema fue inicialmente diseñado para hacer. Así que si modificamos el código anterior a algo que se muestra a continuación, las cosas empiezan a ponerse interesantes.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

Las líneas resaltadas se han añadido en el código original, y cuando lo ejecutamos en un gráfico, obtendremos el siguiente resultado:


Pero luego piensamos, ¡¿qué pasó?! Lo que hicimos fue poner un gráfico dentro de otro gráfico, podríamos poner cualquier objeto, MQL5 nos permite hacer esto, pero hay una ventaja y una desventaja de hacer esto, así que vamos a añadir un cambio más en el código para verificar la ventaja de hacer esto.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

Y al adicionar las líneas resaltadas obtenemos el siguiente resultado:


Esto significa que todo lo que se coloque dentro de un objeto se quedará dentro del objeto, esto es primordial para poder utilizar ventanas flotantes, ya que facilita mucho la parte de la lógica de control, pero no todo son flores, MetaTrader 5 no fue diseñado para esto por lo que tenemos un problema cuando un objeto está dentro de otro, y lo principal es que no podemos enviar eventos a los objetos internos, para entenderlo vamos a añadir algunas modificaciones más en el código. Ahora el código final se puede ver a continuación:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam);
}
//+------------------------------------------------------------------+

Y el resultado cuando lo ejecutamos en la plataforma se ve a continuación:


Nótese que aún haciendo clic en el objeto interno seguiremos teniendo como resultado un clic en el objeto externo, y eso es justamente lo que dificulta las cosas, pero un programador siempre tiende a convertirse en un experto en sortear los problemas, resolviéndolos de manera tal de tener el resultado deseado, y es con este conocimiento visto hasta ahora que construiremos nuestro sistema de manera que se pueda crear un CHART TRADE en una ventana flotante de manera tal que pueda ser funcional y tener una apariencia personal para cada uno.

Hay una última fase en este proceso de planificación, y aunque no parece tener sentido para los ordenadores modernos, deberíamos pensar en ella, estoy hablando de la optimización del tiempo de procesamiento. La cuestión aquí tiene más que ver con el número de operaciones que debe realizar el procesador que necesariamente con el tiempo que tardará en procesar dicha información. El sistema de ventanas flotantes que propongo contiene 4 objetos que tienen que ser movidos, hagas lo que hagas, y cualquier información colocada en la ventana gráfica estará sujeta a las propias modificaciones de la ventana. El punto es que un mínimo de CHART TRADE aumentará la cantidad de objetos, y aunque esto no tiene costos computacionales relevantes, hace que el código sea desagradable, dando la impresión de que está mal optimizado. Así que podríamos simplemente añadir un sistema de control y esto resolvería el problema, pero tengo una propuesta más elegante, aunque parece más laboriosa, en realidad reduce el número de objetos que necesitamos mantener y manipular.


Implementación

Lo primero que haremos es subdividir en más pasos la creación de la ventana flotante, esto para poder reutilizar aún más el código, ajustando sólo pequeñas cosas cuando sea necesario. Luego creamos 2 nuevas rutinas en la clase objeto C_ChartFloating, se pueden ver a continuación:

//+------------------------------------------------------------------+
bool StageLocal01(string sz0, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
	m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
                                
        return true;
}
//+------------------------------------------------------------------+
void StageLocal02(int x, int y, int w, int h)
{
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);                            
        m_Win[m_MaxCounter].PosX        = -1;
        m_Win[m_MaxCounter].PosY        = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
}
//+------------------------------------------------------------------+

Con esto el nuevo código para añadir la ventana flotante se verá como se muestra a continuación:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
	if (!StageLocal01(sz0, TimeFrame, Scale)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        StageLocal02(x, y, w, h);

        return true;
}

Si bien esto no afecta en nada al sistema ya montado, nos permite hacer un mejor uso del mismo, nótese las líneas resaltadas, ahora vamos a crear la rutina para utilizar nuestro IDE, el inicio de la rutina se puede ver a continuación:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        StageLocal02(x, y, w, h);
        return true;
}

Tengamos presente que las líneas resaltadas son las mismas que se encuentran en el código anterior, o que se están reutilizando, y sólo hay que ajustar las cosas que son diferentes. Ahora podemos decirle a nuestro sistema que tenemos medios para controlar el IDE, así que lo hacemos modificando la clase de objeto C_TemplateChart, el fragmento de abajo muestra exactamente lo que se cambiará en la función, así que podemos centrarnos desde este momento en implementar la ventana flotante del IDE, ya que todo el soporte necesario ya estará funcionando correctamente.

void AddTemplate(void)
{

// .... Código de la función....

        if (h == 0)
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
        }
        if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
        {
                if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
        }
}

Veamos como el código se va ajustando de forma que sea lo más maleable posible, esto evita que el sistema se convierta en un Frankenstein a medida que lo vamos modificando, siempre debemos tener esto en mente, evitando reescribir el código, o mejor dicho, volver a probar varias veces lo mismo, procurando siempre hacer la prueba una sola vez, hecho esto, utilicemos y exploremos todo lo posible, antes de tener que hacer una nueva prueba, de esta forma nuestro sistema crecerá con buenas premisas y el código se mantendrá sostenible y ampliable en el tiempo.

Pues bien, si el sistema se pone a funcionar ya mostrará algo en el gráfico, pero nosotros queremos que muestre al menos la interfaz que hemos diseñado, por lo que tenemos que hacer una modificación extra y el código quedará como se puede ver a continuación:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}

Y el resultado cuando lo ejecutamos se ve en la figura siguiente:


¡¡¡Todo sería muy bueno y bonito si existiera la posibilidad de acceder a los objetos que están en el template que cargamos en la línea que se resalta en el código, pero no tenemos esta posibilidad, y aquí es donde vive el gran detalle, en lugar de crear todos los objetos como mostré anteriormente, creamos sólo los objetos que deben ser manipulados!!! Esto nos ahorraría mucho tiempo de procesamiento cuando tengamos que mover la ventana, pero todavía tenemos otro problema, vamos a resolver primero el problema de la manipulación, lo que permitirá que el sistema sea funcional. Sí, esta parte en cierto modo ya está lista, sólo hay que arreglar un poco las cosas para que todo funcione.

Empecemos por hacer un cambio en la secuencia de herencia entre clases, el hecho de no tener herencia múltiple nos obliga a hacer esto por lo que la nueva estructura se verá así:


Pero no debemos preocuparnos por este cambio, el hecho de cambiar la secuencia de herencia no cambiará en casi nada el código, sino que permitirá que esté casi listo. Los puntos que realmente sufrieron cambios se pueden ver a continuación, con los cambios resaltados.

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
	if ((w <= 0) || (h <= 0)) return false;
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}
void AddTemplate(void)
{
// ..... Código ....
        if (h == 0)
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
        }
        if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
        {
		C_Chart_IDE::Create(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h));
                m_Info[m_Counter - 1].szVLine = "";
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
        }
}
bool Create(bool bFloat)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                        
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
	m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, 0, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}
bool LoopCreating(ENUM_OBJECT type)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
        int c0;
        bool b0;
        string sz0 = m_szValue;
        while (m_szLine != "</object>") if (!FileReadLine()) return false; else
        {
                if (m_szLine == "name")
                {
                        b0 = false;
                        StringToUpper(m_szValue);
                        for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
                        if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);

//... El resto de la rutina ....

}

Quizá pensemos: ¡¿Qué demonios hace este loco?! Lo que estamos haciendo es algo sencillo, recordemos que si no estamos usando una ventana flotante, el sistema ya es capaz de manejar los eventos ejecutados en nuestro IDE, pero debido a la ventana flotante tendremos que volver a montar la cosa. Esto es un hecho, pero también es un hecho que no necesitamos hacer esto desde cero, modificaremos el código existente para agregar el IDE en el lugar correcto, pero no con todos los elementos, agregaremos sólo los objetos que reciben eventos. Y estas modificaciones nos permiten precisamente eso, saber si tenemos que crear todos los elementos o sólo una parte.

Si ejecutamos estas modificaciones ya tendremos en el gráfico la información del IDE, solo que los objetos del IDE estarán fuera de lugar haciendo un verdadero desastre, ya que los objetos no están vinculados a la ventana flotante, ahora tenemos que arreglar esto.

Lo primero que hay que hacer es obtener los puntos donde se encuentra la ventana flotante en el gráfico, esta no es la tarea más trivial, ya que bastará con tener los puntos del objeto que representa nuestra ventana, pero como nos estamos manteniendo dentro de las premisas de una programación orientada a objetos tendremos que hacer algunos cambios, aunque la principal consecuencia es la creación de un código que se puede ver a continuación.

//+------------------------------------------------------------------+
struct IDE_Struct
{
        int     X,
                Y,
                Index;
        bool    IsMaximized;
};
//+------------------------------------------------------------------+
class C_ChartFloating
{

// ... Código de la clase ...

//+------------------------------------------------------------------+
                void SetPosition(const int X, const int Y, const int c0)
                        {

// ... Código de la función ...

                                if (c0 == m_IDEStruct.Index)
                                {
                                        m_IDEStruct.X = m_Win[c0].PosX + 3;
                                        m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3;
                                        m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized;
                                }
                        }
//+------------------------------------------------------------------+

// ... Código de la clase ...

//+------------------------------------------------------------------+
inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; }
//+------------------------------------------------------------------+
                bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
                        {
                                if ((w <= 0) || (h <= 0)) return false;
                                if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
                                ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
                                m_IDEStruct.Index = m_MaxCounter;
                                StageLocal02(x, y, w, h);
                                return true;
                        }
//+------------------------------------------------------------------+

//... Código restante de la clase 
}

Como la solución se centra en una sola ventana que será nuestro CHART TRADER podemos hacerlo así, no tiene sentido complicarse más allá de este punto. Ahora podemos posicionar, al menos inicialmente, los objetos en la ventana de forma adecuada. Esto se consigue con la siguiente rutina.

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX);
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY);
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

La rutina anterior sólo posicionará inicialmente los objetos de forma adecuada y el resultado se puede ver a continuación:


Excepto que, además de estar correctamente posicionados inicialmente, los objetos ya responden a los eventos, pero todavía hay fallos en el sistema, que deben ser corregidos. El primero que vamos a arreglar se ve a continuación:


Vemos que la ventana se minimiza, pero los objetos permanecen en la pantalla, esto se debe a que los objetos no están realmente dentro del área de la ventana, ya he explicado por qué no se puede hacer esto. Pero tenemos que corregir esto antes de seguir adelante. Esto es fácil de hacer, sólo hay que modificar un poco el código como se muestra a continuación:

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth()));
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight()));
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

Y el resultado es este:

Bien, ahora vamos a resolver el problema del movimiento, esto es igualmente sencillo de resolver y el código que lo hace se encuentra en el fragmento de abajo:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        static double AccumulatedRoof = 0.0;
        bool            b0;
        double  d0;
        static int px = -1, py = -1;
                                
        C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam);
        if (m_CountObject < eEDIT_STOP) return;
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py))
                        {
                                px = GetIDE_Struct().X;
                                py = GetIDE_Struct().Y;
                                Resize(-1);
                        }
                        break;

//... Código restante de la rutina ...

El resultado final es este:

 


Conclusión

Noten como la programación es algo hermoso, empezamos con un problema, y sin mayores cambios en el código, vamos resolviendo los problemas uno a uno y al final tenemos el código funcionando y con la menor cantidad de código posible.


Nota importante: En caso de que se vea información extraña en el fondo del Chart Trade, es debido a la actualización 3228 que hace que los objetos con color en clrNONE ya no sean transparentes, y se debe usar el archivo IDE adjunto que soluciona el problema.

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

Archivos adjuntos |
EA_1.09.zip (3617.03 KB)
IDE.tpl (10.13 KB)
Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados
¿Cómo acceder a los indicadores personalizados directamente en el EA? A un EA comercial solo se le sacará partido realmente si se puede usar indicadores personalizados en él, de lo contrario, será solo un conjunto de códigos e instrucciones.
Aprendiendo a diseñar un sistema de trading con Envelopes Aprendiendo a diseñar un sistema de trading con Envelopes
En este artículo, compartiré con ustedes uno de los métodos para comeciar con bandas. Esta vez analizaremos el indicador Envelopes y veremos lo fácil que resulta crear algunas estrategias basadas en él.
Desarrollando un EA comercial desde cero (Parte 11): Sistema de órdenes cruzadas Desarrollando un EA comercial desde cero (Parte 11): Sistema de órdenes cruzadas
Creación de un sistema de órdenes cruzadas. Hay una clase de activos que les hace la vida muy difícil a los comerciantes, estos son los activos de contratos futuros, y ¿por qué le hacen la vida difícil al comerciante?
Desarrollando un EA comercial desde cero (Parte 08): Un salto conceptual (I) Desarrollando un EA comercial desde cero (Parte 08): Un salto conceptual (I)
¿Cómo implementar una nueva funcionalidad de la forma más sencilla posible? Aquí daremos un paso atrás y luego daremos dos pasos adelante.