English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 07): Primeiras melhorias (II)

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 07): Primeiras melhorias (II)

MetaTrader 5Exemplos | 21 abril 2023, 13:02
533 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 06): Primeiras melhorias (I), fizemos a correção de alguns pontos, e adicionamos alguns testes no nosso sistema de replay. Estes tentam garantir a maior estabilidade, quanto for possível obter, ao mesmo tempo iniciamos a criação e o uso de um arquivo de configuração para o sistema de replay.

Pois bem, mas apesar de tudo que tem sido feito, e de todos os esforços a ponto de poder criar um sistema de replay, que seja extremamente intuitivo e estável, ainda assim temos alguns problemas pendentes. Alguns deles são mais simples de serem resolvidos, e outros um pouco mais complexos.

Se você observou o video presente no final do artigo anterior, deve ter notado no final do video, que o sistema ainda contém algumas falhas, ou brechas um pouco desconcertantes de serem vistas. Mas decidi que vocês deveriam ver tal falha, para ficarem cientes que o sistema ainda tem muito para ser melhorado e corrigido, antes de realmente podermos começar a usá-lo de uma forma mais intensiva. Não que ele, já não possa ser usado, para que você pratique nele.

No entanto, não quero adicionar funcionalidades, antes de realmente ter corrigido todos os pormenores que tem, ou podem trazer, alguma instabilidade ao sistema, ou para a plataforma. Estou considerando, que alguns pode desejar fazer e praticar no replay, durante o tempo que o mercado esteja aberto, no momento que estejam esperando alguma operação real de fato aparecer, ficam ali no replay. Ou mesmo usando um EA automático, na plataforma, enquanto praticam no replay.

Mas se o sistema de replay não estiver suficientemente estável, não é aconselhável, você manter ele funcionando, durante o tempo que o mercado estiver aberto, isto desde que exista a possibilidade de uma operação real acontecer. O motivo por traz deste alerta é por que, o sistema de replay, pode travar ou impedir que outras funções da plataforma venham a funcionar de forma adequada.

Bem, então continuando este esforço de melhorar ao máximo a estabilidade, e usabilidade do replay de mercado. Vamos implementar mais algumas coisas ao código, de forma a tornar o sistema ainda melhor.


Garantindo que o Indicador de controle permaneça no gráfico

A primeira melhoria que vamos implementar será no nosso indicador de controle. Atualmente, quando você inicia o sistema de replay, um template irá ser carregado. Dentro deste arquivo de template, deveremos ter um indicador que é usado para controlar o serviço de replay. Até ai tudo bem, caso este indicador não esteja presente, o serviço de replay não poderá ser manipulado. Desta forma este indicador é bastante importante para o serviço.

No entanto uma vez carregado, não existia nenhuma forma, até o presente momento, de garantirmos que este indicador permaneça no gráfico. A partir de agora isto será diferente, iremos garantir que ele permaneça no gráfico e caso ele venha a ser retirado por qualquer motivo, o serviço de replay deverá ser encerrado imediatamente.

Mas como podemos fazer isto ?!?! Como garantir que um indicador permaneça no gráfico, ou verificar se ele de fato está lá ?!?! Existem algumas forma de se fazer isto. Mas a forma que ao meu ver é a mais simples, e ao mesmo tempo a mais elegante, é usar a própria plataforma MetaTrader 5, para fazer esta verificação, e isto usando a linguagem MQL5.

Normalmente um indicador não contem certos eventos. Mas tem um, que é especialmente útil aqui para nos, o evento DeInit

Este evento é sempre chamado quando alguma coisa acontece, e o indicador, no caso, deverá ser encerrado para logo em seguida ser inicializado pelo evento OnInit, isto é fato. Então podemos, quando a função OnDeInit for chamada, dizer para o serviço de replay, que o indicador foi removido ?!?! Sim, podemos, mas existe um porém nesta história. O evento OnDeInit, não é chamado apenas e somente quando, no caso, o indicador é removido ou o gráfico fechado.

Ele também é chamado quando você modifica o tempo gráfico ou o indicador tem seus parâmetros alterados. Então aparentemente a coisa parece complicar novamente. Mas se você observar a documentação do evento OnDeInit, irá ver algo que podemos e iremos usar, que são os código de motivo da desinicialização.

Ao observar estes código, notamos que existem dois, que irão nos ser bastante úteis, para informar que o indicador de controle deixou de estar no gráfico. Então criamos o seguinte código:

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        break;
        }
}

O que estamos fazendo aqui, é atestar que o motivo do evento DeInit, ter sido disparado e por consequência disto a função OnDeInit, não é a remoção do indicador do gráfico. Caso seja este o motivo, temos que informar ao serviço de replay, que o indicador não esta mais presente no gráfico, e que o serviço deverá ser encerrado de imediato.

A forma de fazermos isto é removendo a variável global de terminal, que serve de ponte entre o indicador de controle e o serviço de replay. Assim que esta variável for removida, o serviço de replay irá entender isto, como sendo um encerramento das atividades, e nesta hora ele será finalizado.

Se você observar o código do serviço. Irá notar que ao finalizar o serviço, ele também irá fechar o gráfico do ativo de replay. Isto é feito exatamente no momento que o serviço chamar o código abaixo:

void CloseReplay(void)
{
        ArrayFree(m_Ticks.Info);
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

Ou seja, caso o indicador de controle seja removido, o gráfico será fechado junto com o indicador, isto por conta do serviço de replay forçar este fechamento. Mas pode acontecer, de o serviço não conseguir fechar o gráfico do ativo de replay de mercado. Então temos que garantir que este gráfico será fechado, mesmo que o ativo permaneça na janela de observação de mercado, e o serviço de replay por algum motivo não o tire de lá, apesar de tentar fazer isto.

Para fazer isto, iremos adicionar mais uma linha na função OnDeInit do indicador de controle.

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

O que acontece agora é o seguinte: Assim que o indicador de controle, por qualquer motivo seja removido do gráfico. Mesmo que o serviço de replay não consiga remover o gráfico, ele já terá tido uma tentativa de ser fechado pelo próprio indicador. Isto parece ser um pouco irracional, mas quero que em caso de algum tipo de falha ou erro, por parte do usuário, o gráfico, assim como o serviço de replay, deixem a plataforma livre, que não fique ali atormentando.

Com isto implementado, temos pelo menos a garantia de que caso o indicador de controle, deixe de estar presente no gráfico, o sistema será finalizado e o gráfico fechado. Mas temos um outro problema também envolvendo o indicador.


Evitando que o Indicador de controle seja destruído

Esta é uma questão bastante seria, pois pode acontecer do indicador permanecer no gráfico. Mas os elementos que fazem parte dele simplesmente serem removidos ou destruídos, evitando assim que o indicador possa de fato ser utilizado.

Felizmente isto é algo bastante simples de ser contornado e corrigido. Mas é algo que você deve ao mesmo tempo tomar alguns cuidados para que não seja uma fonte de problemas futuros. Isto por que, ao evitar que o indicador seja destruído, ou seus elementos e objetos sejam removidos, podemos de fato criar um monstro incontrolável, que pode nos dar muitos aborrecimentos. Para resolver isto, o que faremos será capturar e manipular o evento de destruição de objetos no gráfico. Mas vamos ver como de fato isto é feito na prática.

Para começar na classe C_Control, iremos adicionar a seguinte linha ao código:

void Init(const bool state = false)
{
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(m_id, state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
}

Ao adicionarmos esta linha ao código, estamos pedindo para que o MetaTrader 5, nos envie um evento quando um objeto gráfico for removido da tela. O simples fato de fazer isto, não nos garante que os objetos não serão removidos, mas nos garante que a plataforma MetaTrader 5, irá nos informar quando isto ocorrer.

Para garantir que quando a classe C_Control for removida, os objetos também o serão. Temos que dizer ao MetaTrader 5, quando não nos enviar o evento de remoção de objetos. Um dos pontos que usa este tipo de função, é vista logo abaixo:

~C_Controls()
{
        m_id = (m_id > 0 ? m_id : ChartID());
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_PrefixObjectName);
}

O que estamos fazendo, é informar ao MetaTrader 5, que NÃO queremos que ele nos envie um evento, quando um objeto for removido do gráfico. Desta forma poderemos remover os objetos desejados sem mais problemas.

Mas nem tudo são flores, e aqui temos um problema em potencial. Observem o fragmento abaixo:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
        static int six = -1, sps;
        int x, y, px1, px2;
                
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                ChartRedraw();
                        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                break;

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

Esta linha, irá e terá que remover a barra de controle de posição, isto irá disparar o evento de remoção de objetos.

Você pode pensar que podemos simplesmente desligar e para depois de ter removido a barra de controle, religar o evento. Isto é verdade, mas pense no seguinte: Conforme o código vai crescendo, estas coisa de ligar e desligar, vão se tornando muito mais comuns, do que pode parecer no inicio. E temos um outro ponto: Os objetos tem que ser colocados em uma determinada ordem, para que eles sejam plotados da forma correta.

Então o simples fato de ligar e desligar o evento de remoção, não irá nos garantir um tratamento correto para o evento. Temos que criar uma solução mais elegante, que seja ao mesmo tempo sustentável, e que mantenha os objetos na ordem correta. Para que a apresentação deles seja sempre a mesma, assim o usuário não irá notar nenhuma diferença no sistema de posicionamento.

A solução mais simples, é criar um procedimento que irá fazer isto de desligar o evento de remoção, remover os objetos que fazem parte de uma mesma cadeia e logo em seguida ligar novamente o evento de remoção. Isto é facilmente conseguido pelo código visto logo abaixo, que irá fazer esta tarefa na barra de controle.

inline void RemoveCtrlSlider(void)
{                       
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_NameObjectsSlider);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
}

Agora sempre que precisamos remover, apenas e somente a barra de controle, chamaremos este procedimento acima, e teremos o resultado que desejamos.

Apesar de parecer algo completamente tolo de ser feito, este procedimento no atual nível de desenvolvimento, é usado não uma, mas duas vezes na mesma função como pode ser visto logo abaixo:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                u_Interprocess Info;
                static int six = -1, sps;
                int x, y, px1, px2;
                        
                switch (id)
                {
                        case CHARTEVENT_OBJECT_DELETE:
                                if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
                                {
                                        if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
                                        {
                                                RemoveCtrlSlider();
                                                CreteCtrlSlider();
                                        }else
                                        {
                                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                                CreateBtnPlayPause(Info.s_Infos.isPlay);
                                        }
                                        ChartRedraw();
                                }
                                break;
                        case CHARTEVENT_OBJECT_CLICK:
                                if (sparam == m_szBtnPlay)
                                {
                                        Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                        if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                        {
                                                RemoveCtrlSlider();
                                                m_Slider.szBtnPin = NULL;
                                        }
                                        Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                        ChartRedraw();
                                }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                                else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                                break;
                        case CHARTEVENT_MOUSE_MOVE:
                                x = (int)lparam;
                                y = (int)dparam;
                                px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                                px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                                if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                                {
                                        if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
                                        {
                                                six = x;
                                                sps = m_Slider.posPinSlider;
                                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                        }
                                        if (six > 0) PositionPinSlider(sps + x - six);
                                }else if (six > 0)
                                {
                                        six = -1;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                                }
                                break;
                }
        }

Mas vamos focar um pouco mais, na parte que é responsável por tratar dos eventos de remoção de objetos. Quando informamos a plataforma MetaTrader 5, de que desejamos receber eventos, quando um objeto gráfico for removido do gráfico, ela irá gerar a cada objeto removido um evento de remoção. Este evento, é então capturado e podemos verificar qual foi o objeto removido. 

Um detalhe, você não verá qual o objeto que será removido, você estará vendo qual foi o objeto que de fato foi removido. No caso, estamos testando para verificar, se foi um dos usados pelo indicador de controle. Caso isto seja verdadeiro iremos fazer um novo teste, e este irá verificar se foi um dos objetos da barra de controle ou se foi o botão de controle. Caso tenha sido um dos objetos que fazem parte da barra de controle, a barra será totalmente destruída para logo em seguida ser recriada. 

Não precisamos informar coisa alguma a esta rotina de criação, já que ela faz todo o trabalho. Agora, caso tenha sido o botão de controle, temos uma situação diferente. Neste caso temos que ler a variável global de terminal, para saber qual é o estado atual do botão para somente ai, podermos fazer o pedido de criação do mesmo.

Para finalizar, forçamos uma colocação imediata de todos os objetos no gráfico, desta forma o usuário se quer irá notar que eles saíram de lá.

Basicamente é isto, que teremos fazendo, para que tudo fique no lugar. Mas agora vamos ver uma outra coisa, que também é importante, e que o sistema de replay consiga executar.


Apenas um gráfico de replay por favor

Uma das coisa que mais acontece, quando estamos trabalhando com um sistema que abre gráficos de forma automática, é que ele começa a abrir gráficos do mesmo ativo, de forma que depois de um tempo você não sabe exatamente, com o que você esta lidando.

Para evitar isto, implementei um pequeno teste, que resolve justamente este problema, que é o fato do sistema de replay ficar abrindo um gráfico após o outro. Sendo todos somente sobre replay. O fato desta rotina existir, nos garante também uma certa estabilidade, com relação aos valores contidos na variável global de terminal.

Se você tiver diversos gráficos refletindo a mesma ideia, no caso replay de mercado, pode ser que em um tenhamos um dado valor, sendo criado pelo indicador de controle, enquanto em outro teremos um valor completamente diferente. Se bem que isto ainda, não foi totalmente solucionado. Mas o simples fato de que não teremos mais vários gráficos, apenas se referindo ao mesmo ativo, e ao mesmo tempo, já será de grande ajuda.

A forma de garantir que iremos ter apenas um único gráfico aberto, de um determinado ativo, é visto logo abaixo:

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

O que estamos fazendo aqui, é verificando se existe algum gráfico aberto no terminal da plataforma MetaTrader 5. Caso exista algum, usamos ele como ponto inicial, para verificar qual o símbolo que se encontra aberto. Se o símbolo é o ativo usado como replay de mercado, então iremos fechar este gráfico .

Aqui existe uma questão, caso o gráfico do símbolo de replay esteja aberto, temos duas alternativas, a primeira é fechar o gráfico que é justamente o que estamos e iremos fazer. A outra, é finalizar o laço, mas neste segundo caso, pode acontecer de se ter mais de um gráfico aberto do mesmo ativo. Então por conta disto, prefiro finalizar os gráficos abertos, e iremos fazer isto até que o último gráfico tenha sido verificado. No final, não teremos nenhum gráfico do replay de mercado aberto.

Então precisaremos abrir um gráfico que irá conter o replay de mercado, aplicar o template de forma que o indicador de controle possa ser utilizado, forçar uma plotagem e retornar o index do gráfico que foi aberto.

Apesar de ter feito este tipo de coisa. Nada impede que o usuário venha a abrir novos gráficos do ativo de replay, depois que o sistema já estiver carregado. Até poderíamos adicionar um teste extra no serviço, de forma que apenas e somente um gráfico, ficaria aberto durante todo o período que o replay estivesse sendo executado. Mas sei que existem operadores, que gostam de utilizar mais de um gráfico do mesmo ativo, e ao mesmo tempo. Porém em cada um destes gráficos, ele irá utilizar um tempo gráfico diferente.

Por conta disto, não irei adicionar tal teste extra, mas iremos fazer uma coisa um pouco diferente. Não iremos permitir, que o indicador de controle, esteja presente e sendo executado em nenhum outro gráfico, que não seja o que foi aberto pelo serviço. Apesar disto, você pode em primeira instancia, finalizar o indicador no gráfico original, tentando colocar ele em outro gráfico. Mas ao fazer isto, o gráfico será fechado e o serviço encerrado, impedindo assim de você efetuar a troca.

Este trabalho de fazer o indicador de controle, não poder ser aberto em outro gráfico, que não o original, é visto no próximo tópico.


Apenas e somente um indicador de controle por seção

Esta parte é bastante interessante, e até pode lhe ajudar em alguns casos. Então vamos ver como garantir, que um indicador irá pertencer, apenas e somente um gráfico, em uma seção de trabalho do MetaTrader 5.

Para entender como fazer isto, observe o código abaixo:

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}

Este código, irá verificar se existe uma variável global de terminal. Caso esta variável exista, iremos capturar o valor dela para uso posterior. Caso ela não exista, iremos inicializá-la.

Agora tem um detalhe: O procedimento OnInit, é chamado sempre que algo acontece, seja no gráfico, seja na atualização dos parâmetros do indicador. No caso atual, o indicador não contem e não irá receber nenhum parâmetro. Então ficamos apenas com os eventos de gráfico, e estes irão acontecer toda a vez que você trocar o tempo gráfico, ou seja se você mudar de 5 minutos para 4 minutos, isto irá fazer com ocorra uma chamada a OnInit. Neste caso, se você simplesmente bloquear a inicialização do indicador, caso a variável global de terminal esteja presente, você terá um problema. O motivo é por que o gráfico será encerrado, e por consequência o serviço também será encerrado. Complicado não é mesmo ?!?!

Mas a solução que iremos utilizar, será bastante simples e ao mesmo tempo bastante elegante. Iremos utilizar exatamente, a variável global de terminal, de forma que iremos saber se já existe ou não um indicador de controle em algum gráfico. Caso exista, ele não poderá ser colocado em outro gráfico, até que ele não esteja presente em nenhum gráfico, que esteja aberto na seção atual do MetaTrader 5.

Para fazer isto, a primeira coisa a ser feita, será criar uma modificação no código usado para comunicação entre os processos,

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay "Replay Infos"
//+------------------------------------------------------------------+
#define def_MaxPosSlider        400
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool    isPlay;         // Indica se estamos no modo Play ou Pause ...
                bool    IsUsing;        // Indica se o indicador está ou não presente ...
                int     iPosShift;      // Valor entre 0 e 400 ...
        }s_Infos;
};

Lembre-se de que podemos adicionar variáveis internas na estrutura, desde que a mesma não ultrapasse o limite de 8 bytes, que é justamente o tamanho que uma variável double tem em memória. Mas já que o tipo boolean irá utilizar apenas 1 bit para poder existir, e temos ainda 7 bits livres dentro do byte, que a variável isPlay esta usando, podemos adicionar tranquilamente mais 7 boolean sem problemas, então vamos utilizar um destes 7 bits livres para saber se o indicador de controle esta ou não presente em algum gráfico.

NOTA: Apesar deste mecanismo se de fato adequado, existe um problema aqui. Mas não irei falar disto agora. Isto será visto em um outro artigo no futuro. Quando será preciso fazer uma mudança nesta estrutura vista neste momento.

Muito bem, então você já deve estar pensando que bastará fazer isto, que estará tudo certo. Mas na verdade, temos que adicionar algumas coisas ao código. Mas não iremos nos preocupar com o código de serviço, e sim e somente, iremos modificar o código do indicador, a ponto de que esta variável adicionada, seja realmente útil para nos.

Então a primeira coisa a ser feita, é adicionar algumas linhas extras no código do indicador, vamos começar pelo ponto visto logo abaixo:

void Init(const bool state = false)
{
        u_Interprocess Info;
                                
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.IsUsing = true;
        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}

Aqui vamos informar e deixar anotado na variável global de terminal, que o indicador de controle foi criado. Então efetuamos uma chamada, de forma que isto irá ficar registrado, mas por que temos que ter uma chamada antes, para criar a variável global de terminal ?!?! Não poderíamos descartar esta chamada ?!?! Na verdade, esta primeira chamada serve para informar a plataforma MetaTrader 5, de que a variável global é temporária, e não deverá ser mantida. Mesmo que você peça para que a plataforma grave os dados das variáveis globais de terminal, estas variáveis tidas como temporárias, não terão seus valores gravados, eles serão perdidos.

Isto é o que queremos, pois caso você precise gravar e depois repor as variáveis globais de terminal, não é adequado, que tenhamos uma informando, que o indicador de controle esta presente. Quando na verdade, ele simplesmente não existe. Por conta disto, temos que fazer as coisas desta forma.

Agora é preciso ter cuidado com relação a este ponto. Já que quando o indicador vier a ser reposto no gráfico pela plataforma, o valor da variável global de terminal, pode estar diferente, por conta de que já avançamos no replay. Então se esta linha não for colocada, o sistema irá simplesmente iniciar do zero o replay de mercado,

O fato de fazer isto, já nos dá uma garantia, mas ainda temos que fazer mais uma mudança,

case CHARTEVENT_OBJECT_CLICK:
        if (sparam == m_szBtnPlay)
        {
                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                {
                        RemoveCtrlSlider();
                        m_Slider.szBtnPin = NULL;
                }
                Info.s_Infos.IsUsing = true;
                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                ChartRedraw();
        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
        break;

Toda vez que clicamos no botão de pause/play, fazemos uma mudança no valor da variável global de terminal. Mas da forma como o código estava, se você clicar neste botão, o valor gravado, não conterá mais a indicação de que o indicador de controle esta presente no gráfico. Por conta deste detalhe, precisamos adicionar uma linha no código. Agora teremos a correta indicação, já que o fato de mudar de pause para play, ou vice-versa, não irá mais criar uma falsa indicação.

A parte que envolvia a classe C_Replay, já esta concluída, mas ainda temos um pouco mais de trabalho. O simples fato de criar a indicação, não garante nada além da existência de tal indicação. Precisamos ir para o código do indicador em si, e agora é preciso tomar um pouco mais de cuidado, para que as coisas funcionem de forma adequada, e não se tornem algo com um comportamento totalmente bizarro.

Então prestem bastante atenção aos detalhes envolvidos. A primeira coisa a ser vista é o código OnInit:

int OnInit()
{
#define def_ShortName "Market Replay"
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if (GlobalVariableCheck(def_GlobalVariableReplay))
        {
                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                if (Info.s_Infos.IsUsing)
                {
                        ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                        return INIT_FAILED;
                }
        } else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
#undef def_ShortName
}

Aqui por motivos práticos, criamos uma definição que nos fornece o nome do indicador. Este nome será usado para que possamos remover o indicador da lista de indicadores presentes, na janela em que podemos ver quais indicadores, estão de fato no gráfico. Mesmo os que não aparecem, podem ser visualizados nesta janela. Não queremos que nenhum indicador inútil permaneça ali.

Por conta disto, eliminados o indicador do gráfico, para saber se o indicador já esta presente em algum gráfico, testamos o valor que criamos na variável global de terminal, desta forma é bem simples e bastante eficiente a verificação. Existem outras forma de se fazer esta mesma testagem, mas já que estamos usando uma variável global de terminal, é mais simples fazer esta verificação por ela.

O restante da função continua da mesma forma, mas agora não será mais possível adicionar o indicador de controle, em mais de um gráfico na mesma seção do MetaTrader 5. Aqui e nem no código que estará em anexo, não foi adicionado nenhum aviso de que o indicador, já está presente em outro gráfico, mas você pode adicionar este aviso, antes da rotina retornar um erro de inicialização.

Apesar de você achar que isto é o suficiente, ainda temos um outro ponto a ser corrigido. Você deve lembrar que cada vez que o MetaTrader 5, recebe um pedido de troca de tempo gráfico, e este talvez seja o evento mais comum na plataforma. Todos os indicadores, assim como outras coisas, serão removidas, para logo depois serem reinicializadas.

Agora pense um pouco: Se o indicador esta dizendo, via variável global de terminal, que existe uma cópia do mesmo sendo executada, em algum gráfico qualquer, e você muda o tempo gráfico, deste gráfico especifico, o indicador será removido. Mas quando a plataforma MetaTrader 5, for repor o indicador no gráfico, este será impedido de ser colocado no gráfico. O motivo é justamente, o que foi visto no código da rotina OnInit. Temos que de alguma forma modificar a variável global de terminal, a fim de que ela não mais informe, que o indicador de controle está presente.

Existem formas bastante exóticas de se fazer isto, mas novamente, a plataforma MetaTrader 5, juntamente com a linguagem MQL5, nos fornece um meio bem simples de fazer esta tarefa. Observem o código abaixo:

void OnDeinit(const int reason)
{
        u_Interprocess Info;
        
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        if (GlobalVariableCheck(def_GlobalVariableReplay))
                        {
                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.IsUsing = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

Lembre-se do seguinte: Quando o indicador vier a ser removido, um evento DeInit será disparado, e este irá chamar a rotina OnDeInit. Esta rotina recebe em seu parâmetro, um valor que indica o motivo da chamada. Este valor é justamente o que iremos utilizar.

Estes valores podem ser vistos em Códigos de Motivos de Desinicialização. Lá vemos que o REASON_CHARTCHANGE, irá indicar que o período gráfico foi modificado. Então o que fazemos é testar, sempre é bom testar a coisas, nunca imagine, ou fique supondo, TESTE, se existe uma variável global de terminal, com o nome esperado. Caso isto seja verdadeiro, iremos capturar o valor da variável. Já que o serviço pode estar fazendo algo, e não queremos atrapalhar ele, modificamos aqui a informação de que o indicador de controle não estará presente mais no gráfico. Feito isto, gravamos a informação de volta na variável global de terminal.

Aqui tenho que deixar um aviso sobre uma possível falha neste sistema. Apesar de ser baixa a probabilidade de que algo de errado aconteça, você sempre deve saber que existe uma falha no método, e assim se preparar para eventuais problemas.

O problema aqui, é que entre a leitura e a escrita da variável, teremos uma pequena brecha. Mas apesar de pequena ela existe, onde o serviço pode escrever um valor na variável global de terminal, antes que o indicador faça isto. Caso este tipo de evento venha a acontecer, o valor esperado pelo serviço quando for acessar a variável global de terminal, será diferente do que realmente está na variável.

Existem forma de se contornar esta falha, mas já que aqui, neste sistema que trata de replay de mercado, não é algo critico, podemos ignorar esta falha e deixar ela passar sem nos preocupar. Mas caso você venha a desejar utilizar este mesmo mecanismo, em algo mais complexo, e onde os valores ali armazenados são critico, aconselho a você, procurar saber mais sobre os meios de bloquear e destravar a leitura e gravação de memória compartilhada, pois a variável global de terminal, nada mais é do que isto, uma memória compartilhada.

No vídeo abaixo, você pode entender um pouco do que foi corrigido e ainda precisa ser corrigido. Vejam que a coisa esta começando a ficar realmente séria.




Conclusão

Apesar do sistema descrito aqui, parecer ser o ideal para resolver falhas operacionais, envolvidas pelo mal uso do indicador de controle. Ainda não é uma solução realmente eficaz, pois ela somente evita alguns dos tipos de problemas, que podemos de fato ter.

Acredito que ao ver o vídeo, você irá acabar notando que ainda temos um outro problema a ser resolvido, que apesar de parecer simples, é bem mais complicado de ser resolvido do que pode parece apenas olhando para ele.


Arquivos anexados |
Market_Replay.zip (13057.92 KB)
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 05): cadeias de Markov Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 05): cadeias de Markov
As cadeias de Markov são uma poderosa ferramenta matemática que pode ser usada para modelar e prever dados de séries temporais em vários campos, incluindo finanças. Na modelagem e previsão de séries temporais financeiras, as cadeias de Markov são frequentemente usadas para modelar a evolução de ativos financeiros ao longo do tempo, ativo esses como preços de ações ou pares de moedas. Uma das principais vantagens dos modelos das cadeias de Markov é sua simplicidade e facilidade de uso.
Algoritmos de otimização populacionais: algoritmo de vaga-lumes Algoritmos de otimização populacionais: algoritmo de vaga-lumes
Vamos considerar o método de otimização de vaga-lumes (Firefly Algorithm, FA). Esse algoritmo evoluiu de um método desconhecido por meio de modificações para se tornar um líder real na tabela de classificação.
Algoritmos de otimização populacionais: Algoritmo do morcego Algoritmos de otimização populacionais: Algoritmo do morcego
Hoje estudaremos o algoritmo do morcego (Bat algorithm, BA), que possui convergência incrível em funções suaves.
Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada
Continuamos a estudar os algoritmos de aprendizado Q distribuído. Em artigos anteriores, já discutimos os algoritmos de aprendizado Q distribuído e de quantil. No primeiro, aprendemos as probabilidades de determinados intervalos de valores. No segundo, aprendemos intervalos com uma probabilidade específica. Em ambos os algoritmos, utilizamos o conhecimento prévio de uma distribuição e ensinamos a outra. Neste artigo, vamos examinar um algoritmo que permite que o modelo aprenda ambas as distribuições.