English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
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)

MetaTrader 5Ejemplos | 12 octubre 2022, 09:47
520 0
Daniel Jose
Daniel Jose

1.0 - Introducción

A pesar de las correcciones y mejoras en el código mostradas en los artículos Desarrollo de un EA comercial desde cero (Parte 24) y (Parte 25), donde mostré cómo aumentar la robustez del sistema en su conjunto, aún quedaron algunos detalles pendientes, no es que sean menos importantes, de hecho son relevantes.

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. Muchos operadores simplemente colocan la orden a un determinado precio y no la mueven de ese punto, pase lo que pase establecerán que ese es el punto de entrada ideal y no cambiarán la orden de lugar, incluso pueden mover los límites a otro lugar o en algunos casos incluso eliminar esos límites pero no cambian el punto de entrada.

Por ello, los fallos que aún persisten en el código no afectarán al modo en que estos operadores actuarán realmente. De hecho se darán cuenta de que el sistema de órdenes del EA tiene fallos, como los que arreglaremos en este artículo, pero a los que les gusta estar corriendo detrás del precio intentando entrar por cualquier vía en una operación, pero no quieren entrar en el mercado, serán testigos de muchos fallos en el sistema, algunos de ellos pueden estorbar, y hacer que las operaciones sean inseguras -esto por decir algo- y otras les quitarán dinero, poniéndolos de rodillas ante el mercado.


2.0 - Implementación

Para comenzar nuestro viaje en este artículo, vamos a empezar por corregir un defecto que hace que el EA sea un verdadero TRITURADOR de dinero, de nuevo, si ustedes no siguen cambiando el punto de entrada todo el tiempo, este defecto no les afectará. Pero por si acaso, les aconsejo que piensen seriamente en corregir el código, a pesar de todo en el código adjunto, la corrección ya estará implementada, pero tal vez piensen que esto perjudicará al EA, restándole algo de rendimiento, esto es cierto, pero les pregunto: ¿Qué es mejor, perder un poco de rendimiento o correr el riesgo de perder dinero en una entrada mal hecha?


2.0.1 - Fallo en el punto de entrada

Este es el primero que vamos a corregir, aunque hay que corregirlos todos de alguna manera, pero este es de lejos el más catastrófico de todos, ocurre cuando ponemos una entrada pendiente, digamos BUY STOP, y movemos el punto de entrada de forma que la orden ahora debería ser de tipo BUY LIMIT. Aquí parece que no hay problemas, pero este fallo es bastante catastrófico, ya que el EA en la fase actual de desarrollo no podrá hacer el cambio de forma correcta, de hecho muchos EAs ni siquiera hacen esta modificación, y en caso de que esto ocurra, se verá en el gráfico una información, pero el servidor tendrá otra información, y el sistema sólo se actualizará correctamente cuando se abra la posición, hasta entonces los datos serán incoherentes entre lo que se muestra en el gráfico por parte del EA y lo que hay en el servidor.

Aunque en algunos casos sólo tenemos esta irregularidad en otros la cuestión será un desastre total, para entenderlo sigan leyendo el artículo con atención.

Para corregir este tipo de fallos tenemos una solución, que en realidad puede pasar por diferentes caminos antes de ser aplicada, pero el principio de funcionamiento será siempre el mismo: quitamos la orden de la profundidad de mercado, la movemos a la nueva posición, cambiamos el tipo de orden a ejecutar, volvemos a poner la orden en la profundidad de mercado. Básicamente esto es lo que se debe hacer, ahora la forma de hacerlo variará de una implementación a otra.

Así que vamos a implementar la solución más básica inicialmente, pero no es perfecta, tendremos que arreglar algunos problemas.

La solución es modificar la función de abajo añadiendo las líneas resaltadas.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Pues bien, a pesar de que esta solución resuelve en parte el problema, en realidad no lo resuelve en su totalidad, por ejemplo, en el caso de las órdenes BUY STOP y SELL STOP, el problema se resuelve con sólo añadir estas simples líneas. Sin embargo para las órdenes BUY LIMIT y STOP LIMIT, el servidor ejecutará inmediatamente la orden en cuanto cliquemos para modificar el punto de entrada, y lo que es peor en este caso es que ya entraremos en la posición perdiendo, y en el caso de que la orden esté configurada como una orden hueca (con límites de ganancia o pérdida) y el punto de Stop Loss esté fuera de los límites de precio, además de que el servidor ejecute inmediatamente la orden, también la cerrará justo después, lo que supondría un desastre total en nuestra cuenta de trading. Por eso los sistemas de comercio son tan complejos de diseñar, porque normalmente lo que hacemos es realizar algunas pruebas en una cuenta DEMO, y si todo parece funcionar, cambiamos a una cuenta REAL, y en ese momento empezamos a perder dinero sin saber lo que realmente está pasando.

Lo reiteraré una vez más: Este fallo NO AFECTA a quien coloca el punto de entrada en el gráfico y no lo cambia, el problema es cuando este punto es movido por el operador.

En realidad las órdenes de STOP están funcionando bien, ahora el problema a resolver es en relación a las órdenes pendientes del tipo LIMIT. Aunque este parezca un problema sencillo de resolver, hay que entender una cosa: NO existe la solución perfecta, y la solución más adecuada para el desarrollador del sistema puede no ser exactamente la que les beneficie a ustedes.

Ya con esto aclarado, voy a mostrar una de las posibles soluciones a esta cuestión. La solución será implementada en esta misma rutina de arriba, así que veamos cómo quedó su código.

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Lo que está siendo hecho es lo siguiente: Cuando vamos a cambiar el punto de entrada de una orden pendiente, comprobamos si la orden que está en la PROFUNDIDAD DEL MERCADO es del tipo STOP LIMIT o BUY LIMIT, si no es así, el flujo de ejecución continúa hacia otro punto del código; ahora bien, si la orden es del tipo especificado, hacemos la captura inmediata del precio actual del activo, y nos basaremos en el siguiente criterio: Si estamos comprando, captaremos el valor ASK actual, si estamos vendiendo utilizaremos el valor BID. Esto sustituye al antiguo método de utilizar el valor LAST, ya que, como este no se utiliza en algunos mercados, no lo utilizaremos como referencia. Una vez hecho esto, efectuamos una comprobación para ver si la orden en la PROFUNDIDAD DEL MERCADO será invalidada o si sólo será modificada.

Si la orden sigue siendo válida, el sistema ignorará el código de verificación y pasará a la parte en la que se modifica la misma. Pero si la orden de la profundidad del mercado es inválida, el sistema ejecutará el siguiente código:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

Aunque esto está bien, este código de arriba sólo modificará las órdenes SELL LIMIT y BUY LIMIT pasándolas a SELL STOP y BUY STOP respectivamente, pero ¡¿qué pasa si queremos revertir estos tipos, haciéndolos volver al original o simplemente impidiendo que se produzca este cambio, cómo debemos proceder en estos casos?!

Si no queremos que el sistema cambie el tipo de orden a ejecutar, lo que debemos hacer es simplemente reemplazar el fragmento resaltado arriba por el siguiente código:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Este código impedirá que se cambie el tipo de orden, en realidad se puede cambiar el punto en el que se ejecutará la orden pendiente, pero no se puede cambiar una orden LIMIT a una orden STOP o viceversa. Ahora bien, si queremos seguir corriendo detrás del precio para forzar una entrada en un punto determinado, debemos utilizar el código que se muestra a continuación, este será el código presente en el EA.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Aviso importante: Se debe tener mucho cuidado al analizar este código debido a la macroForceNewType. Noten que esta macro contiene un break que, cuando se ejecuta, provoca que el código salga del bloque case. Así que mucha atención al modificar o estudiar este código.

Aparte de esto, el sistema ahora ya no tendrá el fallo de movimiento de los puntos de entrada, pero tenemos otros problemas que solucionar. Y les recuerdo que aquí mostré la forma de corregir el problema que ocurre con la modificación o retención del mismo tipo de orden. Ustedes eligen lo que más les convenga, recordando que cada una de estas soluciones tiene sus pros y sus contras. Pero no voy a entrar en esta cuestión, sólo voy a mostrar cómo corregir y aplicar el sistema, lo que cada uno haga con él, es una cuestión personal.

El resultado de estos cambios puede verse en el siguiente vídeo:



2.0.2 - Preparación rumbo al futuro

Aunque el cambio anterior resuelve el problema, hay algo más que se puede hacer, aquí mostraré el inicio de este cambio. Si nos fijamos, el sistema de órdenes del EA todavía puede mejorarse mucho, haciendo que el funcionamiento sea más sencillo o más claro para los que realmente lo utilizan. Los cambios necesarios no son muchos, y quiero explicarlos para que ustedes puedan elegir qué camino se adapta mejor a su forma de operar, ya que cada tráder tiene su propia manera de observar y actuar dentro del mercado. Además, no quiero que nadie se sienta obligado a utilizar el sistema que les muestro, sino que quiero que sirva de base para que cada uno pueda crear un EA adecuado a su estilo de tráding.

Dicho esto, vayamos al siguiente hecho: Desde el artículo Desarrollo de un EA desde cero (Parte 18), he estado mostrando cómo desarrollar un sistema de órdenes fácil de utilizar por aquellos que están operando con un determinado activo. Pero en la Parte 20 de esta serie, el sistema de órdenes recibió elementos visuales, que no están allí por casualidad, sino porque en algún momento el Chart Trade se volverá innecesario para operar, ya que todo será indicado por el propio sistema de órdenes, pudiendo ser cambiado y ajustado muy fácilmente directamente en el gráfico. Ahora bien, para llegar a este punto tenemos que empezar desde algún lugar, y lo haremos en este preciso momento.

¡¿Qué tal si se cambia el volumen negociado directamente en la orden, sin tener que sacar la orden del gráfico, cambiar el volumen en el Chart Trade y luego restablecer la orden en el gráfico?! Tienen curiosidad... ¿no es así...? Es que esta es una característica que vamos a implementar en este mismo momento, y que ayuda mucho en varios escenarios, pero les advierto que necesitan aprender y entender cómo usar el sistema, porque no es algo que vayan a encontrar en ninguna otra plataforma, a decir verdad nunca he visto ningún EA que tenga esta funcionalidad, pero vamos a ver qué tienen que hacer para tener esta funcionalidad en su querido EA.

En primer lugar, definiremos un nuevo índice de indicador.

#define def_IndicatorFloat      3

Cuando una orden pendiente consigue este valor como ticket, puede ser manipulada de una manera totalmente diferente. Todo lo que se ha visto hasta ahora permanecerá en el sistema de órdenes, sólo hemos añadido un nuevo índice.

Después de esto, vamos a añadir un nuevo objeto a nuestro sistema:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Este objeto nos permitirá hacer varias cosas, siempre que la orden sea de tipo pendiente.

Ahora vamos a entrar en la clase C_Object_BitMap, y vamos a hacer algunos cambios en esta clase. Lo primero que hay que hacer es añadir algunas definiciones:

#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"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

Pero necesitamos tener una forma de saber lo que ocurre en esta clase, así que le añadimos las siguientes rutinas:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton devolverá el estado del botón, al fin y al cabo MT5 hace el cambio de estado por nosotros, sin que tengamos que hacer ningún paso extra, por eso tenemos que saber si el botón tiene un valor True o False. Pero puede ocurrir que el estado no represente lo que queremos, por lo que usamos SetStateButton para establecer el estado con el fin de reflejar la condición real vista por el servidor comercial y el EA.

También haremos otra modificación bastante simple, esta vez a la clase C_Object_Edit:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

Esto nos dirá si podremos o no editar el valor. Recordemos que queremos cambiar el volumen de las órdenes directamente en el gráfico, sin tener que recurrir a Chart Trade para ello. Cualquier orden pendiente que se esté creando estará siempre en modo de sólo lectura, pero crearemos un sistema que nos permita cambiar eso.

Así que volvamos a la clase C_IndicatorTradeView, y hagamos algunos cambios más en el código. Vamos a crear una nueva rutina para el sistema. Esta puede ser vista justo debajo:

#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);
                                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

Lo que hace esta rutina es lo siguiente: cuando es llamada, todos los objetos del indicador serán renombrados, es decir, el valor que indicaba el ticket de la orden será sustituido por otro valor, que en este caso es el indicador visto al principio de este tema, pero tenemos otra cuestión. No estoy usando ninguna estructura para mantener la lista de objetos de los indicadores, lo hago de otra manera, lo que hacemos es dejar que MT5 se encargue de esta lista por nosotros, debido a esto no puedo crear órdenes flotantes ilimitadas, tendremos la limitación de tener una sola orden flotante, y para probar esto usamos la línea:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

La prueba aquí es sencilla, si la línea del indicador se posiciona en algún punto, la macro devolverá un valor distinto de 0, por lo que sabemos que ya hay un indicador utilizando el ticket reservado. Esto será importante más adelante para que el EA repare los datos del indicador al que se le ha denegado el pedido, recordemos que MT5 modifica el estado del objeto Bitmap automáticamente, por ello debemos informar al llamante de que se ha producido un fallo.

El siguiente cambio a realizar es en la rutina que crea los indicadores:

#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 ? 84 : (A == IT_PENDING ? 108 : 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);  }                                                                       \
                                                }
                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);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Todos los puntos destacados se han añadido para dar soporte a nuestro nuevo sistema. Básicamente aquí estamos creando una casilla de verificación que siempre empezará a ser true, esto indica que el pedido será colocado inmediatamente en la profundidad del mercado. No quería modificar esta forma de operar, pero no es tan sencillo cambiar el valor de la casilla de verificación de true a false, lo que hará que las órdenes no se coloquen directamente en el gráfico. Para hacer este cambio habría que hacer otros cambios aún más profundos, y el problema es que uno puede en algún momento poner una orden y si se olvida de marcar la casilla de verificación, el punto de entrada pasa y se puede imaginar que el EA está defectuoso, cuando en realidad fue todo por olvido. Así que para evitar esto, por defecto las órdenes pendientes irán directamente a la profundidad del mercado, tendremos que cambiar su estado de forma explícita.

La siguiente rutina realmente importante para nosotros se ve justo debajo:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Las secciones resaltadas del código anterior hacen algo bastante curioso, solo actualizan los valores que deben ser utilizados en el selector, pero los valores en realidad están almacenados en el propio indicador. También puede ocurrir que movamos el sistema de una manera más general por lo que necesitamos que estos valores sean indicados en el selector, esto para que la rutina que hace los cálculos de la posición indique los valores correctos.

Pero hay algo que puede no tener sentido al observar esta rutina. Es un hecho que ella se encarga de crear y modificar los datos de una orden pendiente, pero si nos fijamos, no es posible ver algún punto en el que la orden pendiente vuelva a la profundidad del mercado. Podemos mover, modificar y ajustar el valor del volumen de la orden directamente en el gráfico, pero no podremos notar cómo ella volverá al gráfico.

Esto es un hecho, todo el sistema de cambio y creación de órdenes pendientes tendrá lugar en esta rutina de arriba. Y, a pesar de parecer extraño, esa rutina no vuelve a poner la orden en la profundidad del mercado sólo porque queremos hacer eso, la que realmente hace la solicitud se ve justo debajo. Para facilitar todo sólo mostraré la parte que realmente será responsable de crear la solicitud para poner la orden en la profundidad del mercado.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Código interno ...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        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)
                {
                        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
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        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.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

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

Vean cómo el sistema se construye a sí mismo, nosotros programamos cada vez menos y el sistema crece cada vez más.

Todo el código resaltado tiene alguna relación con el indicador que creamos al principio del tema. A pesar de que todo funciona aparentemente bien, tenemos algunas cosas que se modificarán más adelante, puesto que cuando una orden flotante vuelva a la profundidad del mercado ella tendrá como desventaja el hecho de ser una orden de day trade, por lo que se cerrará al final del día, y esto lo cambiaremos más adelante, pero es necesario que tengan noción de que esto sucede. Ahora puede ser que muchos de ustedes estén confundidos y todavía no entiendan cómo de hecho la orden pendiente entra y sale de la profundidad del mercado cuando hacemos clic en la casilla de verificación. Para tratar de entenderlo, veamos el siguiente diagrama de flujo:

Vean que todas las llamadas vienen de un solo lugar. Tenemos la eliminación de la orden de la profundidad del mercado, pero continuará en el gráfico, y toda la manipulación se hace como se ve en los artículos anteriores, pero si tratamos de encontrar un momento concreto en el que la orden vuelve al libro podemos perdernos un poco en el código. Ahora bien, si observamos el diagrama de flujo, nos damos cuenta de que la llamada se origina en la función DispatchMessage, porque es el único lugar que llama a la función SetPriceSelection, pero si nos fijamos en la función SetPriceSelection, no hay ninguna referencia para crear una orden con el índice utilizado en el sistema flotante. Pero fíjense en una cosa, tenemos la creación de la orden por el índice 0, y eso es exactamente lo que utilizamos, cambiamos el ticket de la orden e informamos que será el ticket de índice 0, de esta manera se creará la orden. Veamos el siguiente fragmento para entender cómo funciona.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
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.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

Todo el código es perfecto, excepto la línea resaltada. De momento no tenemos forma de arreglar esto. Tomaremos medidas al respecto en el próximo artículo, ya que tendremos que hacer algunos cambios en la propia clase.

El resultado de los cambios se puede ver en este vídeo de abajo, observen cómo se modifica el volumen y cómo se envía una nueva orden en el punto indicado. De esta manera es mucho más fácil operar.



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

Archivos adjuntos |
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)
Sigamos avanzando hacia un sistema de órdenes más completo directamente en el gráfico. En este artículo les mostraré una forma de corregir o, más bien, de hacer que el sistema de órdenes sea más intuitivo.
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.
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 24): Dotando de robustez al sistema (I) Desarrollo de un EA comercial desde cero (Parte 24): Dotando de robustez al sistema (I)
En este artículo haremos que el sistema sea más robusto, para que sea más estable y seguro de usar. Una forma de conseguir robustez es intentar reutilizar el código lo máximo posible, de esta forma él mismo será probado todo el tiempo y en diversas ocasiones. Pero esta es solo una de las formas, otra forma es el uso de la programación OOP.