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

MetaTrader 5Ejemplos | 13 octubre 2022, 16:37
614 0
Daniel Jose
Daniel Jose

1.0 - Introducción

Cuando el sistema de órdenes realmente comenzó a desarrollarse, ya desde el artículo Desarrollo de un EA comercial desde cero (Parte 18), no tenía idea de cuánto tiempo llevaría llegar a este punto. Bueno, pasamos por varias cosas, cambios, ajustes, etc. Les mostré cómo hacer algunas cosas específicas, como etiquetar cosas o tener un sistema más intuitivo, pero todavía había algo que no podía mostrar hasta este momento porque el camino no estaba completamente pavimentado. Y ese camino hasta aquí nos ha permitido construir un concepto de una manera en la que todos entienden la idea y saben cómo funciona el sistema.

En todos los artículos anteriores fui allanando el camino para que pudiéramos llegar a este artículo teniendo el mismo nivel de comprensión del funcionamiento del sistema, con lo cual este material no quedó extremadamente confuso ni difícil de ser abarcado. Desde el principio había una cuestión en la que evitaba entrar en detalles y que es muy importante para los operadores más experimentados. A primera vista ella quizá parezca una tontería o una bobada, pero cuando llegue el momento de operar se darán cuenta de que les falta algo en el EA, y se preguntarán "¿¡qué es eso que no hay!?". Me refiero a una forma de poder restablecer los valores de take y stop que fueron eliminados por alguna razón y que los queremos de nuevo en el gráfico.

Si alguna vez ustedes han intentado hacerlo, se habrán dado cuenta de que es algo bastante complicado y una operación muy lenta, ya que hay que "seguir un determinado guión" para que las cosas salgan bien, de lo contrario acabarán cometiendo errores.

Pues bien, la plataforma MetaTrader 5 dispone de un sistema de tickets que nos permite crear o ajustar los valores de las órdenes. Por cierto, la idea es contar con un EA que nos ayude a hacer ese mismo sistema de tickets de una manera más rápida y eficiente. El sistema del MetaTrader 5 no es idóneo, pues es considerablemente más lento y está más sujeto a errores que haciendo uso del EA que les estoy enseñando a desarrollar.

Pero, en ningún momento hasta ahora, he explicado cómo hacer para generar los valores límite (Take y Stop) de una orden o posición. Creo que la eliminación de estos límites ha quedado bastante clara y es lo suficientemente intuitiva como para que ustedes sepan exactamente cómo proceder. Pero en cuanto a la forma de llevarlo a cabo, es decir, a la forma de poner los límites o restablecerlos directamente en el gráfico, ¿cómo debemos proceder? Bueno, esto es algo bastante intrigante, e implica algunas preguntas, y, claro, esta es la razón de la creación de este artículo: mostrar una de las muchas formas de crear estos límites, y esto directamente en el gráfico, sin recurrir a ningún recurso externo, simplemente utilizando el sistema de órdenes del EA.


2.0 - Manos a la obra: la implementación

Pues bien, antes que nada, debemos impedir que EA realice una verificación que se viene realizando desde hace mucho, mucho tiempo, desde los primeros días de su desarrollo. La remoción de esta verificación se hace eliminando todo el código tachado y agregando el código resaltado:

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

El código tachado no dejará de existir, sino que volverá en otro momento, pero por ahora nos estorbará más que otra cosa. Una vez hecho esto, podemos empezar a pensar en cómo vamos a implementar nuestro sistema de creación de takes y stops directamente en el gráfico, sin la ayuda de ningún otro recurso que no sea el EA.

Cada desarrollador tendrá una idea diferente para realizar esta tarea, algunas de ellas resultarán más sencillas de entender por el operador, otras más complicadas, en algunos casos la implementación será más complicada y en otros más sencilla. No voy a decir que la forma que usaré y mostraré aquí sea la más adecuada o la más simple, pero fue por lejos la que mejor se adaptó a mi forma de operar y usar la plataforma, también porque no necesitaré crear ningún elemento nuevo, sólo ajustar algunas cosas en el código.


2.0.1 - Modelado del sistema de arrastre

Básicamente, el propio código del EA en la etapa actual de desarrollo ya nos da algunas pistas sobre cómo debemos esculpir el sistema que voy a crear. Observen el siguiente código:

#define macroUpdate(A, B) if (B > 0) {                                                                  \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                     } else RemoveIndicator(ticket, A);
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Las líneas resaltadas contienen una macro que va a realizar una tarea, debemos modificarla para que nos dé la ayuda necesaria y así implementar lo que necesitamos, que es el indicador de límite. Pero veamos más de cerca el código de la macro, que puede verse en el fragmento siguiente:

#define macroUpdate(A, B){ if (B > 0) {                                                                 \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }

Lo que estamos haciendo es lo siguiente: Cuando el valor B, que puede ser Take o Stop, es mayor que 0, comprobaremos si el indicador está en el gráfico. Si no lo está, lo crearemos, lo posicionaremos y ajustaremos el valor que mostrará. Si el valor B es igual a 0, eliminaremos completamente el indicador del gráfico, y lo haremos en el punto resaltado del código de la macro. Pero, ¿sería suficiente si en lugar de eliminar completamente el indicador del gráfico, mantuviéramos un elemento del mismo, y si dicho elemento se pudiera ajustar para representar lo que queremos hacer, que sería crear el o los límites que faltan en la orden o posición, lo que la convertiría de nuevo en una orden o posición de tipo OCO? SÍ, eso sería suficiente, y esa es la idea: dejar un elemento, en este caso el objeto que nos permite mover los límites y crear el límite que nos falta, solo tendríamos que arrastrar este elemento y se crearía el límite. Esta es la base teórica que utilizaremos para crear nuestro sistema.

Pero el simple hecho de hacer esto no nos da todo lo que necesitamos, tenemos que hacer otra modificación en el código antes de continuar, esta modificación se puede ver en el fragmento resaltado abajo:

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

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

        m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
        m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl));

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

}

Lo que estamos haciendo es comprobar si los valores iniciales introducidos por Chart Trade son cero o no. En caso de que lo sean, no se creará ningún indicador, sólo se mostrará el punto de entrada en el gráfico.

Bien, estupendo, ahora tendremos un funcionamiento más lineal en el resto del sistema. En el caso de que no se indique ningún valor límite, todo el modelado de la orden tendrá el mismo comportamiento, de la misma manera que se está haciendo cuando tenemos un valor indicado, desde el inicio cuando vamos a poner una orden pendiente en el gráfico.

De este modo, ningún operador se preguntará: ¿Qué son esas figuras que cuelgan de la orden o de la posición? Puesto que él será consciente de que representan elementos que se pueden mover en el gráfico.

Pero a pesar de todo tenemos un pequeño problema, y para solucionarlo vamos a modificar dos macros, y esto se puede ver a continuación:

#define macroSetLinePrice(ticket, it, price)    ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)
#define macroGetPrice(ticket, it, ev)           ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE)

Esta modificación es muy importante para todo el resto del sistema que ensamblaremos. Pero ustedes se preguntarán: "¡¿Por qué quitas las líneas de corte?!" La razón es que necesitamos hacer el sistema aún más flexible, y para lograrlo se eliminó este código tachado y en su lugar apareció la línea resaltada. Pero quizás se pregunten por qué tenemos una macroGetPrice si no tenemos una para poner un valor en el precio. De hecho hay un solo y único punto que hace este ajuste en el precio, escribiéndolo en el objeto gráfico... este punto se puede ver en el fragmento de abajo:

#define macroSetPrice(ticket, it, ev, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE, price)
//---

// ... Código extra dentro da classe ....

//---
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                if (it != IT_RESULT) macroSetPrice(ticket, it, EV_MOVE, price);
                                macroSetPrice(ticket, it, EV_LINE, 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));
                        }

Gracias a esto, no es necesario que la macro ajuste el precio en los objetos para que sea visible para todos los demás puntos del código. Ahora bien, de hecho, ni siquiera se necesitaba que fuera una macro, pero lo dejaré como tal, para reducir la posibilidad de cometer algún error más adelante cuando este código sufra cambios.

Bueno, ahora que nuestro sistema ha sido actualizado, podemos pasar al siguiente tema y hacer que las cosas finalmente funcionen según lo previsto.


2.0.2 - Una nueva rutina de actualización

Ya había hablado de esto en el tema anterior, todo lo que tenemos que hacer es ajustar la rutina Update, y el propio EA ya será capaz de resolver nuestros problemas. Pero como el enfoque principal es sólo en los indicadores Take y Stop, y estos se ejecutan dentro de una macro, todo lo que necesitamos es ajustar la macro correctamente.

Pero hay una cuestión que queda por resolver, y debemos hacerlo ahora, necesitamos que el botón de movimiento se cree de forma independiente al resto del indicador, y para ello tenemos que aislar el código de creación de este botón, y con ello nace el código que se puede ver a continuación:

// ... código da classe ...

#define def_ColorLineTake       clrDarkGreen
#define def_ColorLineStop       clrMaroon

// ... código da classe ....

inline void CreateBtnMoveIndicator(ulong ticket, eIndicatorTrade it, color C = clrNONE)
                        {
                                string sz0 = macroMountName(ticket, it, EV_MOVE);

                                ObjectDelete(Terminal.Get_ID(), macroMountName(ticket, it, EV_MOVE));
                                m_BtnMove.Create(ticket, sz0, "Wingdings", "u", 17, (C == clrNONE ? (it == IT_TAKE ? def_ColorLineTake : def_ColorLineStop) : C));
                                m_BtnMove.Size(sz0, 21, 23);
                        }

// ... restante do código da classe ...

Este código creará sólo y únicamente el botón de movimiento para que podamos usarlo después. Veremos que es algo muy sencillo y directo, sin muchas complicaciones. Incluso pensé en dejar este código como una macro también, pero decidí dejarlo como una rutina. Él es declarado como una rutina del tipo inline, por lo que para el compilador sería lo mismo que una macro. Noten que, para facilitarnos la vida, tenemos dos nuevas definiciones en este mismo fragmento de código, ya que en algún momento crearemos sólo el botón de movimiento y querremos que tenga los mismos colores utilizados en el sistema completo. No es deseable que el sistema tenga comportamientos diferentes o aspectos igualmente diferentes en casos similares, y para reducir la cantidad de problemas o complicarse la vida, dejamos los colores declarados como se muestra arriba.

Ahora podemos pasar a la rutina Update, que se muestra en su totalidad a continuación. Tengan en cuenta que la única diferencia entre la versión de abajo y la versión mostrada anteriormente en este artículo es sólo el código que está resaltado, y este código es la macro utilizada por la propia rutina Update, en los puntos resaltados dentro de ella.

#define macroUpdate(A, B){                                                                                                      \
                if (B == 0) {   if (macroGetPrice(ticket, A, EV_LINE) > 0) RemoveIndicator(ticket, A);                          \
                                if (macroGetPrice(ticket, A, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, A);                  \
                            } else if (b0 = (macroGetPrice(ticket, A, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, (B == 0 ? pr : B));                                                                \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                                        \
                        }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetPrice(ticket, IT_RESULT, EV_LINE);
                                        if ((pr == 0) && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                macroUpdate(IT_TAKE, tp);
                                macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Veamos esta macro con más detalle para entender lo que realmente ocurre. Entender esto es primordial para poder resolver algunos problemas que aún aparecen cuando usamos el sistema. Recordemos que aún no está listo, necesitaremos hacer algunos cambios más.

En la primera etapa tendremos el siguiente comportamiento: cuando se elimina uno de los indicadores de límite, ya sea Take o Stop, tendremos inmediatamente la retirada de este indicador del gráfico correspondiente al activo negociado. Para ello se comprueba si existe o no una línea que es uno de los puntos que indican la presencia o ausencia del indicador. Poco después comprobaremos si existe un objeto de movimiento de dicho indicador en el gráfico. Si no existe, será creado, y entonces no tendremos el indicador en el gráfico, pero tendremos un remanente del mismo todavía presente en el mismo, el cual sería un objeto de movimiento.

El segundo paso ocurrirá en caso de que se cree un límite: la orden en el servidor tendrá un límite que deberá aparecer en el gráfico, en este caso se removerá el objeto que representaba el movimiento y se creará un indicador completo que se colocará en el lugar que corresponda, y que señalará dónde estará el límite actual de la orden, ya sea un take o un stop.

En el último paso posicionaremos el indicador en el punto correcto. Un detalle curioso aquí: en el caso de que el indicador de límite sea sólo un objeto que representa la posibilidad de movimiento, el punto en el que el mismo se colocará es exactamente el precio de la orden o posición, es decir, el objeto de movimiento que permite la creación del límite de take o stop estará pegado a la línea de precio de la orden o posición a la que pertenece. De este modo, será fácil darse cuenta de si una orden o posición carece de uno de sus límites en la orden o posición.

Básicamente esto es lo que tenemos que hacer, para que, cuando pulsemos sobre el objeto que indica el movimiento, se cree un fantasma, como es habitual, y al mismo tiempo se cree también una representación de un indicador completo. Esto se hace sin añadir ni modificar ningún otro código. A partir de este punto podemos mover y ajustar los puntos de los límites de forma normal, de la misma manera que se hacía antes. Pero hasta que no hagamos clic en un punto determinado, el límite no existirá realmente, esto quedará claro en el vídeo de demostración al final de este artículo, donde mostraré cómo funciona el sistema en una cuenta real.

Aunque todo parece estar bien, aquí tenemos algunos inconvenientes que nos obligan a crear o más bien modificar el código en algunos puntos, y esto se verá en el próximo tema, ya que este paso ha terminado.


2.0.3 - Solución al inconveniente de los indicadores flotantes

El primero de estos inconvenientes es cuando nos encontramos en el modo de indicador flotante: éste se encuentra en el gráfico pero no está en el servidor, para más detalles al respecto ver los artículos Desarrollo de un EA comercial desde cero (Parte 26) y (Parte 27), en estos artículos mostré cómo funciona el indicador flotante y cómo fue implementado. Pues bien, esos indicadores tienen su utilidad y no serán eliminados del EA, aún así no se adaptan al sistema que se ha visto anteriormente, ya que su funcionamiento es muy diferente al de los indicadores que de hecho representan órdenes o posiciones que están en el servidor comercial. De esta manera para solucionar los problemas que aparecen al utilizar un indicador flotante, tendremos que ir a la rutina DispatchMessage y ajustar las cosas allí, a continuación vemos los cambios que se deben hacer.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;

// ... Código interno ...
                        
        switch (id)
        {

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

                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)
                                        {
                                                macroGetDataIndicatorFloat;
                                                RemoveIndicator(def_IndicatorFloat, it);
                                                if (it != IT_PENDING) UpdateIndicators(def_IndicatorFloat, (it == IT_TAKE ? 0 : m_Selection.tp), (it == IT_STOP ? 0 : m_Selection.sl), m_Selection.vol, m_Selection.bIsBuy);
                                        }else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)

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

Al realizar estos cambios, que están resaltados en el código, prácticamente eliminamos cualquier otro problema relacionado con cómo ajustar los indicadores flotantes, es decir, ahora tendremos la misma forma de ajustar los datos de un indicador flotante o algo que represente datos en el servidor comercial. Pero esto no termina por completo con nuestros problemas, aún nos queda un inconveniente más por resolver, y éste venía persistiendo por mucho tiempo. Para hablar de ello pasemos al siguiente tema, porque amerita una discusión aparte para una correcta comprensión.


2.0.4 - El inconveniente del valor de Take negativo

El último inconveniente que hay que resolver en este artículo es el hecho de que el valor de el take puede configurarse a menudo en negativo, y esto ha estado ocurriendo durante bastante tiempo. Para el sistema comercial esto no tiene ningún sentido, tanto que si uno intenta enviar esto al servidor, recibirá un mensaje de error. Así que tenemos que arreglar eso, pero también tenemos que resolver otro problema, que es la posibilidad de que una orden pendiente tenga su valor de stop modificado para que sea positivo.

Sí, el EA hasta el momento lo permite, y lo que es peor, el sistema de órdenes indica que esto está en el servidor, cuando en realidad el servidor devuelve un error, pero el EA simplemente lo ignora. El problema es más complicado en el caso de las órdenes pendientes, ya que en el caso de las posiciones el comportamiento debería ser otro, y aún no se había corregido este fallo. Desde el momento en que tenemos la posibilidad de definir los límites directamente en el gráfico, este defecto ya no debería existir.

Vale la pena hacer una salvedad aquí, ya que en el caso de una posición abierta podemos tener un stop con un valor positivo, y esto indicará que en caso de que se active el stop tendremos un valor que se abonará en nuestra cuenta, pero en las órdenes pendientes esto es un error que impide que el servidor ensamble la orden correctamente. Para solucionar este problema tenemos que hacer una verificación del valor de el take: cuando éste se haga igual o menor que 0 debemos evitar que se siga modificando a un valor menor. En el caso de una orden pendiente, debemos evitar que el valor del stop sea mayor que 0. De hecho lo que haremos es forzar al EA a utilizar un valor mínimo cuando se verifique la condición 0, de esta forma para el sistema comercial la orden o posición tendrá algún sentido, ya que no tiene sentido abrir una posición con stop o take igual al punto de apertura.

Para hacer esto lo más simple posible, tenemos que crear una variable en el sistema, se puede ver a continuación:

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
                        bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl,
                        MousePrice;
}m_Selection;

Pero entonces puede que se pregunten lo siguiente: ¿Por qué simplemente no cambias el punto del precio en la línea del ratón? La razón es que para manipular correctamente el ratón es necesario utilizar una llamada de sistema, es decir, sería necesario manipular los valores de posición del ratón a través de la API de WINDOWS, y esto nos obligaría a habilitar el uso de dlls externas, y no queremos hacer esto, no aquí y no ahora, por lo que es más sencillo instalar un valor local dentro del EA, y los datos resaltados almacenarán este valor por nosotros.

Este valor será manipulado en 3 lugares diferentes, el primer lugar es en la rutina de movimiento y en el fragmento de abajo. Podemos ver el lugar exacto donde sucede esto:

void MoveSelection(double price)
{
        if (m_Selection.ticket == 0) return;
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        UpdateIndicators(m_Selection.ticket, price, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateIndicators(m_Selection.ticket, m_Selection.tp, price, m_Selection.vol, m_Selection.bIsBuy);
                        break;
                case IT_PENDING:
                        PositionAxlePrice(m_Selection.ticket, IT_PENDING, price);
                        UpdateIndicators(m_Selection.ticket, (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.vol, m_Selection.bIsBuy);
                        m_Selection.MousePrice = price;
                        break;
        }
        if (Mouse.IsVisible())
        {
                m_TradeLine.SpotLight(macroMountName(m_Selection.ticket, m_Selection.it, EV_LINE));
                Mouse.Hide();
        }
}

¿Y por qué no poner todo en esta rutina de arriba, ya que será responsable de todo el movimiento de los puntos de los límites? La razón es que tenemos que hacer algunos cálculos para ajustar correctamente el límite ya sea el de take o el de stop, y es mucho más sencillo hacerlo en otro punto, pero como seguramente estarán pensando, sí, también tenemos que cambiar este valor en otro punto, y este es el segundo punto donde el valor está referenciado y se puede ver en el fragmento justo debajo:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,

// ... Código interno ....      
                                
        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 = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp)));
                                m_Selection.sl = (valueSl == 0 ? 0 : 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(m_Selection.MousePrice); else MoveSelection(price);
                        }
                        break;

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

Con esto tenemos un comportamiento muy predecible dentro del sistema, también hay otro punto donde se referencia este valor, pero debido a la gran complejidad que implica, decidí modificar todo, haciendo que una macro deje de existir, quedando ahora una rutina. La nueva rutina Update se ve justo debajo:

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0, bPen = true;
                                
        if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
        {
                bPen = (pr = macroGetPrice(ticket, IT_RESULT, EV_LINE)) == 0;
                if (bPen && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0))
                {
                        CreateIndicator(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                        ChartRedraw();
                }
                pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE));
                SetTextValue(ticket, IT_PENDING, vol);
        }
        b0 = UpdateIndicatorsLimits(ticket, IT_TAKE, tp, vol, pr, isBuy, bPen);
        b0 = (UpdateIndicatorsLimits(ticket, IT_STOP, sl, vol, pr, isBuy, bPen) ? true : b0);
        if (b0) ChartRedraw();
}

Los puntos resaltados reemplazan la macro anterior, pero como dije que el código necesario era mucho más complejo, veamos dónde está realmente el tercer y último punto donde la nueva variable agregada al EA es referenciada. Noten por el código anterior que no hay diferencia entre el indicador de límite de take y el indicador de stop, ambos son procesados de la misma manera, la única diferencia es que uno representa la ganancia máxima y el otro no, pero para el EA ambos son iguales. Veamos el código abajo.

inline bool UpdateIndicatorsLimits(ulong ticket, eIndicatorTrade it, double price, double vol, double pr, bool isBuy, bool isPen)
{
        bool b0 = false;
        double d1 = Terminal.GetPointPerTick();
                
        if (
    price  == 0)
        {
                if (macroGetPrice(ticket, it, EV_LINE) > 0) RemoveIndicator(ticket, it);
                if (macroGetPrice(ticket, it, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, it);
        } else if (b0 = (macroGetPrice(ticket, it, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, it);
        switch (it)
        {
                case IT_TAKE:
                        price = (price == 0 ? 0 : (((isBuy ? price - pr : pr - price) > 0) ? price : (isBuy ? pr + d1 : pr - d1)));
                        break;
                case IT_STOP:
                        price = (price == 0 ? 0 : (isPen ? (((isBuy ? price - pr : pr - price) < 0) ? price : (isBuy ? pr - d1 : pr + d1)) : price));
                        break;
        }
        if (m_Selection.it == it) m_Selection.MousePrice = price;
        PositionAxlePrice(ticket, it, (price == 0 ? pr : price));
        SetTextValue(ticket, it, vol, (isBuy ? price - pr : pr - price));
                        
        return b0;
}

A partir de ahora en el caso de una orden pendiente el valor del take no puede ser errático, tenemos un límite máximo permitido para ser utilizado en el indicador. Colocar una orden de compra pendiente y tratar de mover el valor de toma a un valor NEGATIVO (es decir, por debajo del punto de entrada) ya no será posible, porque el cálculo del indicador de take lo impedirá. La ventaja de cómo se codificó esto es que, independientemente de que sea una orden o una posición, el valor del take nunca puede ser negativo, porque el propio EA lo impedirá.

Ahora con respecto al valor del stop, tenemos un cálculo un poco diferente, en él verificamos si estamos manipulando una orden o una posición. Si es una orden, el valor del stop nunca será positivo, si es una posición el EA simplemente ignorará cualquier otra condición. En este caso el EA aceptará el valor que el operador está colocando o indicando. De este modo, ahora podemos tener un valor de stop positivo, pero sólo en el caso de las posiciones, esto sin ningún tipo de perjuicio para el resto del código del sistema de órdenes, así el EA finalmente hablará con el servidor comercial de modo que no rechace los datos enviados.


3.0 - Conclusión

Finalmente, después de varios artículos, hemos llegado a nuestro clímax, ahora tenemos un sistema de órdenes prácticamente completo y bastante adaptable a diferentes situaciones y condiciones del mercado. A partir de ahora podremos operar con un sistema totalmente gráfico, utilizando el mouse y el teclado, más su análisis de mercado para entrar o salir de operaciones.

Para aquellos que acaban de llegar y quieren ver cómo se comporta el sistema o cómo se ve en el punto actual de desarrollo, miren el video a continuación... y gracias a todos los que siguieron esta secuencia hasta ahora... pero el trabajo no ha terminado todavía, aun tenemos mucho trabajo por hacer hasta que este EA se convierta en algo memorable... nos vemos 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/10635

Archivos adjuntos |
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 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 30): ¿¡El CHART TRADE ahora como indicador?! Desarrollo de un EA comercial desde cero (Parte 30): ¿¡El CHART TRADE ahora como indicador?!
Vamos a hacer uso del Chart Trade nuevamente... pero ahora será un indicador y podrá o no estar presente en el gráfico.
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.