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

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

MetaTrader 5Exemplos | 27 julho 2022, 11:04
771 0
Daniel Jose
Daniel Jose

1.0 - Introdução

No artigo anterior Dando robustez ao sistema (I), eu mostrei como modificar algumas partes do EA de forma a ter um sistema ligeiramente mais robusto e confiável.

Bem, aquilo foi apenas a introdução do que iremos ver aqui, esqueçam tudo o que sabem, planejam ou desejam, absolutamente tudo. A parte mais complicada de tudo é saber separar as coisas, deste o inicio desta sequência o EA tem crescido de forma quase constante, tendo alguma coisas adicionadas, modificadas e mesmo retiradas, mas agora iremos ao extremo do que foi feito até o momento.

A grande questão é que diferente do que possa parecer, um EA bem projetado, não e repito, não irá conter nenhum tipo de indicador nele, não irá fazer nada além de observar e assegurar que as posições indicadas das ordens seja respeitadas. O EA perfeito basicamente é apenas um assistente que garante, e observa isto, o que o preço esta fazendo ?!?! Ele não olha indicadores, ele simplesmente olha as posições ou ordens que estão no seu gráfico.

Você pode pensar que estou falando bobagens e que não sei do que estou falando. Bem deixe-me lhe perguntar uma coisa: Você já parou para pensar no por que do MetaTrader 5 ter classes diferentes para coisas diferentes ?!?! Por que a plataforma tem indicadores, serviços, scripts e EAs, por que ela simplesmente não deixou tudo em um único bloco ?!?! Fosse ele qual fosse ?!?!

É justamente esta a questão. Se as coisas estão separadas é por que elas precisam ser trabalhadas de forma separada.

Indicadores são usados para um proposito geral, seja ele qual for, mas é bom que o projeto de indicadores seja bem elaborado para não comprometer todo o desempenho, não da plataforma MetaTrader 5, mas de outros indicadores, já que eles trabalham em um thread diferente dos demais código, assim eles podem executar tarefas em paralelo de uma forma bastante eficiente.

Os serviços servem para nos ajudar em várias questões, aqui mesmo nesta serie usamos serviços nos artigos Acessando a WEB (II) e Acessando a WEB (III) para nos prover dados de uma maneira bastante interessante, é verdade que poderíamos fazer aquilo diretamente dentro do EA, mas não é a maneira mais adequada como eu expliquei nos artigos.

Os scripts nos ajuda de maneira bem peculiar, já que eles somente irão existir durante um dado período de tempo, pois eles podem fazer algo bastante especifico e logo depois sair do gráfico, ou podem ficar ali até que mudemos alguma configuração do gráfico, por exemplo o período do gráfico, isto limita um pouco as coisas, mas paciência, faz parte e temos que aprender a conviver com este tipo de coisa.

Já os EAs são específicos para se trabalhar com o sistema de negociação. Apesar de podermos adicionar funcionalidades e códigos que não fazem parte do sistema de negociação dentro dos EAs, isto não é muito adequado em sistema de alta performance ou com um nível muito grande de robustez, e o motivo é que qualquer coisa que não faça parte do sistema de negociação não deveria ser colocado dentro de um EA, deve-se colocar as coisas em locais adequados, e trabalhar com elas de forma adequada.

Por este motivo, a primeira coisa que iremos fazer para dar robustez ao nosso EA será retirar tudo e absolutamente tudo que não faça parte do sistema de negociação de entro do código, e tornar estas coisas indicadores, ou algo do tipo, a única coisa que irá ficar no código do EA será as partes responsáveis por gerenciar, analisar e processar as ordens ou posições, todo o resto será retirado.

Então vamos começar.


2.0 - Implementação

2.0.1 - Retirando o Wallpaper do EA

Apesar de isto não trazer nenhum tipo de maleficio ou problemas para o EA, pode ser que alguns queiram ter a tela limpa, tendo apenas algumas coisas nela, então vamos retirar este sistema de dentro do EA, e transforma-lo em um indicador, a forma de se fazer isto é super simples, não iremos mexer em nada nas classes, apenas criar o código abaixo:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //BitMap a ser usado
input char                      user11 = 60;                    //Transparencia (0 a 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Tipo de imagem de fundo
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

Vejam que foi algo super natural e direto, sem nenhum tipo de dor, ou ressentimento, o código foi retirado do EA e transformado em um indicador que poderá ou não ser adicionado no gráfico, e qualquer mudança, seja no papel de parede, seja no nível de transparência, ou até a retirada dele do gráfico não irá ter nenhum efeito sobre o que o EA estará fazendo.

Bem agora vamos começar a retirar as coisas de que fato promovem alguma queda no desempenho do EA, já que elas trabalham de tempos em tempos, ou a cada movimento dos preços, e por conta disto em alguns momentos pode forçar o EA a ter um tempo que seria vital para ele reduzido impedindo ele de fazer o seu verdadeiro papel que é observar o que esta acontecendo com as ordens ou posições no gráfico.


2.0.2 - Tornando o Volume At Price um indicador

Apesar de não parecer, este sistema do Volume At Price toma um tempo que em várias ocasiões é primordial para o EA, e estou falando de momentos de grande volatilidade, onde os preços oscilam loucamente sem muita direção. é justamente nestes momentos que o EA precisa de cada ciclo de máquina disponível para efetuar sua tarefa, e você não vai querer perder uma boa oportunidade por conta que algum indicador resolveu tomar conta do pedaço, então vamos retirar ele do EA e torná-lo um indicador verdadeiro, pra isto teremos que criar o código abaixo:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Cor das barras
input   char            user1   = 20;                                   //Transparencia (0 a 100 )
input color     user2 = clrForestGreen; //Agressão Compra
input color     user3 = clrFireBrick;   //Agressão Venda
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Estes foram a parte fácil, tudo que foi preciso fazer foi retirar o código do EA e colocar ele em um indicador, e caso você deseje retornar o código para dentro do EA basta copiar o código do indicador e colocar ele de volta no EA.

Vejam que começamos com algo simples, complicamos um pouco a coisa, mas agora a coisa complica mesmo vamos retirar o Times And Trade de dentro do EA.


2.0.3 - Transformando o Times & Trade em um indicador

Isto realmente será bastante complicado de fazer, se o objetivo é ter um código que tanto pode ser usado no EA quando usar o mesmo código dentro de um indicador. Isto por que este indicador funciona em uma sub janela, pode parecer que a conversão dele em indicador seria simples, mas não será isto justamente por ele trabalhar em uma sub janela, o principal problema é que se você simplesmente fizer como foi feito nos casos anteriores terá na janela de indicadores o seguinte resultado:

Este tipo de coisa não é adequado se ter na janela de indicadores, já que irá confundir o operador, caso ele deseje retirar o indicador da tela. Por conta disto temos que seguir um outro caminho. E no final deste caminho que para alguns irá parecer tortuoso, mas na verdade é apenas um simples conjunto de diretivas e um pouquinho de edição, teremos o seguinte resultado na janela de indicadores

este sim é o que um operador espera encontra, e não aquela bagunça da imagem anterior.

Mas vamos ver como é o código do indicador de Times &  Trade, ele pode ser visto logo abaixo na integra:

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Escala
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Ai você pensa, mas o código é exatamente igual ao código que estava sendo usado no EA, a não ser o fato de termos uma linha que esta em destaque, que não faz parte do código do EA, então onde esta a pegadinha ?!?! Ou não tem pegadinha ?!?! Na verdade tem um pegadinha, o código não é exatamente igual ele tem uma diferença, e a diferença não esta no código nem do indicador nem do EA, mas sim no código da classe, mas antes de ver a diferença, vamos pensar no seguinte: Como você como dizer para o compilador o que deve ser compilado ou não ?!?! Ou quando você programa algo, não se preocupa com isto, vai simplesmente criando o código e quando algo não esta do seu agrado você simplesmente deleta as linhas ?!?!

Pois bem, programadores mais experientes tem uma regra : Só se apaga algo quando aquilo definitivamente não funciona, caso contrário guardamos a os trechos mesmo que eles não sejam de fato compilados. Mas como eu faço isto em um código linear, onde as funções vão sendo escritas e quero sempre que elas funcionem ?!?! Ai entra a primeira questão : Você sabe como falar para o compilador o que deve ou não ser compilado ?!?! Se a resposta for não, tudo bem, eu também quando comecei não sabia como fazer isto, mas é algo que ajuda muito. Então vamos aprender como fazer isto.

Algumas linguagem tem uma coisa chamada diretivas de compilação, estas podem receber o nome de preprocessador, dependendo do autor, mas a ideia é a mesma, dizer ao compilador o que deve ou não ser compilado e como deve ser feita a compilação. Até ai acho que é fácil de entender, mas existe um tipo de diretiva bastante especifica que nos ajuda a isolar o código de forma intencional para que possamos testar coisas especificas, estas são as diretiva de compilação condicional, as mesmas se bem empregadas nos permite fazer algo bastante curioso, compilar um mesmo código de formas diferentes, e é justamente isto que é feito aqui no exemplo do Times & Trade, eu escolho quem será o responsável por gerar a compilação condicional, será o EA ou será o Indicador ?!?! Uma vez definido isto eu crio uma diretiva de definição #define e depois usando a diretiva condicional #ifdef #else #endif informo ao compilador como o código será compilado.

Pode ter ficado complicado entender, mas vamos ver na prática como isto realmente funciona.

No código do EA iremos definir e adicionar as linhas em destaque abaixo:

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

o que esta acontecendo, é o seguinte: Caso eu queira compilar o EA com as classes que estão nos arquivos MQH, eu deixo a diretiva #define def_INTEGRATION_WIT_EA definida no EA, isto irá fazer com que o EA tenha todas as classes que estamos tirando e colocando em indicadores, mas e se eu desejar retirar estes indicadores todos de uma só vez, não preciso sair apagando códigos e linhas, tudo que tenho que fazer é transformar a definição em uma não definição, isto pode ser feito simplesmente tornando a linha que a diretiva é declarada em uma linha de comentário, simples assim, já que o compilador não vai encontrar mais a diretiva, ele será dada como não existente, e já que ele não existe, toda a vez que uma diretiva condicional #ifdef def_INTEGRATION_WITH_EA for encontrada ela será totalmente ignorada e o código entre ela e a parte #endif que pode ser vista no exemplo acima não será compilada.

Esta é a ideia, então fazemos isto na classe do C_TimesAndTrade e vejam como ficou a nova classe, vou mostrar só um ponto para aguçar a curiosidade de vocês:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

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

}

Vejam que aqui temos um código bem esquisito para quem não utiliza diretivas de compilação, a diretiva def_INTEGRATION_WITH_EA esta sendo declarada no EA, não se esqueça disto, o que acontece é o seguinte, quando o compilador for gerar o código objeto deste arquivo, ele irá tomar a seguinte atitude: Caso o arquivo que esta sendo compilado é o EA, e este tem a diretiva declarada, o compilador irá gerar o código objeto com as partes que estão entre as diretivas condicionais #ifdef def_INTEGRATION_WITH_EA e a #else, normalmente usamos a diretiva #else nestes casos, e caso quem esta sendo compilado é outro arquivo, por exemplo um indicador que não tem a diretiva def_INTEGRATION_WITH_EA definida ele irá compilar o que estiver entre as diretivas #else e a #endif, é bem deste jeito que a coisa funciona.

Observe todo o código da classe C_TimesAndTrade para entender cada um destes testes e o funcionamento geral, quando você estiver compilando o EA ou quando estiver compilando o Indicador, desta forma o próprio compilador MQL5 fará os ajustes para nos, nos poupando tempo e esforço de ter que manter 2 arquivos distintos.


2.0.4 - Tornando o EA mais ágil

Uma questão interessante, é o fato de eu ter dito que o EA deverá apenas trabalhar com o sistema de ordens, mas até o momento, o EA estava recebendo coisas que agora se tornaram indicadores, o motivo disto é algo muito pessoal, tem haver com coisas envolvidas no cálculos que o EA deveria fazer, mas este sistema de cálculos foi modificado, sendo passado para um outro método, desta forma pude notar que o sistema de ordens estava sendo prejudicado por algumas coisas que o EA fazia ao invés de cuidar das ordens, e o pior dos pontos estava no evento OnTick:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

Agora este evento recebeu esta diretiva condicional, para caso alguém não esteja de fato operando em momentos de grande volatilidade possa ter o EA, caso deseja com todos os indicadores originais. Mas antes que vocês imaginem que isto seria uma boa ideia, deixe-me lembrar vocês como a rotina Update do Times & Trade funciona.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

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

        }
}

O código acima é um fragmento da rotina Update presente na classe C_TimesAndTrade, a parte destacada é que é o problema, toda a vez que ela é executada, é enviado um pedido para o servidor para que ele retorne todos os tickets de negócios ocorridos a partir de um determinado momento, isto não é tão problemático assim diga-se de passagem, o problema é que de tempos em tempos esta mesma chamada irá coincidir com 2 outros eventos.

O primeiro evento e o mais claro é uma grande quantidade de negócios que podem estar ocorrendo e que faz com que a rotina OnTick receba uma quantidade muito grande de chamadas, e esta rotina irá além de ter que executar o código acima presente na classe C_TimesAndTrade terá que lidar com um outro problema, a própria chamada a função SecureChannelPosition, presente na classe C_IndicadorTradeView, ou seja temos um pequeno problema aqui, mas não é somente isto, eu disse que de tempos em tempos mesmo que a volatilidade esteja baixa, teremos a coincidência de 2 eventos, o primeiro foi este.

O segundo evento esta no tratamento dado a eventos do relógio, ou seja no evento OnTime, mas agora este foi atualizado para o formato abaixo:

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

Mas se você for usar o EA como ele estava projetado, e mais o agravante de ele ter recebido ainda mais código, pode ter problemas em alguns momentos, isto por conta desta coincidência, e quando ela acontece, o EA irá ficar, mesmo que por 1 único segundo fazendo coisas que não estão relacionadas ao sistema de ordens.

Diferente da rotina que se encontra no C_TimesAndTrade, esta presente na classe C_VolumeAtPrice, pode fazer um verdadeiro estrago na performance do EA no que diz respeito a cuidar das ordens. Vamos analisar o por que disto:

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

O motivo são os códigos em destaque, mas o pior deles é o REDRAW, este realmente faz um grande estrago na performance do EA, e o motivo disto, é que a cada tick de volume recebido, ACIMA de um dado valor, todo o volume at price é retirado da tela, recalculado e reposicionado novamente, mesmo que isto aconteça a cada 1 segundo, aproximadamente, pode ser que venha a coincidir com outras coisas, por isto que o EA teve todos os indicadores retirados, mesmo que eu tenha deixado eles de forma que você possa usá-los diretamente no EA, eu desaconselho você a fazer isto, pelos motivos que expliquei até aqui.

Estas mudanças eram necessárias, mas tem uma outra que por incrível que pareça é mais emblemática e que de fato precisa ser feita, e a bola da vez é o evento OnTradeTransaction, o fato de eu estar usando este evento em si, é uma tentativa de deixar o sistema o mais ágil quanto for possível, muitos que programam EA de execução de ordens usam o evento OnTrade e ali fazem testes de quais ordens estão ou não no servidor, ou quais posições ainda estão abertas, não digo que eles estão errados em fazer isto, apenas é pouco eficiente, já que o servidor nos informa o que esta acontecendo, mas o grande problema de usar o evento OnTrade é o fato de que temos que ficar testando coisas desnecessariamente, isto por que se utilizarmos o evento OnTradeTransaction iremos ter um sistema pelo menos mais eficiente em termos de analise de movimentos, mas isto não é o objetivo aqui, cada um utiliza o método que mais se encaixa com os seus critérios.

Mas o fato de no desenvolvimento deste EA, eu ter optado em não usar nenhuma estrutura para armazenar, e assim não limitar o numero de ordens ou posições que se poderia ser trabalhada, complica a coisa de forma que uma alternativa se torna necessária ao evento OnTrade, esta é conseguida utilizando o evento OnTradeTransaction.

Este evento OnTradeTransaction é bem complicado de ser implementado, talvez dai muitos não o utilizem, mas eu não tive escolha, ou ele funciona, ou ele funciona, caso contrário o bicho iria pegar, mas na versão anterior o código deste evento era muito pouco eficiente, e ele pode ser visto abaixo:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

apesar do código acima funcionar, ele é PESSIMO, isto para dizer o mínimo, a quantidade de chamadas inúteis que este código acima gera, chega a beirar a insanidade, nada adiantaria melhorar o EA em termos de estabilidade e robustez, se esta coisa acima não pode-se ser corrigida.

Por conta disto fiz várias atividades em conta DEMO de forma a tentar encontrar um padrão nas mensagens, e é bastante complicado achar um padrão, mas encontrei não um padrão, mas algo que evitasse a insanidade de chamadas inúteis que era gerada, deixando assim o código estável, robusto e ao mesmo tempo ágil o bastante para que se pode-se operar qualquer momento de mercado, é verdade que ainda existem alguns por menores que precisam de correção, mas o código ficou muito bom, e ele pode ser visto abaixo:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

Não tentem entender no primeiro momento o que esta acontecendo, apenas admirem a beleza desta rotina acima, ela é quase a perfeição em pessoa. E irei explicar o por que digo isto, não pelo fato de eu ter feito esta codificação, mas pelo auto grau de robustez e agilidade que ele tem.

Apesar de parecer complicado este código conta com dois testes, e eles estão em destaque logo abaixo para que eu explique melhor o que esta acontecendo.

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... código interno ...

}

A linha destacada em VERDE irá sempre que uma transação de histórico acontecer, verificar se o ativo informado é o mesmo observado pelo EA, caso isto seja verdadeiro, será enviada uma ordem para a classe C_IndicatorTradeView para remover o indicador do gráfico, mas esta transação pode acontecer em 2 momentos, quando uma ordem se torna posição, ou quando uma posição é fechada, lembrando que somente uso o modo NETTING e não o HEDGING. Então independente do que aconteça o indicador será retirado do gráfico.

Mas você pode se perguntar: No caso da posição esta sendo fechada tudo bem, mas e no caso da ordem se tornar posição, irei ficar desamparado ?!?! Não, mas esta questão não é resolvida aqui, dentro do EA, esta questão é resolvida dentro da classe C_IndicatorTradeView e será melhor explicada no próximo tópico deste artigo.

Já a linha vermelha, reduz absurdamente a quantidade de mensagens inúteis que era repassadas para dentro da classe C_IndicatorTradeView, isto é feito verificando qual foi a resposta que o servidor nos devolveu ao nosso requerimento, então é preciso ter uma confirmação positivando o nosso requerimento junto com o mesmo nome do ativo que o EA esta observando somente assim uma nova rodada de chamadas será enviada para a classe C_IndicatorTradeView.

Bem isto é tudo que se pode dizer sobre este sistema. Mas a historia ainda não terminou ... temos um grande trabalho pela frente ainda, e de agora em diante iremos focar apenas e somente na classe C_IndicatorTradeView, e vamos começar agora, com algumas mudanças que precisam ser feitas.


2.0.5 - Reduzindo o numero de objetos criados pela classe C_IndicatorTradeView

No artigo Desenvolvendo um EA de negociação do zero ( Parte 23 ) eu introduzi um conceito bastante abstrato, mas no entanto bastante interessante para movimentos de ordens ou limites, o conceito era o uso de fantasmas, ou sombra de posições. Esta iriam identificar e mostrar no gráfico o que o servidor de negociação estava vendo e iria utilizar até que o movimento real ocorre-se de fato. Mas apesar de tudo aquele modelo tem um pequeno problema: Ele adiciona objetos para que o MetaTrader 5 tome conta, e os objetos adicionados não são de fato necessários na grande maioria das vezes, então o MetaTrader 5 ficava com a lista de objetos sendo preenchida com coisas muitas das vezes inúteis ou pouco usadas.

Novamente, não queremos que o EA fique criando objetos, ou que tenha objetos desnecessários na lista de objetos, já que isto degrada a performance do EA durante a gestão das ordens, já que utilizamos o MetaTrader 5 para fazer esta gestão, não devemos ter objetos inúteis apenas atrapalhando todo o sistema.

Mas existe um solução bastante simples, na verdade ela não é tão simples, mas iremos fazer NOVAMENTE, mudanças na classe C_IndicatorTradeView de forma a melhorar isto. Vamos manter os fantasmas, ou sombras na tela e usaremos um método bastante curioso, e muito pouco usado até onde pude notar durante a fase de pesquisa.

Então prepare-se pois a coisa vai ser muito divertida e interessante de ser implementada.

A primeira coisa que faremos é mudar a estrutura de seleção, agora ela terá a seguinte modelagem:

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

Não vou dizer o que mudou, vou deixar você tentarem entender, mas a mudança trouxe alguns benefícios em termos de simplificar alguns pontos sobre a lógica da codificação.

Por conta disto agora nosso indicador fantasma terá um index só dele:

#define def_IndicatorGhost      2

E isto fez com que a modelagem dos nomes também muda-se para o seguinte código:

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

Parece pouco, mas isto irá fazer muita diferença logo logo ... vamos continuar ..

Agora as macros de posição do preço são sempre diretas, não tem mais aquela coisa de duplicidade, então o código delas ficou como mostrado abaixo:

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

Estas mudanças nos forçam a criar 2 outras rotinas, vou mostrar uma agora depois irei mostrar a outra, a primeira é a substituta da rotina que cria os indicadores em si, ela ficou literalmente mais clara quanto ao que realmente diferencia um indicador de outro, e pode ser vista logo abaixo:

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Vocês já devem ter notado que eu adoro usar diretivas de pre processamento nos meus códigos, isto é quase um vício da minha parte, não consigo ficar sem utilizar isto, mas vejam que agora é bastante simples entender qual é a diferença entre um indicador e outro, não é algo que você precise ficar ali se matando para entender como deixar um indicador com as cores que você deseja, basta mudar elas aqui, e já que todos são quase um clone uns dos outros, usando uma macro eu consigo fazer a coisa de forma que todos irá funcionar da mesma forma, e terão com toda a certeza os mesmo elementos. Isto si é reutilização de código levado ao extremo da coisa.

Existe uma outra rotina com um nome muito parecido com esta daqui, mas que faz uma outra coisa, mas para o final irei entrar em detalhes sobre ela.

A rotina IndicadorAdd foi modificada, sendo os trechos cortados retirados dela ...

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);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

Sendo um dos trechos cortados substituído pelo trecho em destaque. Mas espera um pouco, você não esta mais criando os indicadores de ordem pendente ou o indicador 0 ?!?! Calma, eles ainda estão sendo criados, mas não nesta rotina, e sim em outro local, e o motivo é uma outra rotina que teve que surgir, vamos com calma ...

A próxima rotina é justamente a que irá criar os indicadores de ordem pendente e o indicador 0, estamos falando da rotina UpdateIndicators, que tem seu código visto logo abaixo:

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

Vejam que nesta rotina temos um teste bastante curioso, que esta em destaque no código, este teste irá ajudar a criar os indicadores fantasmas, por isto que a rotina IndicadorAdd não é mais capaz de criar os indicadores de ordem pendente e o indicador 0, mas somente fazer este teste não é o suficiente para criar um indicador fantasma, temos mais coisas envolvidas.

Agora tem alguns detalhes envolvidos na rotina DispatchMessage, são pequenas mudanças, mas que facilitam muito a nossa vida, irei mostrar apenas as partes onde estas mudanças de fato ocorreram, então observem o código abaixo:

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

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

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Código ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Código ...

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

// ... Código ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

Observem que no CHARTEVENT_MOUSE_MOVE tem um trecho que mudou, este trecho irá testar e verificar se estamos trabalhando com um fantasma, se for um fantasma ele irá ficar travado, mas se for qualquer coisa diferente disto, irá se mover, desde é claro o indicador tenha esta capacidade de poder se mover.

Mas observem também que assim que clicamos indicando ao sistema uma nova posição do indicador, o fantasma com todos os seus componentes irá ser removido da lista de objetos. Até ai acredito se algo bastante simples de ser entendido, mas observem agora um outro ponto, e este esta em destaque, uma chamada a função CreateGhostndicator, mas este código eu não expliquei ainda, é ele é tema para o próximo tópico deste artigo.


2.0.6 - Entendendo como a função CreateGhostIndicator funciona

A função CreateGhostIndicator é uma função bastante estranha a primeira vista, vejam o código dela na integra logo abaixo:

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

Observem que não existe a criação de coisa alguma nesta função, mas no entanto se você compilar e executar o EA, terá como resultado a criação de fantasmas nos indicadores, que mostram o status da ordem no servidor. Para entender vejam o video abaixo, que se trata de uma demonstração do sistema em funcionamento real.



Reparem como é estranho que de fato irá ser criado indicadores fantasmas no gráfico, mas como isto de fato acontece ?!?! Como eu consegui criar indicadores sem de fato criar eles em algum ponto do código ?!?!

Como eu disse eles são fantasmas, você de fato não irá vê-los sendo criados, não adianta tentar ler o código tentando encontrar uma linha que diz: " AQUI ... achei ... neste ponto é que os indicadores fantasmas são criados ... " , a verdade é que eles simplesmente já estão no gráfico, mas não aparecem em lugar algum, até que você venha a manipular uma ordem ou posição, neste momento eles ficam visíveis. Mas como eu consegui este feito ?!?!

Para entender é preciso entender o fluxo de execução do EA, então vamos entender do inicio.

Quando o EA é inicializado temos o seguinte fluxo de execução visto na figura logo abaixo:

Fluxo 1

init_ea     <<< Fluxo de inicialização do sistema

A região em laranja faz parte do EA, já a região verde é parte da classe C_IndicatorTradeView, observem como as coisas acontecem até que o indicador de fato seja criado para posteriormente ser apresentado na tela, as setas em preto são um caminho em comum tanto das ordens pendentes quanto das posições em aberto, já a seta em azul é o caminho feito pelas posições, e as setas em purpura é o caminho percorrido pelas ordens pendentes para que seus indicadores sejam criados, claro que dentro das funções temos coisas que direciona o fluxo em uma ou outra direção, mas o fluxograma aqui tem apenas o intuito de mostrar como as coisas funcionam a grosso modo, de forma que se você desejar conseguirá entender o sistema a ponto de adaptar ele a outras questões, ou ao seu estilo de operar.

Bem o fluxograma acima é usado apenas uma única vez, e este é somente durante a inicialização do sistema. Agora todas as vezes que você for colocar uma ordem pendente no gráfico teremos dois fluxos distintos de execução, o primeiro é responsável pela criação do indicador 0 e da tentativa de colocar uma ordem no gráfico, esta primeira etapa do fluxo de execução é vista na imagem abaixo:

Fluxo 2

     <<< Fluxo de inicialização do indicador 0

É muito importante você perceber que não é a classe que irá criar de fato a ordem que aparece no gráfico, ela apenas irá tentar fazer isto, caso tudo der certo a função SetPriceSelection terá sucesso na sua execução e um novo fluxo irá acontecer, este sim é que irá apresentar a ordem no gráfico, desta forma teremos o fluxo visto abaixo, que de fato irá posicionar a ordem no local que o servidor de negociação estará informando, então não adianta esperar que a ordem irá de fato ficar no local que você indicou inicialmente, pois se a volatilidade vim a fazer com que o servidor execute a sua ordem em outro ponto, que não o que você indicou, o EA irá corrigir isto e apresentar a ordem no local correto, devendo você apenas analisar se as condições estão adequadas ao seu modelo de operar.

Fluxo 3

     <<< Fluxo da colocação de uma ordem pendente

Isto daqui é apenas a parte responsável por colocar a ordem no gráfico e estou falando de uma ordem completa, ou seja, ela terá um ponto de entrada, um take e um stop, mas qual será o fluxo caso um dos limites, seja ele o take ou stop sejam retirados da ordem, pois estes fluxos acima não respondem isto. De fato o fluxo irá ser bastante diferente destes daqui, mas os elementos irão quase que se repetir, vejamos abaixo como será o fluxo no caso de você clicar no botão de fechar um dos limites.

Bem este fluxo é bastante estranho para quem desejava ver algo linear

Fluxo 4

     <<< Fluxo da remoção de uma ordem ou limites da ordem

Vejam que temos 2 fluxo, um ao lado do outro, o primeiro fluxo a ser executado é o da seta purpura, uma vez que ele seja executado, uma resposta do servidor é capturada pelo evento OnTradeTransaction, e este sim que irá disparar o sistema que remove o indicador da tela, só existe uma diferença entre o fluxo de retirada dos limites para o de fechamento de uma posição, ou encerramento de uma ordem, nestes casos, a função SetPriceSelection não será executada, mas o fluxo do evento OnTradeTransaction irá se manter.

Tudo isto é muito bonito e fantástico, mas ainda não responde como acontece o aparecimento dos fantasmas que é justamente o tema deste tópico.

Acontece que para entender como os fantasmas são de fato criados, é preciso saber como o fluxo de execução acontece, para se mais preciso você tem que saber como o EA coloca uma ordem pendente, ou como o fluxo de criação do indicador 0 realmente se dá na prática, e este fluxo é mostrado nesta imagem, se você entender os fluxos de execução acima, entender os fantasmas será bem mais simples.

Chega de enrolação e vamos ver como os fantasmas são criados, olhe novamente a rotina CreateGhostIndicator, você pode observar que ela não cria nada, ela apenas manipula alguns dados, mas por que ela esta fazendo isto ?!?! O motivo é que se você for tentar criar algum objeto ele irá se sobrepor aos objetos já existentes, sendo mostrado na frente deles, e desta forma os objetos que não queremos ocultar ficarão ocultos. Existem duas soluções para este problema, a primeira é criar um conjunto que será mais baixo que todos os outros, ou seja este conjunto seria criado primeiro que qualquer outro objeto que representaria as ordens, mas isto tem um problema, teríamos uma series de objetos inúteis na janela que contem as lista de objetos, e não queremos isto, percebam que todo o código esta sendo modificando justamente para evitar isto. Bem vejamos então uma segunda solução, criar o fantasma logo depois remover o indicador que será manipulado e então criar o mesmo novamente, bem nenhuma das soluções é de fato muito prática, na verdade todas as duas são bastante dispendiosas.

Mas observando a documentação, me deparei com um fato que me chamou a atenção, a função ObjectSetString permite manipular uma propriedade dos objetos que a primeira vista não faz o mínimo de sentido, a propriedade em questão é OBJPROP_NAME, fiquei intrigado com isto, por que isto é permitido ?!?! Não faz sentido, uma vez que o objeto esteja criado, qual seria o sentido de mudar o nome dele ?!?!

Pois bem, este é o ponto, quando renomeamos um objeto, o objeto antigo deixará de existir, e passará a ter um novo nome, uma vez que o objeto tenha sido renomeado ele tomará a posição do objeto original, desta forma o EA irá poder criar o objeto original sem problemas, e o fantasma pode aparecer e desaparecer sem efeitos colaterais ao gráfico, e sem deixar rastros de sua passagem. O único objeto que terei que remover seria o botão para fechar o indicador e isto é feito nesta linha:

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

mas temos um pequeno detalhe extra aqui, observando a documentação da função ObjectSetString vemos um aviso sobre seu funcionamento:

Quando um objeto é renomeado, dois eventos são formados simultaneamente. Estes eventos podem ser manipulados em um Expert Advisor ou o indicador pela função OnChartEvent():

  • um evento de exclusão de um objeto com o nome antigo;
  • um evento de criação de um objeto com um novo nome.

é importante notar isto, pois não queremos que o objeto que estamos renomeando apareça assim do nada, sem que estejamos preparados para isto, desta forma adicionamos mais uma coisa antes e depois da renomeação:

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

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

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

qualquer coisa que esteja dentro do código seguro não irá disparar os eventos de criação e exclusão de objetos. Agora sim temos um código completo onde os fantasmas irão aparecer e poderemos ter um comportamento adequado.

Mas talvez você ainda não tenha entendido como de fato este código esta criando os fantasmas, quando de fato estamos apenas renomeando o indicador que queremos manipular. Vou deixar vocês com a pulga atras da orelha, mas para facilitar um pouco a vida de vocês, vou mostrar como é o fluxo de execução do fantasma, e este é mostrado na imagem abaixo:

Fluxo 5

    <<< Fluxo de execução do fantasma

notem que ele é praticamente um clone quase perfeito do fluxo 2, então divertam-se entendendo como os fantasmas são criados e removidos sem que nenhum código de criação realmente tenha sido criado.


3.0 - Conclusão

Este artigo foi bastante interessante e até mesmo empolgante ser escrito, é verdade que muitas coisas tiveram que ser mudadas no código do EA, mas acreditem, será melhor assim, e de agora em diante ele será tratado desta forma, tem mais algumas coisas a serem feitas e medidas a serem tomadas para tornar ele ainda mais robusto, mas por hora as mudanças feitas aqui já trarão um grande beneficio a todo o sistema, gostaria de enfatizar que um programa bem desenvolvido muitas vezes passa pelas etapas que foram feitas aqui, estudo da documentação, analise do fluxo de execução, benchmark do sistema para verificar se ele esta sendo sobrecarregado em momentos críticos, e principalmente, serenidade para não tornar o seu código em um verdadeiro monstro prestes a lhe deixar maluco, evite ficar tornado seu código em um frankenstein, pois isto não torna o código melhor, apenas complica futuras melhorias e principalmente correções.

Um forte abraço a todos que estão acompanhando esta serie e até o próximo artigo, pois ainda não terminamos, falta algumas coisas a serem criadas ...



Arquivos anexados |
DoEasy. Controles (Parte 1): Primeiros passos DoEasy. Controles (Parte 1): Primeiros passos
Com este artigo, iniciamos um tópico extenso sobre a criação de controles em MQL5 com base no estilo do Windows Forms. E vamos começar criando uma classe-painel. Tudo já está se tornando difícil sem a presença de controles. Por isso, criaremos todos os controles possíveis no estilo do Windows Forms.
Como desenvolver um sistema de negociação baseado no indicador Estocástico Como desenvolver um sistema de negociação baseado no indicador Estocástico
Neste artigo, nós continuamos nossa série de aprendizado - desta vez, nós aprenderemos como projetar um sistema de negociação usando um dos indicadores mais populares e úteis, que é o indicador Oscilador Estocástico, para construir um novo bloco em nosso conhecimento básico.
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Neste artigo, vamos nos livrar de alguns erros ao trabalhar com elementos gráficos e continuar desenvolvendo o controle CPanel. Isto último irá se tratar de métodos para definir os parâmetros da fonte, que é usada por padrão para todos os objetos de texto do painel, objetos esses que, por sua vez, podem ser localizados nele no futuro.
Gráficos na biblioteca DoEasy (Parte 100): Eliminando bugs ao trabalhar com objetos gráficos padrão estendidos Gráficos na biblioteca DoEasy (Parte 100): Eliminando bugs ao trabalhar com objetos gráficos padrão estendidos
Hoje vamos retocar e eliminar falhas evidentes ao trabalhar com objetos gráficos estendidos (e padrão) e com objetos-formas na tela, além disso vamos consertar os erros observados durante os testes no último artigo. E assim vamos concluir esta seção da descrição da biblioteca.