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

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

MetaTrader 5Sistemas de negociação | 29 junho 2022, 15:20
778 0
Daniel Jose
Daniel Jose

1.0 - Introdução

No artigo anterior, desenvolvendo um EA de negociação do zero ( Parte 20 ), mostrei as principais mudanças que foram preciso ser feitas para começar a ter um sistema de ordens visuais, mas já que os próximos passos exigiria bastante em termos de explicação, decidi o artigo em mais partes, aqui vamos terminar de fazer as mudanças básicas, e elas não serão poucas, serão muitas e todas elas necessárias e todo o trabalho será bastante interessante, mas não irei terminar a coisa toda aqui, pois existem algumas coisas que deverão ser feitas para de fato terminar o sistema, mas ele já estará com quase todas os funcionalidades.

Então vamos direto para a implementação.


2.0 - Implementação

A primeira coisa que iremos fazer é implementar o botão de fechamento ou cancelamento, e ele irá ficar o indicador da ordem, a classe responsável por isto é vista a seguir.

2.0.1 A classe C_Object_BtnBitMap

Esta classe é a responsável por manter os botões do tipo bitmap no gráfico, ela pode ser vista na integra logo abaixo

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_BtnClose    "Images\\NanoEA-SIMD\\Btn_Close.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
		void Create(string szObjectName, string szResource1, string szResource2 = NULL)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
                bool GetStateButton(string szObjectName) const
                        {
                                return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
                        }
//+------------------------------------------------------------------+
};

Durante a codificação desta classe notei que o código de posicionamento poderia ser jogado para dentro da classe C_Object_Base, e com isto a classe C_Object_BackGround perdeu este código, já que agora ele irá pertencer a classe mais baixa, é isto que é conhecido como reutilização de código, você programa menos, produz mais, mas principalmente o código fica cada vez mais estável, já que você estará testando e testando as modificações que vão sendo feitas.

Para adicionar o Botão CLOSE, fazemos o seguinte:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_TradeLine.mqh"
#include "C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
class C_ObjectsTrade
{

// ... Código da classe ...

}

Próximo passo ...

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};

Próximo passo ...

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;

Próximo passo ...

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2;
                                string sz0;
                                

// ... Código interno da função ...

                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                                m_BackGround.Size(sz0, 92, 22);
                                                break;
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 110, 22);
                                                break;
                                }
                                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }

Próximo passo ...

#define macroDelete(A)  {                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));         \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));        \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete

e finalmente, o último passo ....

#define macroSetAxleY(A)        {                                               \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);    \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\
                                }
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {

// ... Código interno ...
                                
                        }
#undef macroSetAxleX
#undef macroSetAxleY

Executando o sistema teremos o seguinte resultado


No entanto este botão ainda não funciona, apesar do MT5 gerar o evento para que o EA o trate, ainda não implementamos isto, vamos deixar isto um pouco mais para frente, mas ainda dentro deste artigo.


2.0.2 - A classe C_Object_Edit

Bem o sistema não seria de grande utilidade se não tivesse um meio de informa para o operador os valores que estão sendo operados. e para isto temos a classe C_Object_Edit. Futuramente esta classe deverá sofrer algumas mudanças, para aumentar a sua funcionalidade, mas no momento, vou deixar ela em um nível que permita informar ao operador o que esta se passando e para isto precisamos de poucas coisas nesta classe, o primeiro fragmento a ser visto é

void Create(string szObjectName, color cor, int InfoValue)
{
        C_Object_Base::Create(szObjectName, OBJ_EDIT);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
        SetTextValue(szObjectName, InfoValue, cor);
}

O código em destaque garante que os valores não serão modificados pelo operador, mas como eu disse futuramente irie mudar isto, mas para isto é necessário fazer outras mudanças que não vem ao caso no momento, então evitamos de deixar o operador modificar os valores presentes no objeto.

A próxima rotina é a que faz a apresentação do texto, mas prestem atenção a um detalhe nesta rotina

void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE)
{
        color clr;
        clr = (cor != clrNONE ? cor  : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive));
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue));
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr);
}

O código em destaque irá dizer qual será a cor do fundo do texto, isto com base no valor informado, é importante que isto seja feito aqui, pois não queremos ter que ficar tentando adivinhar se o valor é negativo ou positivo, ou ficar focando em tentar ver se existe ou não um sinal negativo no texto, o que de fato queremos, é batemos o olho e assim ficar sabendo, se o valor é positivo ou negativo e isto de imediato, e desta forma como foi codificado isto irá acontecer, então será fácil saber se um valor é negativo ou positivo, mas existe uma condição que é o fato de que a cor não tenha sido definida anteriormente, isto será muito útil depois.

Continuando temos a ultima função desta classe que é vista abaixo

long GetTextValue(string szObjectName) const
{
        return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * 
                                (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1));
};

se prestar atenção, você irá perceber que no momento que apresentamos os valores eles sempre serão positivos, isto por que a formatação deles os obriga a isto, sempre ser positivo, mas quando vamos verificar o conteúdo do objeto, temos que ter a informação correta, e para fazer isto usamos o fragmento destacado, ele irá se basear na cor de fundo, se a cor indicar que o valor é negativo, ele será corrigido para representar a informação correta para o EA, caso ele seja positivo o valor irá se manter.

As definições de cores estão na própria classe, e podem ser modificadas se você desejar colocar outras cores depois, mas lembre-se de deixa-las diferentes, para que a função acima funcione corretamente, caso contrário o EA irá receber valores ambíguos, ou melhor dizendo quando o valor é negativo, o EA irá vê-lo como sendo positivo, e isto irá causar problemas em toda a analise que o EA deverá fazer.


2.0.3 - A Classe C_Object_Label

Esta é a última classe que precisamos neste momento, mas na verdade eu em alguns momentos cogitei não criá-la, já que ela irá fazer um papel parecido com o da classe C_Object_BtnBitMap, mas como queria também ter a possibilidade de colocar informações textuais sem depender da classe C_Object_Edit, decidi criar esta classe daqui.

O código dela é super mega simples e pode ser visto na integra logo abaixo

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Edit.mqh"
//+------------------------------------------------------------------+
class C_Object_Label : public C_Object_Edit
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        };
//+------------------------------------------------------------------+
};

E não se precisa de mais nada nesta classe, já que todo restante do trabalho já esta sendo feito pelas classes objeto que estão abaixo dela.

Virão como a OOP é poderosa, quanto mais organizamos os códigos, em classes, menos precisamos programar classes parecidas umas com as outras.

Mas existe uma pequena mudança que deveremos fazer, durante os experimentos, notei que ficava muito complicado interpretar os dados do indicador de resultado, então ele foi modificado conforme pode ser visto na imagem abaixo

    

Desta forma fica mais fácil apresentar valores maiores, então esta será a aparêcia do indicador de resultado, na parte de cima temos o numero de contratos, ou fator de alavancagem da posição aberta, já no campo inferior, o resultado a posição.

Por conta disto, houve a necessidade de uma mudança no classe C_Object_Base, já que ela é a responsável por grande parte do posicionamento dos objetos, e esta mudança se encontra em destaque no código abaixo

virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };

Com isto podemos passar para a próxima etapa, modificar a classe C_ObjectsTrade


2.0.4 - A classe C_ObjectsTrade

Agora vamos acabar de desenhar todos os objetos e assim poder de fato ter o resultado que queremos no gráfico, com cada um dos indicadores sendo apresentados, com todos os seu objetos interligados. O passo a passo aqui é bem simples, se você entender como de fato se faz, conseguirá colocar qualquer outra informação que você venha a desejar, é só seguir os passos e tudo irá dar certo. A primeira coisa a fazer, é definir os novos eventos que os objetos deverão responder, estes estão em destaque no fragmento abaixo

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};

Agora vamos adicionar os objetos, no caso os novos objetos também estão em destaque no fragmento abaixo:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;
C_Object_Edit           m_EditInfo,
                        m_InfoVol;
C_Object_Label          m_BtnMove;

Depois disto, vamos de fato criar os objetos, e definir como eles irão se parecer na tela, lembre-se do seguinte, os objetos deverão ser criados na ordem que eles devem aparecer, ou seja, primeiro o objeto de fundo, depois o objeto que ficará dentro dele, e assim sucessivamente até se criar todos, caso você faça algo fora da ordem, o objeto pode vim a ficar oculto, bastará trocar ele de posição aqui este código. Bem veja como isto é feito, para o que eu pretendo apresentar na tela do gráfico.

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        int infoValue;
                                
        switch (it)
        {
                case IT_TAKE    :
                        infoValue = m_BaseFinance.FinanceTake;
                        cor1 = clrForestGreen;
                        cor2 = clrDarkGreen;
                        cor3 = clrNONE;
                        break;
                case IT_STOP    :
                        infoValue = - m_BaseFinance.FinanceStop;
                        cor1 = clrFireBrick;
                        cor2 = clrMaroon;
                        cor3 = clrNONE;
                        break;
                case IT_PENDING:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrCornflowerBlue;
                        cor2 = clrDarkGoldenrod;
                        cor3 = clrLightBlue;
                        break;
                case IT_RESULT  :
                default:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrDarkBlue;
                        cor2 = clrDarkBlue;
                        cor3 = clrSilver;
                        break;
                }                               
                m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2);
                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1);
                switch (it)
                {
                        case IT_TAKE:
                        case IT_STOP:
                        case IT_PENDING:
                                m_BackGround.Size(sz0, 92, 22);
                                break;
                        case IT_RESULT:
                                m_BackGround.Size(sz0, 84, 34);
                                break;
                }
                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue);
                m_EditInfo.Size(sz0, 60, 14);
                if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                else
                {
                        m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue);
                        m_InfoVol.Size(sz0, 60, 14);
                }
}

Todos os pontos em destaque são as novas adições que o código sofreu desde a ultima versão vista no artigo anterior. Feito isto podemos codificar a próxima rotina

#define macroDelete(A)  {                                                                       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));                 \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));                \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT));                 \
                if (A != IT_RESULT)                                                             \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE));         \
                else                                                                            \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME));       \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete

Vejam o quanto a macro nos ajuda, precisei adicionar apenas as partes em destaque para que pode-se deletar todos os objetos de um indicador, e agora estamos usando 6 objetos em 4 indicadores, se fosse fazer isto de outra forma seria bastante trabalhoso e muito sujeito a erros. E para terminar a rotina de posicionamento

#define macroSetAxleY(A)        {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y);                         \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);                            \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);                            \
                m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));  \
                if (A != IT_RESULT)                                                                     \
                        m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y);                      \
                else                                                                                    \
                        m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1);                 \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B);                 \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);                    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);                \
                m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21);                \
                if (A != IT_RESULT)                                                             \
                        m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80);         \
                else                                                                            \
                        m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21);       \
                                }
                                                                                
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {
                                double ad;
                                int x, y;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetAxleY(it);
                                macroSetAxleX(it, m_PositionMinimalAlxeX);
                                if (Leverange == 0) return;
                                if (it == IT_PENDING)
                                {
                                        ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
                                        macroSetAxleY(IT_TAKE);
                                        macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110);
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
                                        macroSetAxleY(IT_STOP);
                                        macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220);
                                }
                        }
#undef macroSetAxleX
#undef macroSetAxleY

E novamente vejam que foi adicionado muito pouco código, mas mesmo assim a função consegue trabalhar com todos os elementos, posicionando cada um deles da forma correta, graças ao uso de macros. E o resultado ao compilarmos o EA neste estágio é visto logo abaixo:


Apesar de esta tudo muito lindo e maravilhoso, estes controles não funcionam ainda, temos que implementar os eventos para cada um dos objetos, pois sem eles esta interface é quase inútil, já que a única coisa que de fato ela irá fazer é substituir aquelas linhas que era usadas originalmente.


3.0 - Lidando com os problemas

Se tudo fosse simples qualquer um conseguiria fazer as coisas, mas na verdade sempre temos problemas para lidar e isto faz parte do processo de criação, eu poderia simplesmente resolve-los e não mostrar como foi feito, mas quero com estes artigos motivar vocês a lidar com os problemas e aprender a de fato programar, então este tópico será algo interessante.


3.0.1 - Ajustando as coisas conforme o gráfico se atualiza

Este é o primeiro problema que temos, e ele é causado pela falta de atualização das posições dos objetos conforme o gráfico sofre atualizações, para entender veja a animação abaixo:

Este tipo de coisa pode te deixar louco tentando resolver o problema, mas a solução é muito simples, o próprio MT5 gera um evento informando que o gráfico deve ser atualizado, tudo que precisamos fazer é capturar este evento e atualizar o nosso sistema de ordens.

Bem a captura deve ser feita quando é chamado o evento CHARTEVENT_CHART_CHANGE, e a atualização é mais simples se você utilizar a função UpdatePosition, presente no código do EA, desta forma tudo que precisamos fazer é adicionar uma única linha no nosso código, e isto é feito na classe C_OrderView conforme mostrado no fragmento abaixo:

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

// ... Código ....
                                
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

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

Esta solução bastante simples, tem um problema, se você tiver muitas ordens no ativo, o processo irá demorar um pouco, o que faz o EA ficar travado em resolver este ponto antes de voltar a fazer outras coisas, existem outras soluções mais complexas que deixa as coisas mais rápidas, mas para este sistema esta solução já resolve, e o resultado é visto abaixo


Parece estar correto não é mesmo ?!?! Mas existe uma falha ai. É difícil perceber a falha até que você de fato venha a experimenta-la, mas sim existe uma falha neste sistema, mesmo depois de temos corrigido ele, a falha está ai.


3.0.2 - Por favor EA pare de selecionar as coisas automaticamente

Se você olhar a animação acima irá perceber que a linha do Stop esta sendo selecionada, mesmo que você não tenha feito isto, o EA irá fazer isto toda a vez que você manipular o gráfico, e dependendo da configuração no momento em que os indicadores foram criados, pode acontecer de ser a linha do Take ou a da posição serão selecionadas pelo EA, e isto toda a vez que você mexer no gráfico.

Novamente você pode ficar doido tentando resolver isto, mas a solução é ainda mais simples que a anterior, bastará no mesmo fragmento adicionar uma outra linha e o EA deixará de ficar selecionando de forma automática uma das linhas, a correção é mostrada no fragmento abaixo em destaque

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        double infoValue;

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

        Select(NULL);
}

Este tipo de coisa sempre irá acontecer durante o processo de desenvolvimento, falhas que podem ser facilmente notadas, mas também outras que são mais sutis, e as vezes não notamos de inicio, mas de uma forma ou de outra elas sempre acontecem, por conta disto que é bom sempre aprender a programar, pois em alguns casos você mesmo pode resolver estas falhas, e reportar isto para que todos possam também corrigir o problema e assim todos irão ter um código que funcione de forma correta e sem falhas. Você pode experimentar isto, eu até incentivo tão pratica, pois foi assim que aprendi a programar, desta forma você irá aprender como produzir um programa, faz parte do aprendizado pegar um código fonte e sair modificando ele, com cuidado de observar os resultados, mas você deve fazer isto com um código funcional, desta forma é mais simples entender como ele foi construído, e isto muitas das vezes dá bons frutos, já que aprendemos muito entendendo como cada programador conseguiu resolver um problema especifico.

Bem mas isto daqui foi só algo para motivar vocês a estudarem, vamos continuar em algo de fato importante ....


4.0 - Tratando os Eventos

A primeira coisa que iremos fazer é montar o sistema que será responsável por informar o resultado de uma posição, isto irá substituir aquela área no Chart Trade, mas irei manter o Chart Trade intacto, e o motivo é que no Chart Trade é indicado o resultado geral das posições, e caso você esteja usando uma conta HEDGING o valor irá ser diferente do indicado no sistema de ordens, já que uma representa o valor local e a outra o valor geral, mas para os demais tipos de conta esta diferença não existirá então se desejar pode retirar o sistema de resultado do Chart Trade.

4.0.1 - Visualizando o resultado de uma posição

Quem observa o código pela primeira vez pode ficar perdido, e não saber onde procurar e buscar as informações, criando assim outras operações para fazer algo que o código original já faz, e isto é que costuma trazer muitos problemas, gerando código extra que pode gerar bugs que o código original não continha, além de sair da filosofia de reutilizar sempre, programar só quando necessário. Então conhecendo como o MT5 trabalha, e sabendo como o EA já esta funcionando, você deve procurar de onde é gerado o resultado que é apresentando no Chart Trade, pois se ele esta apresentado o resultado das posições, ali é que esta o que queremos e devemos usar, fazendo isto voltamos nossa atenção ao fragmento abaixo

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
        TimesAndTrade.Update();
}

Vamos então para o ponto em destaque, e o código original é visto abaixo

inline double CheckPosition(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())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        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;
                        };

Bem, você pode pensar, mas como vou tirar os dados dai, e aplicar eles no indicador, se não tenho como saber em qual dos objetos deverei referenciar ?!?! Durante a criação os objetos parecem ter sido criados de forma solta, sem nenhum tipo de critério ou cuidado ... Bem, sinto lhe informa, mas isto não é verdade, se você pensa, ou imaginou a coisa desta forma, é bom começar a estudar um pouco mais sobre como a plataforma MT5 de fato funciona. É bem verdade que não criei nenhum tipo de lista, array ou qualquer tipo de estrutura que referencia-se os objetos que estavam sendo criados, mas isto foi feito de proposito, eu procurei fazer assim, pois sei que funciona, e mostrarei a você que de fato funciona, você não precisa de nenhum tipo de estrutura para armazenar os objetos que estarão no gráfico para poder referenciar um em especifico, você precisa SIM, modelar de forma adequada o nome do objeto, apenas isto, modelar o nome do objeto.

Pergunta: Como o nome dos objetos foram modelados ?!?!

Eles foram modelados da seguinte forma:

1 - Sequencia do cabeçario Esta sequencia irá distinguir um objeto usado no sistema de ordens de todos os outros
2 - Carácter limitador Este serve para informar que alguma outra informação virá a seguir
3 - Indicador do tipo Este faz a diferença entra cada tipo de indicador, um indicador de Take é diferente de um de Stop
4 - Carácter limitador Mesma função do item 2
5 - Ticket da ordem ou posição Memoriza o valor do ticket da ordem, isto liga indicadores de uma ordem OCO e diferencia uma ordem de outra
6 - Carácter limitador Mesma função do item 2
7 - Indicador de evento Este diferencia os objetos dentro de um mesmo indicador

Ou seja, a modelagem é tudo, mesmo que pareça a quem de fato não programe que estamos criando algo repetitivo, na verdade estamos criado algo único, cada um dos objetos é único e pode ser referenciado por meio de uma regra simples. E esta regra é criada pelo seguinte código:

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev)
                        {
                                return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev);
                        }

Então se você disser ao código acima, qual o ticket da ordem, qual é o indicador e qual é o evento que queremos acessar, teremos o nome do objeto especifico, e desta forma podemos manipular os seus atributos. Saber disto é o primeiro passo, agora temos que tomar uma outra decisão: Como fazer está manipulação de forma segura, sem provocar um caos no código e assim transformar ele em um frankenstein ?!?!

Esta é a parte que irei fazer agora. Para fazer isto vamos a classe C_ObjectsTrade e adicionaremos o seguinte código

inline void SetResult(ulong ticket, double dVolume, double dResult)
                        {
                                m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult);
                                m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult);
                        }

Agora vamos a classe C_Router e iremos adicionar o fragmento em destaque

inline double CheckPosition(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())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT));
                                        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;
                        };

Isto resolve um dos problemas. mas temos outros problemas para resolver.


4.0.2 - Indicando o Volume em uma ordem pendente

Agora vamos resolver o problema do volume indicado em uma ordem pendente, para fazer isto temos que criar um novo procedimento na classe C_ObjectsTrade esta é vista abaixo

inline void SetVolumePendent(ulong ticket, double dVolume)
                        {
                                m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                        }

feito isto, usamos a rotina UpdatePosition presente na classe C_Router e a atualização acontecerá sem mais problemas

void UpdatePosition(int iAdjust = -1)
{

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

        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
                bTest = CheckLimits(price);
                vol = OrderGetDouble(ORDER_VOLUME_CURRENT);

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

                CreateIndicatorTrade(ul, price, IT_PENDING);
                SetVolumePendent(ul, vol);
                CreateIndicatorTrade(ul, take, IT_TAKE);
                CreateIndicatorTrade(ul, stop, IT_STOP);
        }
};

Com isto resolvemos este problema, agora temos que resolver o problema dos valores indicados como Take e Stop, pois estes valores não estão correspondendo a verdade, depois que colocamos a ordem no gráfico.


4.0.3 - Evento clicando no Botão Close do indicador

A única forma segura de remover uma ordem, ou um dos limites da mesma, é o botão close que está no canto de cada um dos indicadores. Mas aqui não temos de fato o evento sendo devidamente implementando, mas agora vamos corrigir isto.

Bem na classe C_OrderView, é que o evento clique de fato deverá ser implementando, iremos substituir o antigo sistema, pelo codigo quem destaque.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   	ticket;
        double  	price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType 	ev;
                                
        switch (id)
        {

// ... Código interno ...
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetInfosOrder(sparam, ticket, price, it, ev))
                        {
                                switch (ev)
                                {
                                        case EV_CLOSE:
                                                if (OrderSelect(ticket)) switch (it)
                                                {
                                                        case IT_PENDING:
                                                                RemoveOrderPendent(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                                break;
                                                }
                                                if (PositionSelectByTicket(ticket)) switch (it)
                                                {
                                                        case IT_RESULT:
                                                                ClosePosition(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0);
                                                                break;
                                                }
                                                break;

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

Bem mas ainda dentro desta mesma classe temos que adicionar uma coisa. O que aconteceria se o operador acidentalmente deletasse um objeto que informa dados da posição ?!?! Bem, não queira saber ... e para evitar isto adicionamos o seguinte código ao sistema.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType      ev;
                                
        switch (id)
        {
                case CHART_EVENT_OBJECT_DELETE:
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

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

desta forma caso o operador delete algo que não deve, o EA rapidamente irá repor o indicador ou objeto deletado.

Bem, no video a seguir você pode ver como esta atualmente o sistema em funcionamento, ocorreram algumas outras mudanças que eu não destaquei aqui no artigo por serem coisas de ordem menor, ou melhor dizendo, não iria agregar muito na explicação como um todo.




5.0 - Conclusão

Bem apesar do sistema parecer completo e você pode estar ansioso em operar usando ele, devo avisar que ele ainda não está concluído, este artigo foi para mostrar com você pode adicionar, modificar as coisas de forma a ter um sistema de ordens bem mais prático e simples de se trabalhar, mas ainda falta o sistema responsável por mover as posições e é justamente isto que fará o EA se tornar bastante didático, pratico e intuitivo em seu uso, mas isto irá ficar para o próximo artigo.

Em anexo deixo com vocês o sistema no atual estágio de desenvolvimento.


Arquivos anexados |
Como desenvolver um sistema de negociação baseado no indicador RSI Como desenvolver um sistema de negociação baseado no indicador RSI
Neste artigo, eu compartilharei com você um dos indicadores mais populares e comumente usados no mundo das negociações, o RSI. Você aprenderá como desenvolver um sistema de negociação usando este indicador.
O que você pode fazer com as Médias Móveis O que você pode fazer com as Médias Móveis
O artigo considera vários métodos de aplicação do indicador Média Móvel. Cada método que envolve uma análise de curva é acompanhado por indicadores que visualizam a ideia. Na maioria dos casos, as ideias mostradas aqui pertencem a seus autores respeitados. Minha única tarefa era reuni-los para permitir que você veja as principais abordagens e, esperançosamente, tome decisões de negociação mais razoáveis. Nível de proficiência em MQL5 — básico.
Ciência de Dados e Aprendizado de Máquina (Parte 01): Regressão Linear Ciência de Dados e Aprendizado de Máquina (Parte 01): Regressão Linear
É hora de nós, como traders, treinarmos nossos sistemas e a nós mesmos para tomar decisões com base no que o número diz. Não aos nossos olhos, e o que nossas entranhas nos fazem acreditar, é para onde o mundo está indo, então vamos nos mover perpendicularmente à direção da onda.
Gráficos na biblioteca DoEasy (Parte 97): Processando o movimento dos objetos-forma independentemente Gráficos na biblioteca DoEasy (Parte 97): Processando o movimento dos objetos-forma independentemente
No artigo de hoje, veremos como gerar o movimento independente de qualquer objeto-forma por meio do mouse, além disso, complementaremos a biblioteca com mensagens de erro e com as novas propriedades de negócios que foram introduzidas anteriormente no terminal e em MQL5.