English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Desarrollo de un EA comercial desde cero (Parte 23): Un nuevo sistema de órdenes (VI)

Desarrollo de un EA comercial desde cero (Parte 23): Un nuevo sistema de órdenes (VI)

MetaTrader 5Trading | 23 septiembre 2022, 17:26
835 0
Daniel Jose
Daniel Jose

1.0 - Introducción

En el artículo anterior Desarrollo de un EA comercial desde cero (Parte 22), desarrollamos un sistema para mover órdenes pendientes y límites de posición. Pero aunque ese método es relativamente seguro, ya que refleja lo que hay en el servidor de operaciones, no es la mejor manera de hacer movimientos rápidos.

El problema es que cada vez que hacemos un cambio con el ratón, tenemos que informar al servidor y esperar a que nos dé una respuesta. Pero esto se hacía tick a tick, lo que significa que, si queremos mover el límite de la orden o la posición varios ticks a la vez, tendríamos que pasar por todos los valores intermedios, lo que hace que todo sea extremadamente lento. Es por ello que aquí mostraré cómo y dónde hacer cambios en el código para tener algo más fluido que nos permita modificar los límites de posición mucho más rápido.


2.0 - Planificación

Para ello, tenemos que hacer algo muy sencillo: NO INFORMAREMOS AL SERVIDOR DE TODOS LOS CAMBIOS, sino sólo del que queramos. El simple hecho de hacer esto hará que todo funcione bien, aunque no estaremos absolutamente seguros de que todo será como lo hacemos en el momento exacto en que lo hacemos.

Así que vamos a ver en qué parte del código tenemos que hacer realmente la modificación, y ese punto es una sola rutina, que se ve a continuación:

#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP))
                void MoveSelection(double price, uint keys)
                        {
                                static string memStr = NULL;
                                static ulong ticket = 0;
                                static eIndicatorTrade it;
                                eEventType ev;
                                double tp, sl, pr;
                                bool isPending;
                                
                                string sz0 = m_TradeLine.GetObjectSelected();
                                
                                if (sz0 != NULL)
                                {
                                        if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev);
                                        isPending = OrderSelect(ticket);
                                        switch (it)
                                        {
                                                case IT_TAKE:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP));
                                                        else ModifyPosition(ticket, price, macroGetPrice(IT_STOP));
                                                        break;
                                                case IT_STOP:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price);
                                                        else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price);
                                                        break;
                                                case IT_PENDING:
                                                        pr = macroGetPrice(IT_PENDING);
                                                        tp = macroGetPrice(IT_TAKE);
                                                        sl = macroGetPrice(IT_STOP);
                                                        ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr));
                                                        break;
                                        }
                                };
                        }
#undef macroGetPrice

Pero junto con los cambios que haremos en él, también tendremos que hacer algunos cambios relacionados con los eventos del ratón, pero nos centraremos en estos primero.

Las partes resaltadas deben ser sustituidas por otra cosa, de forma que representemos las nuevas posiciones que vamos a utilizar, pero al mismo tiempo tenemos que hacer de alguna manera que los cambios sean fáciles de entender para el operador, ya que de lo contrario este podría confundirse.

La forma más sencilla que encontré para que todo fuera fácil de entender y al mismo tiempo funcional sin hacer grandes cambios en toda la estructura del código fue crear un indicador fantasma, algo que no se verá hasta que tengamos que notarlo en el gráfico. Afortunadamente MetaTrader 5 nos permite hacer esto de una manera muy sencilla, por lo que este artículo será muy fácil de entender para aquellos que están siguiendo la serie.


3.0 - Implementación

Para implementar un indicador fantasma, simplemente lo crearemos junto con el indicador real. Será una sombra exacta del indicador real hasta el momento en que vayamos a manipular los precios de la manera mostrada en el artículo anterior. En dicho momento, aparecerá el indicador fantasma, y ustedes podrán verlo en el gráfico mientras mueven el indicador real, por lo que podrán comparar fácilmente lo que está ocurriendo y si es factible o no realizar los cambios.


3.0.1 - Creación del indicador fantasma

Todos los cambios se harán dentro de la clase C_IndicatorTradeView. Empecemos por definir 3 nuevas directivas:

#define def_IndicatorGhost      "G"
#define def_IndicatorReal       "R"
#define def_IndicatorGhostColor clrDimGray

Una vez hecho esto, vamos a hacer que MetaTrader 5 trabaje para nosotros. La regla es la siguiente: primero creamos el indicador fantasma y luego el indicador real. De esta forma, el propio MetaTrader 5 se asegurará de que el indicador fantasma no sea visible hasta el momento en que realmente deba ser visto. Y como esto es realizado por cuenta del MetaTrader 5, nos ahorraremos mucho en términos de programación y lógica por desarrollar.

Un detalle es que, si se desea cambiar el color del fantasma, basta con modificar el color indicado en la parte resaltada.

Por lo tanto, el siguiente paso es modificar la rutina que permite la creación de nombres únicos.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}

Las partes resaltadas se han añadido o modificado con respecto a la versión anterior. Con esto, tendremos la posibilidad de hacer que MetaTrader 5 cree los nombres de forma única. Así que no tenemos que preocuparnos de esta tarea, pero observen que he añadido un valor a los eventos, y lo he hecho para evitar que el fantasma reciba eventos e intente tomar el control.

El siguiente paso es el más obvio, crear el propio indicador:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2, cor3;
                                string sz0, sz1;
                                
                                switch (it)
                                {
                                        case IT_TAKE    :
                                                cor1 = clrForestGreen;
                                                cor2 = clrDarkGreen;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_STOP    :
                                                cor1 = clrFireBrick;
                                                cor2 = clrMaroon;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_PENDING:
                                                cor1 = clrCornflowerBlue;
                                                cor2 = clrDarkGoldenrod;
                                                cor3 = def_ColorVolumeEdit;
                                                break;
                                        case IT_RESULT  :
                                        default:
                                                cor1 = clrDarkBlue;
                                                cor2 = clrDarkBlue;
                                                cor3 = def_ColorVolumeResult;
                                                break;
                                }
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor);
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2);
                                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                                if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor);
                                m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1);
                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 92, 22);
                                                m_BackGround.Size(sz1, 92, 22);
                                                break;
                                        case IT_RESULT:
                                                m_BackGround.Size(sz1, 84, 34);
                                                break;
                                }
                                m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose);
                                m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0);
                                m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0);
                                m_EditInfo1.Size(sz0, 60, 14);
                                m_EditInfo1.Size(sz1, 60, 14);
                                if (it != IT_RESULT)
                                {
                                        m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor);
                                        m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                                        m_BtnMove.Size(sz1, 21, 21);
                                }else
                                {
                                        m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0);
                                        m_EditInfo2.Size(sz1, 60, 14);
                                }
                        }

Todas las líneas resaltadas crean los fantasmas, y una cosa que puede parecer extraña es por qué no he reproducido todos los elementos. La realidad es que el fantasma no es una copia exacta del indicador real, es sólo una sombra. Así que no necesitamos crear todos los elementos. El indicador real es el que dictará lo que va a ocurrir, el fantasma sólo sirve como referencia de lo que verá el servidor de trading.

Ahora viene la parte que requiere más detalle, donde haremos que MetaTrader 5 realmente trabaje duro. Ustedes pueden pensar que colocar los objetos en los lugares correctos será mucho trabajo, pero miren lo que realmente cambió en el código original.

#define macroSetAxleY(A, B)     {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y);                              \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y);                                 \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y);                                 \
                m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0));      \
                m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y));         \
                m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1);  \
                                }
                                                                        
#define macroSetAxleX(A, B, C)  {                                                       \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B);      \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B);         \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3);     \
                m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21);    \
                m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80);      \
                m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21);  \
                                }                                                                               
//+------------------------------------------------------------------+
inline void ReDrawAllsIndicator(void)
                        {
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, -1);
                                ulong           ticket;
                                double          price;
                                eIndicatorTrade it;
                                eEventType      ev;
                                
                                for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev))
                                        PositionAxlePrice(ticket, it, price);
                        }
//+------------------------------------------------------------------+
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price));
                                macroSetAxleY(it, true);
                                macroSetAxleY(it, false);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 110; break;
                                        case IT_STOP: desl = 220; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true);
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false);
                        }
#undef macroSetAxleX
#undef macroSetAxleY

¡¿Pero eso es todo?! ¿Sólo modificar las macros? Sí, no es necesario recrear todo el código, solo hay que retocar las cosas, y lo único que realmente hay que hacer es decirle a MetaTrader 5 el nombre del objeto que estamos manipulando, y MetaTrader 5 hará el resto por nosotros. Muchos pensarían que tendríamos que crear una serie de rutinas para hacerlo, pero todo lo que necesitamos es añadir los puntos resaltados.

Y la penúltima rutina que modificaremos en esta fase se muestra a continuación:

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;
                                
        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Y de nuevo fue necesario hacer sólo las adiciones que se destacan. Con esto nuestro fantasma se crea y refleja exactamente lo que sucede con el indicador REAL. En realidad, está reflejando demasiado bien, tanto que tendremos que hacer algunos ajustes más para que las cosas funcionen de forma correcta, no es que estén mal, pero el fantasma está demasiado vinculado a lo real, y no queremos eso.

Y la última rutina que habrá que cambiar se muestra a continuación:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                                {
                                        macroDestroy(IT_RESULT, true);
                                        macroDestroy(IT_RESULT, false);
                                        macroDestroy(IT_PENDING, true);
                                        macroDestroy(IT_PENDING, false);
                                        macroDestroy(IT_TAKE, true);
                                        macroDestroy(IT_TAKE, false);
                                        macroDestroy(IT_STOP, true);
                                        macroDestroy(IT_STOP, false);
                                } else
                                {
                                        macroDestroy(it, true);
                                        macroDestroy(it, false);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
                        }

Esto se modificó en los puntos resaltados, lo que no merece especial atención.


3.0.2 - Separemos el fantasma del real

Los cambios realizados en el apartado anterior crean el fantasma, pero tenemos un problema, y es que está muy conectado con lo real. Esto es algo que a primera vista será muy laborioso de resolver, pero cuando miramos el código, vemos que ya tenemos la solución, sin embargo está en el lugar equivocado, tenemos que cambiar el lugar donde está la solución y hacerla más visible en toda la clase.

La solución puede verse en el siguiente fragmento:

                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price, tp, sl;
                                bool            isBuy,
                                                        bKeyBuy,
                                                        bKeySell,
                                                        bEClick;
                                long            info;
                                datetime        dt;
                                uint            mKeys;
                                eIndicatorTrade         it;
                                eEventType                      ev;
                                
                                static bool bMounting = false, bIsDT = false, bIsMove = false;
                                static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_MOUSE_MOVE:

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

Sí, la solución es el código resaltado..., pero ¡¿cómo es posible?! Recordemos que cuando se está posicionando una orden pendiente el sistema consigue que se creen y manipulen los datos para que al final se tenga una representación de los objetos en el gráfico que indique dónde se posicionará la orden. Esto se hace con el siguiente código:

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)
                {
                        Mouse.Hide();
                        bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl);
                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange);
                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange);
                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                        bMounting = true;
                }
                tp = price + (bKeyBuy ? valueTp : (-valueTp));
                sl = price + (bKeyBuy ? (-valueSl) : valueSl);
                UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy);
                if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT);
                }else if (bMounting)
                {
                        UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false);
                        Mouse.Show();
                        memLocal = 0;
                        bMounting = false;

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

Basándose en el hecho de que ustedes están presionando SHIFT para la compra o CTRL para la venta, el sistema creará una representación de la orden pendiente por crear y esto se puede ver directamente en el gráfico. Los valores a utilizar como Take o Stop se capturan dentro del Chart Trade, por lo que ustedes mueven la representación hasta el punto en el que desean colocar la orden, y después, pulsando el botón izquierdo del ratón, le indican al sistema que allí debe colocarse una orden pendiente, y en cuanto el ratón se vuelve a mover, se elimina el indicador utilizado para representar la orden, y se deja atrás el indicador de la orden.

Hasta aquí no hay nada espectacular, pero profundizando en el código vemos en este mismo evento el fragmento de abajo:

// ... Código do CHARTEVENT_MOUSE_MOVE ....

        }else if ((!bMounting) && (bKeyBuy == bKeySell))
        {
                if (bEClick)
                {
                        bIsMove = false;
                        m_TradeLine.SpotLight();
                }
                MoveSelection(price, mKeys);
        }
break;

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

Así, cuando no estamos creando una orden pendiente y las teclas están libres, enviamos la posición del precio del ratón a algún indicador que pueda estar resaltado. Esto se hace en la línea resaltada. Pues bien, en cuanto hagamos clic con el botón izquierdo terminaremos esta transferencia, ya que el indicador será deseleccionado, y esto cambiará el estado de una variable que actualmente es local, bIsMove. Pero vamos a cambiar esto, esta variable tiene su estado cambiado justo por otro evento dentro de la función DispatchMessage, y este evento se ve a continuación:

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

        case EV_MOVE:
                if (bIsMove)
                {
                        m_TradeLine.SpotLight();
                        bIsMove = false;
                }else
                {
                        m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                        bIsMove = true;
                }
        break;

Este código cambiará el estado de la variable bIsMove al mismo tiempo que hará que cambie la línea de algún indicador, lo que indicará que está o no seleccionado.

De esta manera, si convertimos esta variable en una variable visible en toda la clase, podemos separar el fantasma del real, y sólo se manipularía el real o sólo el fantasma - la elección dependerá de lo que les resulte más interesante. Pero aquí voy a cambiar el real, y el fantasma indicará lo que está siendo visto por el servidor de trading.

De esta manera no tendré que trastear mucho en el código, solo ajustar algunos detalles. Y en cuanto se dé el clic izquierdo y se mueva un indicador, se enviará una orden que hará que se modifique la orden o el límite.

Pero veamos cómo se hace en la práctica. Primero vamos a crear una variable privada.

bool    m_bIsMovingSelect;

Esto reflejará lo que he explicado anteriormente, pero tenemos que conseguir que sea inicializada.

C_IndicatorTradeView() : m_bIsMovingSelect(false) {}

Ahora vayamos a la rutina DispatchMessage y usemos esta variable en lugar de bIsMove.

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

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

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

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

                                }else if ((!bMounting) && (bKeyBuy == bKeySell))
                                {
                                        if (bEClick)
                                        {
                                                m_bIsMovingSelect = false;
                                                m_TradeLine.SpotLight();
                                        }
                                        MoveSelection(price, mKeys);
                                }
                                break;

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

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {

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

                                case EV_MOVE:
                                        if (m_bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_bIsMovingSelect = false;
                                        }else
                                        {
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                                m_bIsMovingSelect = true;
                                        }
                                        break;
                        }
                        break;
                }
}

Los puntos resaltados muestran dónde se han producido los cambios. De esta manera ahora toda la clase sabe cuando estamos o no con algún indicador seleccionado por el operador. De esta manera podemos separar el fantasma del real y así tener una mejor indicación.


3.0.3 - Movamos solo lo que importa

En los dos temas anteriores creamos e hicimos algunos ajustes en el sistema para tener una indicación fantasma, pero ahora necesitamos mover los componentes de forma independiente, y para ello tendremos algo de trabajo por delante. Lo primero será crear una estructura para albergar diversa información que necesitamos para evitar un exceso de llamadas entre funciones. Podemos ver esta estructura a continuación:

struct st00
{
        eIndicatorTrade it;
        bool            bIsMovingSelect,
                        bIsBuy;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_InfoSelection;

El punto destacado ya estaba en el código, pero ahora formará parte de esta estructura. No se preocupen, a medida que se muestren los añadidos, quedará más claro por qué la estructura tiene estos elementos.

Aquí destacaré solo las funciones que han cambiado y que merecen alguna explicación. La primera es la función SetTextValue.

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;

        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Cuando estamos moviendo algo, no queremos que el fantasma siga los nuevos datos, queremos que se quede quieto, mostrando dónde estaba el indicador. Esto se consigue fácilmente añadiendo los puntos resaltados. De este modo, el fantasma se quedará quieto mientras el indicador se mueve libremente. Al fin y al cabo, aquí no tendremos el movimiento en sí, sino la indicación de los valores que hay en el servidor.

Lo siguiente es el código de movimiento propiamente dicho, que se ve a continuación:

void MoveSelection(double price)
{
        double tp, sl;
                                
        if (!m_InfoSelection.bIsMovingSelect) return;
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_PENDING:
                        tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr);
                        sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr);
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
        }
}

El verdadero detalle al que debemos prestar atención es la parte resaltada. Esta corrige el movimiento para que el sistema indique los datos correctamente. Parece extraño hacer esto, pero dentro de la rutina UpdateINfosIndicators hay otra corrección. Y si no lo hacemos ahora, tendremos problemas más adelante. El resto de la función no merece mucha atención, siendo algo muy simple.

void SetPriceSelection(double price)
{
        bool isPending;
        if (!m_InfoSelection.bIsMovingSelect) return;
        isPending = OrderSelect(m_InfoSelection.ticket);
        m_InfoSelection.bIsMovingSelect = false;
        m_TradeLine.SpotLight();
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl);
                        else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl);
                        break;
                case IT_STOP:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price);
                        else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price);
                        break;
                case IT_PENDING:
                        ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr));
                        break;
        }
}

La función anterior informará al servidor de trading de lo que está ocurriendo y de los nuevos datos de comercio. Observen, en las líneas resaltadas, que debemos tener algún indicador seleccionado, de lo contrario no se enviará ninguna petición al servidor.

La última rutina destacada se ve justo debajo:

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, bIsDT = 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)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.ticket = def_IndicatorTicket0;
                                        m_InfoSelection.bIsMovingSelect = true;
                                        m_InfoSelection.pr = price;
                                        bMounting = true;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        MoveSelection(0);
                                        m_InfoSelection.bIsMovingSelect = false;
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                MoveSelection(0);
                                m_InfoSelection.bIsMovingSelect = false;
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev))
                        {
                                CreateIndicatorTrade(ticket, it);
                                GetInfosTradeServer(ticket);
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateInfosIndicators(0, ticket, m_InfoSelection.pr, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ChartSetInteger(ChartID(), CHART_SHOW_OBJECT_DESCR, false);
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        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_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                        }
                                        break;
                        }
                        break;
        }
}

Esta rutina tiene varios puntos que vale la pena resaltar, pero quiero que noten la diferencia con la versión anterior, aquí estamos reutilizando mucho más código. De este modo, si tenemos algún fallo en alguna otra parte del código, nos daremos cuenta pronto y podremos arreglarlo. Anteriormente esta rutina no se beneficiaba de tales mejoras, el código era un poco inconsistente, con ciertos puntos corregidos y defectos en otros. Uno de los puntos principales era que cuando colocábamos una orden pendiente había un sistema separado para mover los indicadores, pero ahora tenemos un único sistema, el mismo que se utiliza para colocar una orden en el gráfico se utiliza también para mover los indicadores, es decir, ahora el mismo evento CHARTEVENT_MOUSE_MOVE se utiliza tanto para colocar una orden pendiente como para mover los indicadores. Puede parecer algo insignificante, pero hacer esto hace visible cualquier cambio en el código, y si tenemos un problema, se manifestará siempre que estemos usando el evento del ratón.


4.0 - Conclusión

Para tener más claro lo que ocurre con los cambios realizados, vean el vídeo de abajo, y entiendan que ahora solo faltan algunos detalles y el EA estará completo en cuanto al sistema de órdenes.



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

Archivos adjuntos |
DoEasy. Elementos de control (Parte 10): Objetos WinForms: dando vida a la interfaz DoEasy. Elementos de control (Parte 10): Objetos WinForms: dando vida a la interfaz
Ha llegado el momento de revitalizar la interfaz gráfica de usuario, haciendo que los objetos interactúen con el usuario y otros objetos. Y para que los objetos más complejos funcionen correctamente, necesitaremos una funcionalidad que permita a los objetos interactuar entre sí y con el usuario.
Desarrollando un EA comercial desde cero (Parte 22): Un nuevo sistema de órdenes (V) Desarrollando un EA comercial desde cero (Parte 22): Un nuevo sistema de órdenes (V)
Hoy seguiremos desarrollando el nuevo sistema de ordenes. No es nada fácil implementar un nuevo sistema, muchas veces nos encontramos con problemas que dificultan mucho el proceso, cuando suceden hay que parar y volver a analizar el rumbo que se está tomando.
Trabajamos con matrices y vectores en MQL5 Trabajamos con matrices y vectores en MQL5
Para resolver problemas matemáticos, se han añadido a MQL5 matrices y vectores. Los nuevos tipos tienen métodos incorporados para escribir un código conciso y fácilmente comprensible que se acerque a una notación matemática. Los arrays son algo bueno, pero las matrices, en muchos casos, resultan mejores.
El modelo de movimiento de precios y sus principales disposiciones (Parte 2):  Ecuación de evolución del campo de probabilidad del precio y aparición del paseo aleatorio observado El modelo de movimiento de precios y sus principales disposiciones (Parte 2): Ecuación de evolución del campo de probabilidad del precio y aparición del paseo aleatorio observado
En el presente artículo, hemos derivado una ecuación para la evolución del campo probabilístico de precio, hemos encontrado un criterio para acercarnos al salto de precio, y también hemos revelado la esencia de los valores de precio en los gráficos de cotización y el mecanismo para la aparición de un paseo aleatorio de dichos valores .