English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Desarrollo de un EA comercial desde cero (Parte 27): Rumbo al futuro (II)

Desarrollo de un EA comercial desde cero (Parte 27): Rumbo al futuro (II)

MetaTrader 5Ejemplos | 12 octubre 2022, 09:56
447 0
Daniel Jose
Daniel Jose

1.0 - Introducción

En el artículo anterior Desarrollo de un EA comercial desde cero (Parte 26) arreglamos un fallo catastrófico que existía en el sistema de órdenes, pero al mismo tiempo empezamos a implementar algo que nos da toda una suerte de nuevas posibilidades en el sistema de órdenes. Aunque el sistema inicialmente implementado en ese artículo es bastante interesante, hay un fallo que hace que el sistema sea poco viable. Y este defecto se mostró al final de ese artículo. La causa fue el hecho de no saber cómo operar, o mejor, cómo seleccionar cuál sería el momento de vencimiento de la orden o posición, además de otras cuestiones menores. Ahí bloqueé el sistema como una orden o posición que debe cerrarse al final de la sesión de negociación o del día actual, pero a veces queremos hacer operaciones con plazos más largos, y dejar las cosas así no ayuda mucho.

Así que en este artículo les mostraré una manera de corregir, o mejor dicho, de hacer más intuitivo el sistema de órdenes, de manera que puedan identificar inmediatamente y con precisión lo que representa cada orden y cómo se procesa, y qué tipo de movimiento se espera.

La cuestión es tan interesante y al mismo tiempo tan sencilla e intuitiva que, después de ver el funcionamiento, no querrán operar sin este sistema. Y esto que les mostraré en este artículo es sólo una de las muchas posibilidades que pueden implementar en el sistema de órdenes. Tal vez muestre otras cosas en el futuro, pero esto que veremos durante el artículo les dará una gran base para construir otras modificaciones igualmente útiles e interesantes para su caso específico, ya que estoy tratando de dejar todo lo más genérico posible en estos artículos.


2.0 - Un modelo intuitivo para operar

Nuestro sistema de designación de órdenes hasta el momento se realizaba de la siguiente manera:

Los indicadores de Take y Stop tienen esta forma, lo cual es bastante intuitivo, ya que el color verde indica que el valor se abonará en nuestra cuenta, mientras que el color rojo, que tal se cargará en la cuenta, esto es muy sencillo y claro. Ya cuando tenemos el indicador de Stop como se muestra a continuación, tendremos la indicación de que la activación del stop generará una cantidad que se abonará en nuestra cuenta. Es decir, no hay que meterse con los indicadores de límite, al menos en este momento, puede ser que en el futuro decida cambiar algo en ellos, pero de momento ya son suficientemente aptos para su uso.

Esta forma de utilizar los indicadores de límite es algo bastante sencillo e intuitivo de analizar por cualquier operador. Pero tenemos algo que todavía no está tan claro, el primero es el indicador del punto de entrada de una orden pendiente.

Sinceramente, ¿se puede saber si esta orden pendiente es de venta o de compra? Y algo más: ¿Se puede saber si esta orden pendiente se cerrará al final del día o si se abrirá una posición a más largo plazo? Complicado no es... ahora supongamos que ya tenemos posiciones abiertas, el indicador será como el que se muestra a continuación:

De nuevo tenemos los mismos problemas que el indicador de órdenes pendientes. Cuando uno mira el gráfico y ve estos indicadores, no puede saber con seguridad si la posición se cerrará al final del día o si durará más. Y en caso de que vaya a tener su cierre al final del día, no querríamos que el bróker diera un stop obligatorio, ya que nos cobraría por ello, y salir cerrando las órdenes sin ningún tipo de criterio no es algo muy sensato, ni siquiera la caja de herramientas de MetaTrader nos da esta información, por lo que tenerla en el gráfico mediante un indicador es maravilloso.

De esta manera, tendremos que hacer cambios aquí, y especialmente en los indicadores que muestran el punto de apertura de una posición, para tener una mejor comprensión de lo que está sucediendo.


2.0.1 - Cómo añadir nueva información a los indicadores

La forma más básica de añadir nueva información sin ocupar demasiado espacio en el gráfico es con el uso de mapas de bits, ya que son fáciles de entender y bastante representativos. Así, sin insertar ningún código extra, hemos añadido 4 nuevos mapas de bits al EA, que se pueden observar en la clase C_IndicatorTradeView.

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
#define def_BtnDayTrade         "Images\\NanoEA-SIMD\\Inf_DayTrade.bmp"
#define def_BtnSwing            "Images\\NanoEA-SIMD\\Inf_Swing.bmp"
#define def_BtnInfoBuy          "Images\\NanoEA-SIMD\\Inf_Buy.bmp"
#define def_BtnInfoSell         "Images\\NanoEA-SIMD\\Inf_Sell.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled
#resource "\\" + def_BtnDayTrade
#resource "\\" + def_BtnSwing
#resource "\\" + def_BtnInfoBuy
#resource "\\" + def_BtnInfoSell

A continuación, sólo tendremos que introducir 2 nuevos objetos en nuestro sistema de órdenes.

//+------------------------------------------------------------------+
enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PENDING, IT_RESULT};
enum eEventType {EV_NULL, EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_PROFIT, EV_MOVE, EV_CHECK, EV_TYPE, EV_DS};
//+------------------------------------------------------------------+
C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck,
                        m_BtnInfoType,
                        m_BtnInfo_DS;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Tengan en cuenta que cada vez que añadan un nuevo objeto al indicador tendrán que insertar también un EVENTO vinculado a él, lo que garantizará que cada objeto tenga un nombre único.

Ahora viene la parte divertida de la programación. Al principio es importante tener cuidado con los fantasmas para actualizar la forma en que se crean. En este caso mantendremos la información básica insertada en ellos, pero si desea que se eliminen deberá actuar de acuerdo con el fragmento siguiente:

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        if (it == IT_PENDING)
                                        {
                                                macroSwapName(it, EV_CHECK);
                                                macroSwapName(it, EV_TYPE);
                                                macroSwapName(it, EV_DS);
                                        }
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

Las líneas resaltadas transfieren los objetos al fantasma, y hay un código que convierte los indicadores pendientes en flotantes. Ambos procesos se llevan a cabo fácilmente.

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                macroSwapAtFloat(IT_PENDING, EV_TYPE);
                                macroSwapAtFloat(IT_PENDING, EV_DS);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

Las líneas resaltadas trasladan los objetos al indicador flotante, lo que nos permite realizar varias acciones en las que profundizaremos a continuación. Es necesario señalar primero algunas precauciones especiales a tomar con el código que crea el indicador y que ustedes pueden probar y ajustar hasta que esté a su gusto. El mismo cuidado se asocia a los cambios realizados en los puntos resaltados en el siguiente código:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 100 : (A == IT_PENDING ? 144 : 92)), (A == IT_RESULT ? 34 : 22));      \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                                                                                                                
#define macroInfoBase(A)        {                                                                                               \
                m_BtnInfoType.Create(ticket, sz0 = macroMountName(ticket, A, EV_TYPE), def_BtnInfoBuy, def_BtnInfoSell);        \
                m_BtnInfoType.SetStateButton(sz0, m_Selection.bIsBuy);                                                          \
                m_BtnInfo_DS.Create(ticket, sz0 = macroMountName(ticket, A, EV_DS), def_BtnDayTrade, def_BtnSwing);             \
                m_BtnInfo_DS.SetStateButton(sz0, m_Selection.bIsDayTrade);                                                      \
                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                macroInfoBase(IT_PENDING);
                                                break;
                                        case IT_RESULT  :
                                                macroCreateIndicator(it, clrSlateBlue, clrSlateBlue, def_ColorVolumeResult);
                                                macroInfoBase(IT_RESULT);
                                                break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroInfoBase
#undef macroCreateIndicator

Hay que tener en cuenta que la MacroInfoBase crea los objetos en los indicadores de apertura de posición y resultado de posición, que a su vez se utilizan en el indicador, y no es necesario crear estos objetos en otros indicadores. Sin embargo, vean que no posicionamos los objetos en el lugar donde los creamos, esa acción se realiza en otro lugar que veremos próximamente.

#define macroSetAxleY(A)                {                                                                               \
                m_BackGround.PositionAxleY(macroMountName(ticket, A, EV_GROUND), y);                                    \
                m_TradeLine.PositionAxleY(macroMountName(ticket, A, EV_LINE), y);                                       \
                m_BtnClose.PositionAxleY(macroMountName(ticket, A, EV_CLOSE), y);                                       \
                if (A != IT_RESULT)m_BtnMove.PositionAxleY(macroMountName(ticket, A, EV_MOVE), y, 1);                   \
                else m_EditInfo2.PositionAxleY(macroMountName(ticket, A, EV_PROFIT), y, 1);                             \
                m_EditInfo1.PositionAxleY(macroMountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));            \
                if (A == IT_PENDING) m_BtnCheck.PositionAxleY(macroMountName(ticket, A, EV_CHECK), y);                  \
                if ((A == IT_PENDING) || (A == IT_RESULT))      {                                                       \
                        m_BtnInfoType.PositionAxleY(macroMountName(ticket, A, EV_TYPE), y + (A == IT_PENDING ? 0 : 8)); \
                        m_BtnInfo_DS.PositionAxleY(macroMountName(ticket, A, EV_DS), y - (A == IT_PENDING ? 0: 8));     \
                                                                }                                                       \
                                        }
                                                                        
#define macroSetAxleX(A, B)             {                                                                                               \
                m_BackGround.PositionAxleX(macroMountName(ticket, A, EV_GROUND), B);                                                    \
                m_TradeLine.PositionAxleX(macroMountName(ticket, A, EV_LINE), B);                                                       \
                m_BtnClose.PositionAxleX(macroMountName(ticket, A, EV_CLOSE), B + 3);                                                   \
                m_EditInfo1.PositionAxleX(macroMountName(ticket, A, EV_EDIT), B + 21);                                                  \
                if (A != IT_RESULT) m_BtnMove.PositionAxleX(macroMountName(ticket, A, EV_MOVE), B + 80 + (A == IT_PENDING ? 52 : 0));   \
                else m_EditInfo2.PositionAxleX(macroMountName(ticket, A, EV_PROFIT), B + 21);                                           \
                if (A == IT_PENDING) m_BtnCheck.PositionAxleX(macroMountName(ticket, A, EV_CHECK), B + 82);                             \
                if ((A == IT_PENDING) || (A == IT_RESULT))      {                                                                       \
                        m_BtnInfoType.PositionAxleX(macroMountName(ticket, A, EV_TYPE), B + (A == IT_PENDING ? 100 : 82));              \
                        m_BtnInfo_DS.PositionAxleX(macroMountName(ticket, A, EV_DS), B + (A == IT_PENDING ? 118 : 82));                 \
                                                                }                                                                       \
                                        }
//---
        void ReDrawAllsIndicator(void)
                        {
                                C_IndicatorTradeView::st00 Local;
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, OBJ_EDIT);
                                ulong           ticket;
                                eIndicatorTrade it;
                                eEventType ev;
                                
                                Local = m_Selection;
                                m_Selection.ticket = 0;
                                for (int c0 = 0; c0 <= max; c0++)
                                   if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, OBJ_EDIT), ticket, it, ev))
                                      if ((it == IT_PENDING) || (it == IT_RESULT))
                                      {
                                        PositionAxlePrice(ticket, IT_STOP, macroGetLinePrice(ticket, IT_STOP));
                                        PositionAxlePrice(ticket, IT_TAKE, macroGetLinePrice(ticket, IT_TAKE));
                                        PositionAxlePrice(ticket, it, macroGetLinePrice(ticket, it));
                                        }
                                m_Selection = Local;
                                ChartRedraw();
                        }
//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetLinePrice(ticket, it, price);
                                macroSetAxleY(it);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 160; break;
                                        case IT_STOP: desl = 270; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2));
                        }
#undef macroSetAxleX
#undef macroSetAxleY

También hago hincapié en que no me gusta hacer cambios drásticos y radicales en el código, los únicos cambios inevitables se destacan en el código anterior.


2.0.2 - Problemas a la vista

Aunque prácticamente todo funciona bien en el sistema, se detectó un problema y sin éxito busqué una solución sencilla en la documentación del MQL5. El problema es la dificultad para discriminar una posición recién abierta, es decir, identificar si es del tipo Day Trade (operaciones cortas realizadas en el mismo día) o Swing Trade (operaciones más largas). En el caso de una posición más antigua abierta el día anterior, el análisis se simplificaría, ya que bastaría con comparar el día actual con el de apertura, es decir, si fueran diferentes, la posición sería un Swing Trade. Sin embargo, ¿qué pasa si el EA está cerrado y lo iniciamos el mismo día en que se abrió la posición? Ya no sería posible saber si la posición era una Day Trade o una Swing Trade.

Este problema no existe en el caso de las órdenes pendientes, ya que tenemos un método de distinción para ellas. Al ejecutar una llamada a OrderGetInteger utilizando el parámetro ORDER_TYPE_TIME, a través del enumeradorENUM_ORDER_TYPE_TIME sabremos si la orden es de tipo Day Trade o Swing Trade, pero no ocurre lo mismo con las posiciones.

Por esta razón, la solución que se me ocurrió para este caso es hacer que la orden o posición indique el periodo de duración de la operación al EA independientemente de otra información. No es una solución perfecta, porque puede ser eficaz en varios casos, pero no en todos. La limitación está asociada a la hipótesis de que el operador puede modificar el sistema utilizado por el EA para identificar la naturaleza de la operación (Swing o Day Trade), pero el período requerido para el análisis ha pasado.

Para una mejor comprensión, veamos la aplicación de la solución.

inline char GetInfosTradeServer(ulong ticket)
{
        long info;
                                
        if (ticket == 0) return 0;
        if (OrderSelect(ticket))
        {
                if (OrderGetString(ORDER_SYMBOL) != Terminal.GetSymbol()) return 0;
                info = OrderGetInteger(ORDER_TYPE);
                m_Selection.bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                m_Selection.pr = OrderGetDouble(ORDER_PRICE_OPEN);
                m_Selection.tp = OrderGetDouble(ORDER_TP);
                m_Selection.sl = OrderGetDouble(ORDER_SL);
                m_Selection.vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                m_Selection.bIsDayTrade = ((ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) == ORDER_TIME_DAY);
                                        
                return -1;
        }
        if (PositionSelectByTicket(ticket))
        {
                if (PositionGetString(POSITION_SYMBOL) != Terminal.GetSymbol()) return 0;
                m_Selection.bIsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Selection.pr = PositionGetDouble(POSITION_PRICE_OPEN);
                m_Selection.tp = PositionGetDouble(POSITION_TP);
                m_Selection.sl = PositionGetDouble(POSITION_SL);
                m_Selection.vol = PositionGetDouble(POSITION_VOLUME);
                if (macroGetDate(PositionGetInteger(POSITION_TIME)) == macroGetDate(TimeTradeServer()))
                        m_Selection.bIsDayTrade = PositionGetString(POSITION_COMMENT) == def_COMMENT_TO_DAYTRADE;
                else m_Selection.bIsDayTrade = false;
                                        
                return 1;
        }
        return 0;
}

Como ya se mencionó anteriormente, en el caso de las órdenes pendientes, basta con hacer una llamada al OrderGetInteger para obtener el valor que necesitamos. El procedimiento asociado a las posiciones ya exige un mayor esfuerzo debido a su complejidad: Comprobamos el día de apertura de la posición con el día actual del servidor de operaciones. Si ambos son iguales, comprobaremos el comentario presente en la orden. Si el comentario indica la línea que se utiliza en la clase C_Router para informar que en caso de abrir la posición será de tipo Day Trade, el EA lo interpretará e informará en el indicador de posición. Pero es necesario que el comentario no se cambie hasta el final del día, porque si esto ocurre el EA puede informar que una posición de Day Trade sería en realidad Swing Trade, y la culpa no estará en el EA sino en el hecho de que el operador haya cambiado el comentario antes de tiempo.

Esta es la falla en esta solución, pero si alguien tiene alguna idea de cómo saber si una posición es Day Trade o no simplemente mirando los datos de la posición, déjelo en los comentarios, ya que esto sería muy bienvenido.

La forma en que se ve con las órdenes pendientes se ve en el video de abajo:


Ahora tenemos casi todo listo, lo único que falta es hacer algunas adiciones más al código para que el EA sea muy interesante de usar.


2.0.3 - Respuesta a los mensajes de la plataforma

Todo nuestro sistema de órdenes se basa en los mensajes enviados por MetaTrader 5 para que el EA pueda saber lo que debe o no debe hacer, y por ello, saber cómo implementar el sistema de mensajería es algo tan importante como tener la idea de cómo tener el sistema modelado.

El código completo de los mensajes puede verse a continuación.

#define macroGetDataIndicatorFloat      {                                                                                                               \
                m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();      \
                m_Selection.bIsBuy = m_BtnInfoType.GetStateButton(macroMountName(def_IndicatorFloat, IT_PENDING, EV_TYPE));                             \
                m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);                                                                     \
                m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);                                                                        \
                m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);                                                                        \
                m_Selection.bIsDayTrade = m_BtnInfo_DS.GetStateButton(macroMountName(def_IndicatorFloat, IT_PENDING, EV_DS));                           \
                                        }
                                                                                                
                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price;
                                bool   	bKeyBuy,
                                        bKeySell,
                                        bEClick;
                                datetime dt;
                                uint     mKeys;
                                char     cRet;
                                eIndicatorTrade  it;
                                eEventType       ev;
                                
                                static bool bMounting = false;
                                static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_MOUSE_MOVE:
                                                Mouse.GetPositionDP(dt, price);
                                                mKeys   = Mouse.GetButtonStatus();
                                                bEClick  = (mKeys & 0x01) == 0x01;    //Clique esquerdo
                                                bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT Pressionada
                                                bKeySell = (mKeys & 0x08) == 0x08;    //CTRL Pressionada
                                                if (bKeyBuy != bKeySell)
                                                {
                                                        if (!bMounting)
                                                        {
                                                                m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
                                                                valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                                                valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                                                m_Selection.it = IT_PENDING;
                                                                m_Selection.pr = price;
                                                        }
                                                        m_Selection.tp = m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                                        m_Selection.sl = m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                                        m_Selection.bIsBuy = bKeyBuy;
                                                        m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy);
                                                        if (!bMounting)
                                                        {
                                                                IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0);
                                                                bMounting = true;
                                                        }
                                                        MoveSelection(price);
                                                        if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price);
                                                }else if (bMounting)
                                                {
                                                        RemoveIndicator(def_IndicatorTicket0);
                                                        memLocal = 0;
                                                        bMounting = false;
                                                }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                                                {
                                                        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                                                }
                                                break;
                                        case CHARTEVENT_OBJECT_DELETE:
                                                if (GetIndicatorInfos(sparam, ticket, it, ev))
                                                {
                                                        if (GetInfosTradeServer(ticket) == 0) break;
                                                        CreateIndicator(ticket, it);
                                                        if ((it == IT_PENDING) || (it == IT_RESULT))
                                                                PositionAxlePrice(ticket, it, m_Selection.pr);
                                                        ChartRedraw();
                                                        m_TradeLine.SpotLight();
                                                        m_Selection.ticket = 0;
                                                        UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                                }
                                                break;
                                        case CHARTEVENT_OBJECT_ENDEDIT:
                                                macroGetDataIndicatorFloat;
                                                m_Selection.ticket = 0;
                                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                                break;
                                        case CHARTEVENT_CHART_CHANGE:
                                                ReDrawAllsIndicator();
                                                break;
                                        case CHARTEVENT_OBJECT_CLICK:
                                                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                                                {
                                                        case EV_TYPE:
                                                                if (ticket == def_IndicatorFloat)
                                                                {
                                                                        macroGetDataIndicatorFloat;
                                                                        m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1)));
                                                                        m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1)));
                                                                        m_Selection.ticket = 0;
                                                                        UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                                                                } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam));
                                                                break;
                                                        case EV_DS:
                                                                if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam));
                                                                break;
                                                        case EV_CLOSE:
                                                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                                                {
                                                                        case IT_PENDING:
                                                                        case IT_RESULT:
                                                                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                                                                break;
                                                                        case IT_TAKE:
                                                                        case IT_STOP:
                                                                                m_Selection.ticket = ticket;
                                                                                m_Selection.it = it;
                                                                                SetPriceSelection(0);
                                                                                break;
                                                                }
                                                                break;
                                                        case EV_MOVE:
                                                                        if (ticket == def_IndicatorFloat)
                                                                        {
                                                                                macroGetDataIndicatorFloat;
                                                                                m_Selection.ticket = ticket;
                                                                                m_Selection.it = it;
                                                                        }else   CreateGhostIndicator(ticket, it);
                                                                break;
                                                        case EV_CHECK:
                                                                if (ticket != def_IndicatorFloat)
                                                                {
                                                                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                                                                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                                                                } else
                                                                {
                                                                        macroGetDataIndicatorFloat;
                                                                        m_Selection.ticket = def_IndicatorTicket0;
                                                                        m_Selection.it = IT_PENDING;
                                                                        SetPriceSelection(m_Selection.pr);
                                                                        RemoveIndicator(def_IndicatorFloat);
                                                                }
                                                                break;
                                                }
                                                break;
                                }
                        }
#undef macroGetDataIndicatorFloat

No se alarmen al ver este código, aunque parece grande y complicado, en esencia es bastante sencillo, pero me centraré en los aspectos más destacados para explicar las nuevas características de este sistema de procesamiento de mensajes.

La primera novedad en el código es el tratamiento del evento CHARTEVENT_OBJECT_ENDEDIT. Este es disparado por MetaTrader 5 cada vez que terminamos de editar el contenido presente en un objeto EDIT. Bien, ¿qué nos importa esto? Es muy importante, ya que si no tratamos este evento e intentamos manipular los datos de los indicadores de límite después de editar el valor de apalancamiento, tendremos inconsistencia en los valores, aunque el EA forzará el valor para que vuelva a su valor original, pero si tratamos este evento como se muestra en el código, este problema no existirá y podremos operar sin problemas con los nuevos datos de apalancamiento. Recordemos que cuando le pedimos al EA que nos permita hacer estos ajustes estaremos queriendo verificar si es o no una buena idea entrar más o menos apalancados en la operación, de esta manera podremos hacer el estudio sin riesgo, ya que el EA sólo enviará la orden al servidor cuando se lo pidamos, el momento en que esto sucede es la activación de la casilla de verificación.

Ahora veamos más de cerca el evento CHARTEVENT_OBJECT_CLICK, para ello tomaremos el fragmento marcado en el código anterior y lo traeremos más cerca.

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
                case EV_TYPE:
                        if (ticket == def_IndicatorFloat)
                        {
                                macroGetDataIndicatorFloat;
                                m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1)));
                                m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1)));
                                m_Selection.ticket = 0;
                                UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam));
                        break;
                case EV_DS:
                        if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam));
                        break;

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

¿Qué hace realmente este código? ¿Tienen alguna idea? Bueno, en los vídeos de este artículo se muestra, pero ¿se podría entender cómo se hace ese tipo de cosas? Muchos imaginaron que se trata de un código extremadamente complejo, pero él se encuentra allí.... justo encima...

Hay dos tratamientos que tenemos que hacer, el primero es que, cuando un objeto BitMap recibe un clic, su estado cambia y tenemos que comprobar si el ticket es algo que ya está en el servidor o es algo que sólo está en el gráfico, esto se hace mediante los puntos resaltados en verde si el ticket es algo que está en el servidor, este cambio de estado hay que deshacerlo, y el EA lo corregirá haciendo el cambio por nosotros.

Ahora miremos la sección resaltada en amarillo, ¿qué pasa ahí? La idea se basa en lo siguiente: ¿Por qué tengo que poner otra orden en el gráfico, si ella ya existe en el gráfico, sólo quiero invertir la decisión, es decir, si estaba comprando, ahora quiero vender, y si estaba vendiendo ahora quiero comprar.... Pues bien, el fragmento en amarillo hace exactamente eso, cuando el BitMap encargado de indicar si estamos comprando o vendiendo recibe un clic, la decisión cambia automáticamente. Un detalle es que esto sólo se puede hacer en una orden flotante, esto está prohibido por el EA en algo que ya está en el servidor.

Pues bien, con todos estos cambios los nuevos indicadores son los que se muestran a continuación:

Indicador de órdenes pendientes:

 

Indicador de posición:

Fíjense que es mucho más sencillo identificar lo que hace una orden pendiente o una posición abierta, sin duda se puede saber cuál es el movimiento esperado o el periodo de vida de la posición. La flecha que apunta hacia arriba y es de color verde e indica una posición de compra, si es de color rojo y apunta hacia abajo, estamos esperando un movimiento a la baja. Ahora bien, la letra D representa una Day Trade, es decir, que se cerrará al final del día, pero si en lugar de esta tenemos una letra S tendremos la representación de una Swing Trade y la operación no necesariamente se completará al final del día.

En el siguiente vídeo, les muestro cómo funciona el nuevo sistema de órdenes. Es cierto que he dado un enfoque a las órdenes pendientes, porque pueden sufrir más modificaciones, en cuanto que los indicadores de posición no pueden ser modificados, sólo representarán lo que el servidor nos está diciendo respecto a la posición abierta. Veamos con atención cómo funcionan las cosas antes de probarlo en una cuenta real, porque este sistema es muy práctico, pero es necesario familiarizarse con él para sacar el máximo provecho de su funcionalidad.




3.0 - Conclusión

Bueno, nuestro sistema de órdenes es ahora bastante versátil, puede hacer varias cosas y nos ayuda mucho, pero todavía falta un detalle crucial que será implementado en el próximo artículo, así que hasta entonces...


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

Archivos adjuntos |
Desarrollo de un EA comercial desde cero (Parte 28): Rumbo al futuro (III) Desarrollo de un EA comercial desde cero (Parte 28): Rumbo al futuro (III)
Nuestro sistema de órdenes todavía falla en hacer una cosa, pero FINALMENTE lo resolveremos...
Desarrollo de un EA comercial desde cero (Parte 26): Rumbo al futuro (I) Desarrollo de un EA comercial desde cero (Parte 26): Rumbo al futuro (I)
Llevaremos nuestro sistema de órdenes al siguiente nivel, pero primero tenemos que resolver algunas cosas. Y es que ahora tenemos cuestiones que dependen de cómo queremos operar y de qué tipo de cosas hacemos durante la jornada de tráding.
Desarrollo de un EA comercial desde cero (Parte 29): Plataforma parlante Desarrollo de un EA comercial desde cero (Parte 29): Plataforma parlante
En este artículo aprenderemos a hacer hablar a la plataforma MT5. ¿Qué tal si hacemos que el EA sea más divertido? Operar en los mercados financieros suele ser una actividad extremadamente aburrida y monótona, pero podemos hacerla un poco menos tediosa. Este proyecto podría ser peligroso en caso de que tengas un problema que te haga adicto, pero en realidad con las modificaciones todo el escenario podría ser más entretenido, menos aburrido.
Desarrollo de un EA comercial desde cero (Parte 25): Dotando de robustez al sistema (II) Desarrollo de un EA comercial desde cero (Parte 25): Dotando de robustez al sistema (II)
Aquí terminaremos de dar un empujón en el rendimiento del EA... así que prepárense para una larga lectura. Lo primero que haremos para que nuestro EA sea robusto es eliminar del código todo y absolutamente todo lo que no forme parte del sistema comercial.