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

Desenvolvendo um EA de negociação do zero (Parte 24): Dado robustez ao sistema (I)

MetaTrader 5Negociação | 19 julho 2022, 15:38
815 0
Daniel Jose
Daniel Jose

1.0 - Introdução

Diferente do que muitos pensam, algumas coisas não são tão simples. O sistema de ordens é uma destas coisas, você pode até criar um sistema mais modesto que lhe atenda perfeitamente bem, isto foi feito no artigo Desenvolvendo um EA do Zero onde foi criando um sistema bastante modesto e básico que pode até atender muitas das pessoas, mas para outras aquilo não é o suficiente, então foi chegado um momento em que a coisa começou a mudar de figura, desta forma nascia a primeira parte desta sequencia sobre um novo sistema de ordens isto pode ser visto no artigo Desenvolvendo um EA do zero (Parte 18), lá começamos a desenvolver um sistema que seria gerenciado pelo EA mas mantido pelo MetaTrader 5, onde a ideia era não ter um limite de ordens no gráfico, inicialmente o sistema parecia bastante ousado, devo confessar, o fato de criar um sistema onde os objetos não seriam mantidos pelo EA e sim pelo MetaTrader 5 parecia algo sem sentido e pouco eficiente.

Mas o sistema foi se desenvolvendo e no artigo Desenvolvendo um EA do zero (Parte 23) desenvolvemos um sistema fantasma para facilitar o manejo das ordens, posições ou pontos de limites ( Take e Stop ). Aquilo foi bastante interessante de ser desenvolvido, e até se mostrou bastante curioso, mas existe um problema. Se você olhar a quantidade de objetos usados e visíveis frente ao objetos mantidos pelo MetaTrader 5 irá se surpreender pois a quantidade mantida será sempre maior.

O problema não é tão grave em muitas das ocasiões, até dá para contornar e conviver com algumas coisas, mas existem duas questões que durante testes em momentos de grande volatilidade do mercado, fazia o sistema se mostrar pouco estável e em alguns casos fazendo o operador agir de forma errada, isto por que em alguns momentos o operador adicionava uma ordem pendente, o sistema enviava esta ordem para o servidor, e o servidor as vezes demorava um pouco mais do que o normal para responder, e o sistema simplesmente indicava em alguns momentos que existia uma ordem ali e em outros que não existia a ordem, e quando se fazia isto em posições ( existe uma diferença entre ordens e posições leia a documentação para mais detalhes )a coisa ficava ainda mais embaraçosa, ficando o operador totalmente sem saber se o servidor tinha ou não executado o comando conforme o esperado.

Existem diversas forma de se solucionar isto, algumas mais simples, outras mais complexas, mas independente disto, temos que confiar no EA, caso contrário não devemos usa-lo em hipótese alguma.


2.0 - Planejamento

A grande questão aqui é planejar um sistema que tenha duas qualidades, Rapidez e Robustez. Em alguns tipos de sistemas é bastante complexo conseguir ambas, para não dizer impossível então em muitos dos casos tentamos balancear as coisas, mas se tratando de dinheiro, e o NOSSO dinheiro, não queremos correr o risco de ter um sistema que não tenha estas qualidades. Lembrando que estamos tratando de um sistema que funciona em REAL TIME e este é o cenário mais complicado no qual um projetista irá se meter, pois temos que tentar sempre ter um sistema que seja extremamente rápido, ou seja responda rapidamente aos eventos, ao mesmo tempo que ele precisa ser robusto a ponto de não travar por conta de alguma tentativa de melhoria do mesmo. Então vejam que o desafio é bastante grande.

A rapidez pode ser conseguida fazendo com que as rotinas sejam chamadas e executadas de maneira o mais adequada possível, evitando ao máximo chamadas desnecessárias, em momentos ainda mais desnecessários, com isto teremos um sistema que seja o mais rápido quanto for possível dentro dos limites da linguagem, se bem que se você desejar algo ainda mais rápido terá que descer ao nível de linguagem de máquina, e neste caso estou falando em ASSEMBLY, mas isto muitas vezes é desnecessário, podemos usar a linguagem C e conseguir resultados tão bons quanto.

Uma das formas de se conseguir robustez é procuramos reutilizar ao máximo o código, desta forma ele será testado o tempo todo e em diversas ocasiões diferente, mas esta é apenas uma das formas, outra forma é o uso da programação OOP ( Programação Orientada em Objetos ), se isto for feito de forma correta e adequada, onde cada classe de objeto não manipula os dados de classes objetos diretamente, salvo em caso de herança, já será o suficiente para termos um sistema bastante robusto, isto em alguns momentos reduz a velocidade de execução, mas esta redução é tão pequena que praticamente pode ser ignorada pelo incremento exponencial gerado pelo encapsulamento fornecido pela classe, este encapsulamento nos dá a robustez que precisamos.

Vejam que não é tão simples quanto parece, obter um sistema que seja rápido ao mesmo tempo que robusto. Mas o grande detalhe é que não precisamos sacrificar tanto as coisas, como você pode estar imaginado a primeira vista, podemos simplesmente consultar a documentação do sistema e verificar o que pode ser mudado de forma a melhorar as coisas, o simples fato de não tentar reinventar a roda já será um bom começo, lembre-se programas e sistemas vivem em constante aperfeiçoamento, então é sempre bom tentar usar o máximo possível do que esta disponível, e somente no ultimo caso realmente reinventar a roda.

Antes que alguns achem desnecessário apresentar as mudanças que foram feitas neste artigo, ou acharem que estou mudando muito o código sem que ele de fato saia do lugar, deixe-me esclarecer um detalhe: Quando codificamos algo não temos de fato ideia de como o código final irá ficar, tudo que temos são os objetivos a serem alcançados, uma vez que este objetivo tenha sido alcançado começamos a olhar a forma como fizemos para alcançar este objetivo e começamos a tentar melhorar a coisas, de forma a torna-las melhores.

No caso de um sistema comercial, seja ele um executável, uma biblioteca, fazemos as mudanças e lançamos como uma atualização, o usuário não precisa de fato saber os caminhos envolvidos para se alcançar o objetivo, já que se trata de um sistema comercial, é até bom que ele de fato não saiba, mas se tratando de um sistema aberto, e no caso documentado, não quero dar a entender que você irá conseguir logo de cara desenvolver um sistema extremamente eficiente, assim logo de inicio, pensar desta forma não é adequado, chega até ser um insulto, já que por mais que um programador ou desenvolvedor tenha o conhecimento da linguagem a ser usada, sempre haverá coisas que podem ser melhoradas no decorrer do tempo.

Então não encarem esta sequencia como sendo algo que poderia ser resumido em 3 ou 4 artigos, pois se fosse assim seria melhor simplesmente que o código fosse criado, ficando da forma que eu achasse mais adequado e o lança-se de forma comercial. Minha intenção não é esta, aprendi a programar vendo o código de outros programadores mais experientes, sei do valor que isto tem. Saber como a coisa vai de desenvolvendo ao longo do tempo, é muito mais adequando do que simplesmente pegar a coisa já terminada e tentar entender como ela funciona.

Feito estas considerações vamos ao que interessa.


3.0 - Implementação

3.0.1 - Uma nova modelagem dos indicadores de posição

A primeira coisa a ser notada no novo formado do código é a mudança de uma rotina que agora passou a ser uma macro.

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

mesmo que o compilador de fato coloque este código em cada um dos pontos em que ele esta sendo referenciado, isto por conta da palavra reservada inline, não quero ficar sujeito a esta esperança, já que esta rotina é chamada muitas vezes dentro do código, tenho que garantir que ela seja de fato executada de forma o mais rápido quanto for possível, então o novo código dela ficará assim:

#define macroMountName(ticket, it, ev, Ghost) 								 \
		StringFormat("%s%c%llu%c%c%c%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,          \                                                                                                                                                                                                                                                                                                
                                                       ticket, def_SeparatorInfo,                        \                                                                                                                                                                                                                                        
                                                       (char)it, def_SeparatorInfo,                      \ 
                                                       (char)(Ghost ? ev + 32 : ev), def_SeparatorInfo,  \ 
                                                       (Ghost ? def_IndicatorGhost : def_IndicatorReal))

Notem que ocorreu uma mudança aqui entre os dados, da versão antiga para esta da macro, isto tem um motivo, que será visto mais para frente, neste mesmo artigo.

Mas por conta desta mudança temos também que fazer uma pequena mudança no código de outra rotina.

inline bool GetIndicatorInfos(const string sparam, ulong &ticket, eIndicatorTrade &it, eEventType &ev)
                        {
                                string szRet[];
                                char szInfo[];
                                
                                if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
                                if (szRet[0] != def_NameObjectsTrade) return false;
                                ticket = (ulong) StringToInteger(szRet[1]);
                                StringToCharArray(szRet[2], szInfo);
                                it = (eIndicatorTrade)szInfo[0];
                                StringToCharArray(szRet[3], szInfo);
                                ev = (eEventType)szInfo[0];

                                return true;
                        }

A mudança aqui foi somente no index a ser usado para indicar quem é o ticket e quem é o indicador, nada muito complicado, apenas um simples detalhe, mas tem que ser feito caso contrário teríamos dados incoerentes durante as a esta função.

Mas ai você se pergunta: Por que destas mudanças ?!?! O sistema já não estava funcionando perfeitamente ?!?! SIM, estava, mas existem coisas das quais você não controla de fato, a simples melhoria por parte dos que mantém e desenvolvem a plataforma MetaTrader 5 em algumas das funções que não estão sendo usadas no EA, não irá fazer com que nosso EA se beneficie. A regra é não tentar reinventar a roda, mas procurar usar os recursos que temos em mãos. Por conta disto devemos sempre tentar utilizar funções fornecidas pela linguagem que no caso é o MQL5, e evitar criar as nossas próprias funções, parece um absurdo, mas na verdade se você parar, observar e pensar verá que de tempos em tempos a plataforma passa por melhorias em algumas funções, e se você estiver utilizando estas mesmas funções irá ter um melhor desempenho e a segurança aumentada, em seus programas e isto sem precisar fazer nenhum esforço extra.

Desta forma os fins justificam os meios. Mas estas mudanças feitas acima ajudariam o EA se beneficiar de qualquer melhoria na biblioteca do MQL5 ?!?! A resposta é um sonoro NÃO, as mudanças feitas acima são necessárias para que a modelagem do nome dos objetos seja adequada para que possamos de fato nos beneficiarmos de alguma possível melhoria futura vinda por parte dos desenvolvedores do MQL5 e da plataforma MetaTrader 5. Um dos pontos que pode vim a se beneficiar é mostrado abaixo:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_InfoSelection.bIsMovingSelect = false;
        ChartRedraw();
}

A versão anterior deste mesmo código acima é vista logo abaixo, para quem não se lembra, ou não a observou antes, segue o código:

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
}

Pode parecer que o código ficou apenas mais compacto, mas não, isto é o que uma pessoa menos treinada irá achar, que apenas houve uma redução no código, mas a verdade é muito mais profunda, o que de fato aconteceu foi que o código antigo foi substituído por um novo que utiliza melhor os recursos da plataforma, no entanto a modelagem do nome dos objetos que existia antes não permitiria esta melhoria, então a modelagem foi refeita e assim passamos a poder contar com a possibilidade de usar uma função do próprio MQL5, assim caso esta função seja melhorada por qualquer motivo, o EA irá se beneficiar desta melhoria, isto sem precisar fazer nenhuma mudança na estrutura do EA. A função em questão é ObjectsDeleteAll, usando ela da forma correta, o próprio MetaTrader 5 irá fazer a limpeza para nos, e não iremos precisar ficar dando muitos detalhes, apenas informamos o prefixo do nome do objeto, ou objetos no caso, e deixamos o resto para o MetaTrader 5 fazer. Os pontos em que esta função é usada esta em destaque no código novo, observem como foi feita a modelagem para informar o prefixo a ser usado, isto não era possível antes da modificação da modelagem do nome dos objetos.

Quero chamar a atenção para um detalhe no fragmento do novo código, este esta em destaque logo abaixo.

if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));

Por que estou adicionando a parte em destaque ?? Você consegue imaginar o motivo ?!?!

O detalhe é que caso o sistema crie um ticket iniciando como um valor igual a 1 todos os objetos serão retirados da tela assim que a ordem pendente for colocada. Não entendeu ?!?! O ticket usado para colocar a ordem pendente tem como valor 1, ou seja o indicador 0 na verdade tem como valor 1 e não 0 já que o 0 é utilizado para executar outros testes dentro do EA, por conta disto o valor inicial será 1, agora temos um problema, vamos supor que o sistema de negociação crie um ticket 1221766803 então o objeto que irá representar este ticket terá como prefixo o seguinte valor: SMD_OT#1221766803 então quando o EA executar a função ObjectsDeleteAll para remover o indicador 0, o nome dos objetos seria SMD_OT#1, e com isto todos os objetos iniciados com esta informação serão deletados, incluindo o sistema recentemente criado. para resolver isto faço um pequeno ajuste no nome a ser informado a função ObjectsDeleteAll, de forma a adicionar um caracter extra no final do nome e assim a função saber se estamos removendo o indicador 0 ou outro indicador.

Então caso o indicador 0 seja o que irá ser removido, a função irá receber o seguinte valor SMD_OT#1# isto evitará o problema, ao mesmo tempo no caso do exemplo acima a função irá receber o seguinte nome SMD_OT#1221766803*, parece ser algo simples, mas pode fazer você ficar tentando entender por que o EA esta retirando o indicador de uma ordem recém colocada.

Agora um detalhe curioso, na nova função, que pode passar batido, observe que no final da função existe uma chamada para ChartRedraw, mas por que esta chamada esta ali ?!?! O MetaTrader 5 não faz esta atualização para nos ?!?! SIM ele faz, mas você nunca sabe exatamente quando isto irá acontecer, e temos um outro problema, todas as chamadas para colocação ou retirada de objetos no gráfico são chamadas síncronas, ou seja elas são executadas em um dado momento, mas não necessariamente naquele que você imagina, mas o nosso sistema de ordens estará utilizando objetos, seja para mostrar ou gerenciar as ordens, e temos que ter certeza se o objeto esta ou não no gráfico quando formos analisar as coisas, não podemos nos dar ao luxo de achar que o MetaTrader 5 já colocou, ou retirou os objetos do gráfico, temos que ter certeza disto, então forçamos a plataforma a fazer esta atualização.

Desta forma quando fazemos a chamada ChartRedraw iremos forçar a plataforma a atualizar a lista de objetos no gráfico, assim teremos a real certeza de que um determinado objeto esta ou não presente no gráfico. Se ainda assim você não compreendeu o por que disto, vamos ver o próximo tópico.


3.0.2 - Menos objetos é igual a mais velocidade

A rotina que inicializava os indicadores na versão anterior era muito custosa. Havia muitos testes que estavam sendo repetidos e algumas coisas estavam duplicadas, além de termos alguns problemas de menor grandeza, o sistema estava reutilizando muito pouco da capacidade que já havia sido implementada, então aproveitando a nova modelagem, decidi reduzir a quantidade de objetos que estava sendo criado durante a inicialização. Desta forma a rotina passou por um enxugamento ficando conforme mostrado abaixo:

void Initilize(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = OrdersTotal(); c0 >= 0; c0--) IndicatorInfosAdd(OrderGetTicket(c0));
        for (int c0 = PositionsTotal(); c0 >= 0; c0--) IndicatorInfosAdd(PositionGetTicket(c0));
}

Parece que ficou diferente, de fato ficou, agora estamos reutilizando uma rotina que estava sendo sub utilizada que era a rotina de adicionar indicadores no gráfico, vamos dar uma olhada nesta rotina muito especial.

inline void IndicatorAdd(ulong ticket)
{
        char ret;
                                
        if (ticket == def_IndicatorTicket0) ret = -1; else
        {
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if ((ret = GetInfosTradeServer(ticket)) == 0) return;
        }
        switch (ret)
        {
                case  1:
                        CreateIndicatorTrade(ticket, IT_RESULT);
                        PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                        break;
                case -1:
                        CreateIndicatorTrade(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                        break;
        }
        ChartRedraw();
        UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
}

Observe com atenção o código acima. A primeira vista ele parece conter testes que não fazem muito sentido, mas estes testes estão ai por um motivo muito simples. Esta rotina é a única porta de entrada para que um indicador de ordem pendente ou posição, seja de fato criado. desta forma as duas linhas em destaque irão verificar se o indicador existe, isto é feito verificando se há algum valor gravado no objeto que esta sendo usado como linha, no caso este valor será o valor do preço onde o objeto se encontra. Este valor somente será diferente de zero, caso o indicador esteja no gráfico, em todos os outros casos ele será zero, seja pelo fato do indicador não existir, seja por qualquer outro motivo, isto não importa, o indicador não existe. Viram por que temos que atualizar de forma forçada o gráfico ?!?! Se não ocorresse esta atualização forçada, o EA iria ficar adicionado objetos sem necessidade, por conta disto não podemos esperar que a plataforma faça isto em algum momento desconhecido, temos que ter certeza que o gráfico foi atualizado, caso contrário, quando estes testes forem feitos, eles irão informar coisas incoerentes com a atual situação dos indicadores, isto tornará o sistema menos confiável.

Apesar destes testes parecerem reduzir a velocidade do EA em si, isto é um erro conceitual, já que ao fazer tais testes, e evitar ficar tentando forçar a plataforma a criar um objeto que possivelmente já esta na fila para ser criado, falamos para a plataforma, ATUALIZE JÁ, e depois quando precisar testamos para verificar se o objeto já foi criado, e caso já tenha sido o utilizamos da forma desejada. Isto sim é programar de forma correta. Por que assim fazemos a plataforma trabalhar menos evitando que ela mesmo teste os objetos criados ou não, e o EA fica mais confiável, pois sabemos que temos os dados que desejamos trabalhar.

Então uma vez que os testes indicarão que não existe um indicador na gráfico que corresponda ao ticket informado, o indicador será criado. Mas observem que logo no começo fazemos um outro teste, e verificamos se estamos criando o indicador 0 ou outro indicador qualquer, isto faz com que não tenhamos nenhum objeto desnecessário sendo mantido pelo MetaTrader 5, temos apenas os objetos realmente usados atualmente no gráfico. Então se estamos criando o indicador 0, nenhum outro teste se faz necessário, já que ele será criado em condições bastante especiais e especificas. Este indicador 0 é usado para posicionar as ordens via comando SHIFT ou CTRL mais o uso do mouse, mas não se preocupem, logo verão como ele é trabalhado e mantido.

Mas tem um detalhe no código acima, por que estou atualizando o gráfico antes de chamar a rotina de Update ?!?! Isto não faz sentido. Para entender isto vamos ver a rotina UpdateIndicadors que esta logo abaixo.

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0 = false;
                                
        pr = macroGetLinePrice(ticket, IT_RESULT);
        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
        SetTextValue(ticket, IT_PENDING, vol);
        if (tp > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_TAKE, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_TAKE);
                PositionAxlePrice(ticket, IT_TAKE, tp);
                SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        }
        if (sl > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_STOP, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_STOP);
                PositionAxlePrice(ticket, IT_STOP, sl);
                SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
        }
        if (b0) ChartRedraw();
}

esta rotina basicamente irá tratar dos indicadores de limites. Agora repare nas duas linhas em destaque, se o gráfico não estiver atualizado, estas linhas irão falhar, retornando um valor igual a 0, e se isto acontecer todo o restante do código irá falhar, e os indicadores de limites não seriam corretamente apresentados na tela.

Mas observe que mesmo antes de criar os indicadores de limite, fazemos alguns testes, para ver se eles realmente devem ser criados, ou simplesmente ajustados, e isto é feito da mesma forma que fizemos ao criar o indicador base. E mesmo aqui quando os objetos dos indicadores são criados, também iremos forçar uma atualização no gráfico, desta forma estaremos sempre com o gráfico o mais atual possível.

Mas ai você se pergunta; Por que de tantas atualizações forçadas ?!?! Isto é mesmo necessário ?!?! E a resposta é um GRANDE e SONORO SIM ... e o motivo é a rotina logo a seguir:

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

você pode pensar que esta rotina não tem nada de especial. Certo ?!?! ERROU ... esta rotina contém um ponto que temos que ter certeza que o indicador esta na gráfico, caso contrário todo o código de criação dele será chamado varias vezes, criando assim uma grande fila para o MetaTrader 5 gerenciar, e pode ser que alguns destes dados sejam perdidos, ou fiquem demasiadamente desatualizados, isto torna o sistema instável, menos seguro e consequentemente pouco confiável. notem que temos uma chamada a função que cria o indicador, em destaque, caso não estivéssemos forçando o MetaTrader 5 a atualizar o gráfico para nos, em pontos estratégicos, poderíamos ter problemas, já que esta rotina acima é chamada justamente pelo evento OnTick e em momentos de grande volatilidade o numero de chamadas vindas de OnTick é bastante alto, isto poderia gerar um excesso de objetos na fila o que não é nada bom. Então forçamos as atualizações via chamada ChartRedraw, e testamos via ObjectGetDouble, desta forma reduzimos a chance que isto venha a ocorrer, um excesso de objetos na fila.

E logo de cara sem se quer olhar como o sistema esta operando, você já pode pensar. Que bom agora caso o objeto TradeLine seja deletado acidentalmente, o EA irá notar isto e quando for feito o teste via ObjectGetDouble e este teste falhar o indicador será recriado. É isto mesmo, e esta é a ideia. Mas não é aconselhável que o operador, ou usuário delete os objetos presentes na janela de lista de objetos, sem realmente saber do que se trata o objeto, pois se você deletar qualquer outro objeto, que não seja a TradeLine, o EA pode não vim a sentir falta do indicador, deixando assim você sem meios de acessar o indicador, já que ele simplesmente não tem outra forma de ser acessado sem ser pelos botões presentes nele.

O cenário acima seria uma verdadeiro pesadelo se não fosse a rotina que vem logo a seguir, e que é a responsável por manter todo o fluxo de mensagens para dentro da classe, no entanto ela ainda não é o unico ponto de entrada, sim estou falando da rotina DispatchMessage, vamos então dar uma olhada nela.

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_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.pr = price;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
                                        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
                                        m_InfoSelection.bIsMovingSelect = bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        RemoveIndicator(def_IndicatorTicket0);
                                        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)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                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, it, ev))
                        {
                                if (GetInfosTradeServer(ticket) == 0) break;
                                CreateIndicatorTrade(ticket, it);
                                if ((it == IT_PENDING) || (it == IT_RESULT))
                                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                                ChartRedraw();
				m_TradeLine.SpotLight();
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, 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.ticket = ticket;
							m_InfoSelection.it = it;
                                                        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(macroMountName(ticket, it, EV_LINE, false));
                                        }
                                        break;
                        }
                        break;
        }
}

esta rotina passou por tantas mudanças que de fato terei que desmembrar ela em partes menores para explicar o que esta acontecendo dentro dela, pois se você já tem experiencia em programação, não terá dificuldades em entender o que ela esta fazendo, mas se for um entusiasta, ou um aspirante a programação usando a linguagem MQL5, pode ser um pouco confuso entender no começo esta rotina, então vou explicar ela com calma n próximo tópico.


3.0.3 - Destrinchando a rotina DispatchMessage

Este tópico será a explicação do que esta acontecendo na rotina DispatchMessage, caso você já tenha ideia de como ela funciona apenas olhando o código, este tópico nada irá lhe acrescentar.

A primeira coisa que temos depois das variáveis locais, são as variáveis estáticas

static bool bMounting = false, bIsDT = false;
static double valueTp = 0, valueSl = 0, memLocal = 0;

Estas poderiam ser simplesmente declaradas como variáveis privativas dentro da classe, mas já que elas irão ser usadas apenas neste ponto do código, não faz sentido, outras rotinas da classe verem tais variáveis, mas por conta disto temos que declarar elas como estáticas, pois elas devem se lembrar dos valores que tinham quando a rotina for chamada novamente, se não adicionarmos a palavra reservada static na declaração, estas teriam os valores perdidos assim que a rotina terminasse.

Feito isto iremos começar a tratar dos eventos informados pelo MetaTrader 5 ao EA. O primeiro evento que iremos tratar começa a ser visto 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

o que fazemos aqui é capturar e isolar os dados do mouse e de algumas teclas ( presentes no teclado ) ligadas ao mouse. Feito isto entramos em uma conjunto longo de código que se inicia com um teste

if (bKeyBuy != bKeySell)

caso você esteja pressionando a tecla SHIFT ou CTRL, mas não ambas ao mesmo tempo, este teste irá fazer com que o EA imagine que você deseja pendurar uma ordem em um determinado preço. Se este for o caso vamos fazer um novo teste

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_InfoSelection.it = IT_PENDING;
        m_InfoSelection.pr = price;
}

caso ainda não tenha sido montado o indicador 0, este teste irá passar, e ao fazer isto ele irá ocultar o mouse, depois irá capturar os valores presentes no Chart Trade, fazer a conversão destes valores para pontos baseado no nível de alavancagem que o operador estará informando no Chart Trade, e irá indicar um valor inicial onde a ordem será colocada. Esta sequencia só acontecerá uma única vez, a cada ciclo de uso.

A próxima coisa é criar os níveis de take e stop, e indicar se estaremos comprando ou vendendo.

m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
m_InfoSelection.bIsBuy = bKeyBuy;

Estes são criados fora do circulo, pois ao movermos o mouse para outra faixa de preço teremos que mover também o take e o stop. Mas por que este código acima não esta dentro do teste de montagem ?!?! O motivo é que se você trocar soltar a tecla SHIFT e pressionar a tecla CTRL, ou vice versa, sem mover o mouse, isto com os indicadores na tela, os valores dos indicadores de take e stop irão ficar trocados, para evitar isto este fragmento tem que ficar fora do teste. Mas isto nos obriga a fazer um novo teste de montagem, que é visto abaixo:

if (!bMounting)
{
        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
        m_InfoSelection.bIsMovingSelect = bMounting = true;
}

Mas por que dois testes ?!?! Não poderíamos fazer a coisa somente com um único teste ?!?! Bem, isto seria o ideal, mas a rotina em destaque no código acima não nos permite fazer assim, olhe IndicatorAdd para entender este fato. Uma vez feito a criação do indicador 0, tornamos ele como selecionado, e mostramos que ele já esta iniciado e montado. Então poderemos mover ele usando a seguinte linha

MoveSelection(price);

mas ainda dentro deste mesmo critério de SHIFT ou CTRL estar pressionada para colocação de uma ordem pendente temos um ultimo estágio

if ((bEClick) && (memLocal == 0))
{
        RemoveIndicator(def_IndicatorTicket0);
        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
}

Este irá fazer com que uma ordem pendente seja adicionada no ponto exato que estamos apontando, é preciso que duas conduções sejam respeitadas, a primeira é que o botão esquerdo seja pressionado, e a segunda é que não tenhamos feito isto no mesmo preço ao mesmo tempo, ou seja, para colocar duas ou mais ordens no mesmo preço, você deverá estar posicionado esta nova ordem com uma outra chamada, na mesma chamada isto não irá acontecer. 

Ao mesmo tempo que o indicador 0 é removido do gráfico, uma ordem é enviada ao servidor de negociação, com todos os parâmetros devidamente preenchidos.

Agora vamos para uma próxima etapa ...

if (bKeyBuy != bKeySell)
{

// ... código descrito até o momento ....

}else if (bMounting)
{
        RemoveIndicator(def_IndicatorTicket0);
        Mouse.Show();
        memLocal = 0;
        bMounting = false;
}

Agora caso o indicador 0 tenha sido montado, mas a condição de apenas SHIFT ou CTRL estarem pressionadas não for atendida, o código em destaque será executado, retirando o indicador 0 da lista de objetos, ao mesmo tempo que repoe o mouse e faz com que as variáveis estáticas fiquem em seu estado incial. Ou seja o sistema estará limpo.

A próxima e ultima etapa dentro do evento mouse da rotina é mostrado abaixo:

if (bKeyBuy != bKeySell)
{

// ... código já descrito ....

}else if (bMounting)
{

// ... código já descrito ...

}else if ((!bMounting) && (bKeyBuy == bKeySell))
{
        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
}

O código em destaque é a ultima etapa do trabalho com o mouse dentro do tratamento de mensagens. Aqui caso não estejamos com o indicador 0 montado e nem as teclas SHIFT ou CTRL em status diferentes, ou seja elas podem estar ao mesmo tempo pressionadas ou liberadas,  temos o seguinte comportamento, caso ocorra um clique com o botão esquerdo do mouse o preço será enviado ao indicador, caso apenas tenhamos o movimento do mouse o preço será usado para mover o indicador. Mas ai você se pergunta: Qual indicador ?!?! Calma nos veremos qual o indicador em breve, mas caso esteja curioso o indicador 0 não usa este fragmento destacado. Se não entendeu volte ao inicio deste tópico e releia como este tratamento de mensagens funciona.

A próxima mensagem que iremos tratar é vista abaixo:

case CHARTEVENT_OBJECT_DELETE:
        if (GetIndicatorInfos(sparam, ticket, it, ev))
        {
                if (GetInfosTradeServer(ticket) == 0) break;
                CreateIndicatorTrade(ticket, it);
                if ((it == IT_PENDING) || (it == IT_RESULT))
                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                ChartRedraw();
		m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
        }
        break;

Você se lembra, lá atras neste artigo, quando falei que o EA tinha um pequeno sistema de segurança para evitar que os indicadores fossem apagados indevidamente ?!?! Pois bem este sistema esta bem acima, no código de tratamento das mensagens de eventos enviados pelo MetaTrader 5 quando um objeto é deletado.

Quando isto acontece, o MetaTrader 5 informa pelo parâmetro sparam qual o nome do objeto deletado, assim testamos para verificar se foi um indicador, e se foi, qual deles. Notem que não importa qual foi o objeto, o que queremos saber é qual foi o indicador afetado, depois disto iremos verificar se existe alguma ordem ou posição relacionada ao indicador, em caso positivo, iremos recriar todo o indicador, em um caso extremo de o indicador afetado tenha sido o indicador base, reposicionamos ele agora imediatamente, e forçamos o MetaTrader 5 a coloca o indicador no gráfico de forma imediata, independente de qual seja o indicador, retiramos a indicação de seleção de qualquer indicador selecionado, e fazemos um pedido de update nos dados de limite do indicador. 

O próximo evento a ser tratado é extremamente simples, ele simplemente faz um pedido para redimensionar todos os indicadores na tela, seu código é visto logo abaixo

case CHARTEVENT_CHART_CHANGE:
        ReDrawAllsIndicator();
        break;

O próximo evento a ser tratado é o evento de clicar em um objeto

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
//....
        }
        break;

este se inicia da forma como é vista logo acima, onde o MetaTrader 5 nos informa qual o objeto que recebeu o clique, assim o EA pode testar e verificar qual o tipo de evento que deverá ser manipulado, até o momento temos 2 eventos CLOSE e MOVE, vamos primeiro ver o evento CLOSE, que irá fechar e determinar o fim de um indicador na tela.

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.ticket = ticket;
			m_InfoSelection.it = it;
                        m_InfoSelection.bIsMovingSelect = true;
                        SetPriceSelection(0);
                        break;
        }
        break;

O evento close, irá fazer o seguinte: Ele irá usar o ticket para procurar junto ao servidor o que deve ser encerrado, e se de fato temos alguma coisa a ser encerrada, pois pode acontecer de no momento em que você vai encerrar algo o servidor já tenha feito isto mas o EA ainda não foi informado disto. Uma vez que temos algo que deverá ser encerrado vamos fazer isto da forma correta, desta forma temos alguns testes e um modo de correto de informar a classe para encerrar ou retirar o indicador do gráfico.

Desta forma chegamos a ultima etapa deste tópico, que é visto abaixo

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(macroMountName(ticket, it, EV_LINE, false));
        }
        break;

O evento MOVE é que irá de fato fazer, a seleção do indicador a mover conforme os movimentos do mouse, a indicação é feita aqui, mas o movimento é executado durante um evento de movimento do mouse. Lembra que lá no inicio do tópico, falei que existia uma condição onde não estaríamos tratando do indicador 0 e que mesmo assim algo iria se mover, pois bem, este algo é indicado neste ponto, no evento move, aqui iremos testar se existe algo selecionado para mover, caso positivo, o indicador que estava selecionado irá deixar de estar e não receberá os eventos de movimento do mouse, e o novo indicador será selecionado. Neste caso os dados do novo indicador a receber os dados do mouse serão armazenados em uma estrutura e este indicador irá receber uma mudança que indicará que ele estará selecionado, esta mudança é vista na espessura da linha, dando assim algum destaque para ele.


3.0.4 - Uma nova classe objeto Mouse

Além desta melhorias vistas acima também temos outras que merecem algum destaque.

Apesar de grande parte dos operadores não sentir necessidade de ter o sistema de indicação que esta implementado no EA, e que conta com o auxilio do mouse para ser usado, outros podem precisar e desejar ter este sistema funcionando perfeitamente, mas por algum deslise pode acontecer de em algum momento o operador acabar deletando algum dos objetos que fazem parte do indicador do mouse, fazendo com que ele funcione de forma precária ou deixe de funcionar totalmente, bem felizmente temos como evitar isto usando o sistema de EVENTOS e assim que um evento de retirada de objeto fosse detectado e enviado para o EA, a classe na qual o objeto pertence poderia recriar o objeto deixando o sistema estável, mas é bom deixar o mínimo possível de objetos listados, e criar os mesmo conforme eles vão sendo necessários e retirando eles em seguida, quando ele não serão mais necessários, foi isto que fizemos até agora, mas faltava a classe Mouse para que todo o sistema ficasse o mais leve possível.

Começaremos criando algumas definições, isto substitui o sistema de criar nomes constantes vai alguma variável.

#define def_MousePrefixName "MOUSE "
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TMPV"
#define def_NameObjectLineT def_MousePrefixName + "TMPT"
#define def_NameObjectBitMp def_MousePrefixName + "TMPB"
#define def_NameObjectText  def_MousePrefixName + "TMPI"

Feito isto a nova rotina de inicialização fica da seguinte forma:

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        Show();
}

Note que ela é muito mais simples que a versão anterior, mas não ficará devendo em nada, neste ponto temos a chamada que irá mostrar o sistema do mouse, isto é feito no ponto em destaque no código acima, e este irá chamar o código que irá de fato criar o sistema de indicação no eixo do preço.

inline void Show(void)
{
        if (ObjectGetDouble(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_PRICE) == 0)
        {
                ObjectCreate(Terminal.Get_ID(), def_NameObjectLineH, OBJ_HLINE, 0, 0, 0);
                ObjectSetString(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_TOOLTIP, "\n");
                ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_BACK, false);
        }
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, m_Infos.cor01);
}

Este código é bem curioso, pois o que estamos fazendo é testando se existe ou não o objeto indicador do mouse no preço. Se o teste for bem sucedido significa que a linha ou qualquer coisa envolvida com o mouse existe no gráfico, desta forma tudo que fazemos é ajustar a cor da da linha horizontal. Mas por que estamos fazendo este teste ?!?! Para entender é preciso olhar a rotina responsável por ocultar, ou melhor dizendo remover os objetos ligados ao mouse. Veja a rotina abaixo:

inline void Hide(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(Terminal.Get_ID(), def_MousePrefixName + "T");
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
}

Aqui temos um estilo de funcionamento extremamente curioso. todos os objetos ligados ao mouse e que tem um nome prefixado será removido do gráfico pelo MetaTrader 5, desta forma a lista de objetos será sempre bem pequena, mas a linha horizontal não é removida, ela apenas tem sua cor modificada, por conta disto que a rotina que mostra o mouse faz um teste antes de criar o objeto, pois ele na verdade não é excluído da lista de objetos, ele apenas é ocultado, mas todos os demais objetos são deletados da lista de objetos. Mas então como vamos utilizar estes outros objetos que são usados nos estudos ?!?! Já que os estudos são momentos de curta duração onde você simplesmente deseja saber de alguns detalhes, não faz sentido manter objetos na lista apenas para serem utilizados 1 ou 2 vezes, é melhor criar eles, fazer o estudo e depois remover eles da lista, desta forma teremos um sistema mais robusto.

Pode parecer bobagem fazer isto, mas o sistema de ordens que estou mostrando como criar, é baseado no uso de objetos, e quanto mais objetos tivermos na lista maior será o trabalho do MetaTrader 5 para pesquisar a lista quando formos acessar um objeto especifico, desta forma não vamos deixar objetos desnecessários no gráfico ou na lista de objetos, vamos manter o sistema o mais leve quanto for possível.

Desta forma temos nossa atenção voltada para a rotina DispatchMessage, ela começa como mostrado abaixo:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;

logo em seguida temos o código que irá começar a tratar o primeiro evento

switch (id)
{
        case CHARTEVENT_MOUSE_MOVE:
                Position.X = (int)lparam;
                Position.Y = (int)dparam;
                ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                ObjectMove(Terminal.Get_ID(), def_NameObjectLineH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                if (b1 > 0) ObjectMove(Terminal.Get_ID(), def_NameObjectLineV, 0, Position.dt, 0);
                key = (uint) sparam;
                if ((key & 0x10) == 0x10)    //Botão do meio....
                {
                        CreateObjectsIntern();
                        b1 = 1;
                }

quando pressionamos o botão do meio do mouse, geramos uma chamada, mas isto não vem ao caso agora, depois iremos ver o que a rotina chamada faz, mas observe o código em destaque, notem que tentamos mover um objeto que não existe, já que ele não consta na lista de objetos mantidas pelo MetaTrader 5. mas esta chamada somente irá ocorrer quando o botão do meio for pressionado, observem a variável b1, ela controla em que ponto o operador esta dentro do conjunto envolvido na geração do estudo.

Uma vez que o operador pressione o botão esquerdo do mouse, e a primeira etapa esteja comprida, iremos ter o próximo fragmento sendo executado:

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

Este irá posicionar uma linha de tendência e chamar a próxima etapa com a mudança do valor da variável b1. Neste ponto iremos para o próximo fragmento

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), def_NameObjectBitMp, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), def_NameObjectText, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), def_NameObjectText, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Este fragmento acima é que de fato irá mostrar os estudo na tela, todos estes objetos que estão neste fragmento não existirão quando o estudo terminar, eles serão criados e destruídos dentro desta rotina, apesar de parecer ser pouco eficiente fazer isto, não notei qualquer queda ou aumento de tempo de processamento durante a fase de estudos, na verdade notei uma leve melhoria no sistema de ordens, algo bastante sutil, que praticamente fica dentro da margem de erro de um benchmarking então não posso falar que estas mudanças de fato trouxeram melhorias no ponto de vista do processamento.

Mas notem que o estudo irá acontecer enquanto o mouse estiver com o botão esquerdo pressionado, uma vez que ele venha a ser liberado, iremos executar o próximo fragmento

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}
Position.ButtonsStatus = (b1 == 0 ? key : 0);

neste iremos retirar da lista de objetos todos objetos usados para se criar o estudo, e voltar a mostrar a linha do mouse na tela, a grande sacada é o código em destaque, ele evita que qualquer função ou rotina dentro do EA venha a ter uma falsa indicação quando capturamos os botões do mouse, caso algum estudo esteja sendo feito, o status dos botões deverá ser ignorado pelo EA, e para fazer isto usamos esta linha em destaca, não é uma solução perfeita, mas é melhor do que nada.

Bem mas ficou faltando mostrar o código que criar os objetos para executar os estudos, mas já que é uma rotina bastante simples não vou dar destaque a ela aqui no artigo.


4.0 - Conclusão

Apesar de parecer bobagem todas as mudanças feitas aqui, de fato fazem muita diferença no sistema em si, lembre-se de uma coisa: Nosso sistema de ordens se baseia em objetos gráficos na tela, então quanto mais objetos o EA tiver que lidar menor será seu desempenho durante alguma pesquisa que terá que ser feita a procura de algum objeto especifico, e para deixar a coisa ainda mais complexa, estamos tratando de um sistema que executa em tempo real, ou seja, quanto mais rápido for o sistema do nosso EA melhor será seu desempenho. Então quanto menos coisas o EA de fato tem que fazer melhor, o ideal seria se ele pode-se tratar apenas e somente do sistema de ordens, e todo o resto fosse posto em um outro patamar e o MetaTrader 5 ficasse responsável por eles. Mas nos vamos fazer isto, claro que aos poucos, já que teremos que fazer várias pequenas mudanças, mas nada de muito complicado, e isto será feito no decorrer dos próximos artigos, muito provavelmente não será em um voltado exclusivamente a dar mais robustez ao EA, mas uma coisa é verdade, no futuro o EA irá ficar responsável apenas pelo sistema de ordens.

No próximo artigo iremos deixar o EA de uma forma bastante interessante, iremos reduzir ainda mais o numero de objetos presentes na lista durante o funcionamento do EA, já que o grande gerador de objetos é o sistema de ordens, irei mostrar como mudar este sistema de forma a reduzir ao máximo a carga que ele gera ao MetaTrader 5.

Por conta disto neste artigo não irei deixar nenhuma parte ou modificação anexada, já que o próprio código ainda irá passar por mudanças, mas não se preocupem, irá valer muito a pena esperar o próximo artigo. As mudanças irão elevar bastante o desempenho geral do nosso EA ... então nos vemos no próximo artigo desta serie .


Gráficos na biblioteca DoEasy (Parte 98): Movendo pontos de ancoragem de objetos gráficos padrão estendidos Gráficos na biblioteca DoEasy (Parte 98): Movendo pontos de ancoragem de objetos gráficos padrão estendidos
Neste artigo, continuaremos a desenvolver objetos gráficos padrão estendidos e criaremos uma funcionalidade que move os pontos de ancoragem de objetos gráficos compostos por meio de pontos de controle usados para gerir as coordenadas dos pontos de ancoragem do objeto gráfico em questão.
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).
Gráficos na biblioteca DoEasy (Parte 99): Movendo um objeto gráfico estendido com um ponto de controle Gráficos na biblioteca DoEasy (Parte 99): Movendo um objeto gráfico estendido com um ponto de controle
No último artigo, geramos o movimento dos pontos de ancoragem de um objeto gráfico estendido por meio de formas de controle. Agora vamos mover o objeto gráfico composto com ajuda de um ponto/forma de controle de objeto gráfico.
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.