English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
preview
Desenvolvendo um EA de negociação do zero (Parte 23): Um novo sistema de ordens (VI)

Desenvolvendo um EA de negociação do zero (Parte 23): Um novo sistema de ordens (VI)

MetaTrader 5Negociação | 12 julho 2022, 08:50
732 0
Daniel Jose
Daniel Jose

1.0 - Introdução

No artigo anterior Desenvolvendo um EA de negociação do zero (Parte 22) desenvolvemos um sistema para movimentar as ordens pendentes e os limites das posições, mas apesar de aquele método relativamente seguro, já que ele irá refletir o que esta no servidor de negociação, não é o melhor meio de fazer movimentos rápidos.

O problema é que cada vez que fazemos uma mudança usando o mouse, na verdade temos que informar ao servidor e ele irá devolver para nos a resposta, mas isto é estava sendo feito tick a tick ou seja, caso você queira mover o limite da ordem ou da posição vários ticks de uma só vez, você teria que passar por todos os valores intermediários oque deixa a coisa extremamente lenta, mas aqui irei mostrar como e onde mudar no código para se ter algo mais fluído, onde você pode modificar os limites da posição muito mais rapidamente.


2.0 - Planejamento

Para fazer isto, temos que fazer uma coisa bastante simples. NÃO IREMOS INFORMAR AO SERVIDOR TODAS AS MUDANÇAS, mas iremos informar apenas a que queremos. O simples fato de fazer isto já tornará a coisa toda bastante fluida, apesar de não termos absoluta certeza de que a coisa irá esta como fizemos no momento exato que a fizermos.

Vamos então olhar em qual ponto do código realmente teremos que mexer, e este é apenas uma única rotina, que é vista abaixo:

#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

Mas junto com as mudanças que faremos nela teremos que fazer também algumas relacionadas aos eventos do mouse, mas vamos focar primeiramente nesta.

As partes destacadas devem ser substituídas por alguma outra coisa, de forma a representar as novas posições que iremos usar, mas ao mesmo tempo temos que de alguma forma fazer com que as mudanças sejam facilmente entendidas pelo operador, caso contrário ele pode ficar perdido.

A forma mais simples que eu encontrei de deixar a coisa fácil de entender ao mesmo tempo que fique funcional sem fazer grandes mudanças em toda a estrutura do código, foi criar um indicador fantasma, algo que não será visto até você de fato ter que nota-lo no gráfico. Felizmente o MetaTrader 5 nos permite fazer isto de uma forma muito simples, então este artigo será bem fácil de entender para quem esta acompanhando a serie.


3.0 - Implementação

Para implementar um indicador fantasma, vamos simplesmente criá-lo junto com o indicador real, mas ele irá ser uma sombra perfeita do indicador real, até o momento que formos manipular os preços da forma como foi mostrado no artigo anterior, neste momento o indicador fantasma irá aparecer e você irá poder vê-lo no gráfico enquanto move o indicador real podendo assim comparar facilmente o que esta acontecendo e se de fato é ou não viável fazer as mudanças.


3.0.1 - Criando o indicador fantasma

Todas as mudanças serão feitas dentro da classe C_IndicatorTradeView. Vamos começar definindo 3 novas diretivas:

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

Feito isto, vamos fazer com que o MetaTrader 5 trabalhe para nos. Bem a regra é a seguinte: Primeiro criamos o indicador fantasma depois criamos o indicador real, fazendo desta forma o próprio MetaTrader 5 irá fazer com que o indicador fantasma não seja visível até o momento em que ele realmente precisar ser visto, e por conta do MetaTrader 5 fazer isto para nos irá nos poupar muito em termos de programação e lógica a ser desenvolvida.

Um detalhe é que se você desejar mudar a cor do fantasma bastará modificar a cor indicada na parte em destaque.

Então o próximo passo e modificar a rotina de criação de nomes exclusivos.

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

As partes em destaque foram adicionadas ou modificadas da versão anterior, e com isto teremos a possibilidade de fazer o MetaTrader 5 criar os nomes de forma única, então não precisamos nos preocupar com as coisas, mas observem que adicionei um valor aos eventos, isto para evitar que o fantasma receba eventos e tente tomar o controle.

O próximo passo é o mais evidente, criar o indicador em si:

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 a linhas em destaque criam os fantasmas, uma coisa que pode parecer estranha é por que não repliquei todos os elementos. O fato é que o fantasma não é uma cópia exata do indicador real, ele é apenas uma sombra, então não precisamos criar todos os elementos, quem irá realmente ditar o que irá acontecer é o indicador real, o fantasma serve apenas como referencia ao que estará sendo visto pelo servidor de negociação.

Agora vem a parte de detalhe onde iremos fazer o MetaTrader 5 realmente trabalhar mais pesado. Posicionar os objetos nos locais corretos, você talvez pense que isto dará muito trabalho, mas veja o que de fato foi mudado no 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

Mas é somente isto ?!?! Apenas modificou as macros ?!?! Sim, não é preciso ficar recriando todo o código, basta ajustar as coisas, e a única coisa que de fato é preciso fazer será dizer ao MetaTrader 5 o nome do objeto que estamos manipulando, o MetaTrader 5 irá fazer o resto para nos, muitos iriam pensar que seria necessário criar uma serie de rotinas para fazer isto, mas tudo que foi preciso fazer foi adicionar os pontos em destaque.

E a penultima rotina que iremos modificar nesta fase é mostrada abaixo:

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;
        }
}

E mais uma vez foi necessário fazer apenas as adições que estão em destaque. Com isto nosso fantasma esta criado e reflete exatamente o que acontece com o indicador REAL. Na verdade ele esta refletindo até bem demais, tanto que iremos ter que fazer mais uns ajustes para deixar as coisas funcionando de forma correta, não que elas estejam erradas, mas o fantasma esta muito ligado ao real, e não queremos isto.

E a última rotina que irá precisar ser mudada é mostrada abaixo:

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
                        }

Esta foi modificada nos pontos em detaque, não merecendo uma atenção especial.


3.0.2 - Separando o Fantasma do Real

As mudanças feitas no tópico anterior, criam o fantasma, mas temos um problema, ele esta muito ligado ao real, mas isto é algo que a primeira vista será muito trabalhoso de se resolvido, mas quando olhamos o código, vemos que já temos a solução no entanto ela esta no local errado, temos que mudar o local onde a solução está e deixa-la mais visível por toda a classe.

A solução pode ser vista no fragmento abaixo:

                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 ...

Sim, a solução é o código destacado .... mas como isto é possível ?!?! Lembre-se que quando você esta posicionando uma ordem pendente o sistema consegue fazer com que os dados sejam criados e manipulados de forma que no final você tem uma representação dos objetos no gráfico indicando onde a ordem será posicionada. Isto é feito pelo código abaixo:

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 ....

Baseado no fato de você estar pressionado a tecla SHIFT para compra ou CTRL para venda, o sistema irá criar uma representação da ordem pendente a ser criada e esta pode ser vista diretamente no gráfico. Os valores a serem usados como Take ou Stop são capturados dentro do Chart Trade, então você move a representação até o ponto que deseja colocar a ordem e depois disto ao pressionar o botão esquerdo do mouse, diz ao sistema que ali deverá ser colocada uma ordem pendente, e assim que ou mouse volta a se mover o indicador usado para representar a ordem é removido, e o indicador da ordem é deixado para tras.

Até neste ponto não existe nada de espetacular, mas ao observar o código mais profundamente vemos neste mesmo evento o fragmento abaixo:

// ... 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 ....

Então quando não estamos criando uma ordem pendente, e as teclas estão livres, enviamos a posição de preço do mouse para algum indicador que possa estar em destaque isto é feito na linha em destaque. Pois bem, assim que clicamos com o botão esquerdo encerramos esta transfêrencia, já que o indicador irá ficar deselecionado, e isto irá mudar o status de uma variável que atualmente é local, bIsMove, mas vamos mudar isto, mas esta variável tem seu status modificado justamente por outro evento dentro da função DispatchMessage, e este evento é visto abaixo:

// ... 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 irá modificar o status da variável bIsMove ao mesmo tempo que irá fazer com que a linha de algum indicador mude, indicado que ele esta ou não selecionado.

Então se transformarmos esta variável em uma variável visível por toda a classe, podemos separar o fantasma do real e apenas o real seria manipulado ou apenas o fantasma seria, a escolha irá depender do que você achar mais interessante, mas aqui irei mudar o real, e o fantasma irá indicar o que estará sendo visto pelo servidor de negociação.

Desta forma não precisarei mexer muito no código, apenas ajustar alguns detalhes, e assim que o clique com o botão esquerdo for dado o mover um indicador, ele irá mandar um comando que fará a ordem, ou limite ser modificado.

Mas vamos ver como isto será feito na prática. Primeiramente vamos criar uma variável privativa.

bool    m_bIsMovingSelect;

esta irá refletir o que expliquei acima, mas precisamos fazer com que ele seja inicializada.

C_IndicatorTradeView() : m_bIsMovingSelect(false) {}

Agora vamos para a rotina DispatchMessage e usar esta variável no lugar da 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;
                }
}

Os pontos destacados mostram onde as mudanças aconteceram. Desta forma agora toda a classe sabe quando estamos ou não com algum indicador selecionado pelo operador, e desta forma podemos separar o fantasma do real e assim ter uma indicação mais adequada.


3.0.3 - Movendo apenas o que interessa

Nos dois tópicos anteriores criamos e fizemos alguns ajustes no sistema de forma a ter uma indicação fantasma, mas agora precisamos mover as coisas de forma independente e para fazer isto teremos algum trabalho pela frente. A primeira coisa a se fazer será criar um estrutura para guardar diversas informações que precisamos para evitar um excesso de chamadas entre funções a ponto de saber o que estava acontecendo. Esta estrutura é vista abaixo:

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

O ponto em destaque já se encontrava no código, mas agora irá fazer parte desta estrutura, não se preocupem pois conforme as adições forem sendo mostradas, ficará mais claro do porque da estrutura ter estes elementos.

Aqui irei dar algum destaque apenas as funções que sofreram modificação que merece alguma explicação. A primeira é a função 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;
        }
}

Quando estamos movendo algo, não queremos que o fantasma siga os novos dados, queremos que ele fique parado, mostrando onde o indicador estava, isto é conseguido facilmente adicionando os pontos em destaque, desta forma o fantasma ficará parado enquanto o indicador se move livremente. Apesar de tudo aqui não teremos o movimento em si, mas a indicação dos valores que estão no servidor.

O próximo é o código de movimento em si, e este é visto abaixo:

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;
        }
}

O real detalhe que devemos no atentar é na parte em destaque, ela corrige o movimento para que o sistema indique os dados de forma correta, parece estranha fazer isto, mas dentro da rotina UpdateINfosIndicadors existe uma outra correção, e se não fizermos isto agora, teremos problemas depois, o restante da função não merece grande destaque, sendo algo bem simples.

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;
        }
}

A função acima ira informar ao servidor de negociação o que esta acontecendo, e quais são os novos dados de negociação. Perceba nas linhas em destaque que temos que ter algum indicador selecionado, caso contrário nenhum pedido será enviado para o servidor.

A ultima rotina de destaque é vista logo abaixo:

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 rotina tem vários pontos que merecem destaque, mas quero que reparem no quanto ela ficou diferente da versão anterior, aqui estamos reutilizando bem mais o código, desta forma caso tenha alguma falha em alguma outra parte do código, iremos logo notar, e poderemos corrigir esta falha, mas anteriormente esta rotina não se beneficiava com tais melhorias, ficando o código meio inconsistente, tendo certos pontos sido corrigidos, enquanto ainda teríamos falhas em outros pontos, um dos principais pontos, é que quando iriamos colocar uma ordem pendente havia um sistema separado do usado para mover os indicadores, mas agora temos um sistema único, o mesmo usado para pendurar uma ordem no gráfico também é usado para mover os indicadores, ou seja agora o mesmo evento que no caso o CHARTEVENT_MOUSE_MOVE tanto é usado para colocar uma ordem pendente, quanto para mover os indicadores, pode parecer algo de pouco valor, mas fazer isto torna qualquer mudança no código visível e caso tenhamos um problema ele irá se manifestar sempre que estivermos usando o evento de mouse.


4.0 - Conclusão

Para ficar mais claro o que esta acontecendo com as mudanças feitas, veja o video abaixo, e entenda que agora só falta alguns poucos detalhes e o EA estará completo em termos de sistema de ordens.



Arquivos anexados |
Ciência de Dados e Aprendizado de Máquina (Parte 02): Regressão Logística Ciência de Dados e Aprendizado de Máquina (Parte 02): Regressão Logística
A classificação de dados é uma coisa crucial para um algotrader e um programador. Neste artigo, nós vamos nos concentrar em um dos algoritmos de classificação logística que provavelmente podem nos ajudar a identificar os Sims ou Nãos, as Altas e Baixas, Compras e Vendas.
Como desenvolver um sistema de negociação baseado no indicador CCI Como desenvolver um sistema de negociação baseado no indicador CCI
Neste novo artigo da nossa série para aprender a projetar os sistemas de negociação, eu apresentarei o indicador Commodities Channel Index (CCI), eu explicarei suas especificidades e compartilharei com você como criar um sistema de negociação baseado neste indicador.
Como desenvolver um sistema de negociação baseado no indicador MACD Como desenvolver um sistema de negociação baseado no indicador MACD
Neste artigo, nós aprenderemos uma nova ferramenta de nossa série: aprenderemos como projetar um sistema de negociação com base em um dos indicadores técnicos mais populares, o Moving Average Convergence Divergence (MACD).
Desenvolvendo um EA de negociação do zero (Parte 22): Um novo sistema de ordens (V) Desenvolvendo um EA de negociação do zero (Parte 22): Um novo sistema de ordens (V)
Vamos continuar o desenvolvimento do novo sistema de ordens. Não é nada fácil implementar um sistema novo, muitas vezes nos deparamos com questões que dificultam muito os próximos passos, nestes casos temos que parar, e reanalisar a direção que esta sendo tomada.