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

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 02): Primeiros experimentos (II)

MetaTrader 5Testador | 16 março 2023, 12:14
783 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 01): Primeiros experimentos (I), demonstrei as limitações, que qualquer tentativa de montar um sistema de eventos, com um tempo suficiente curto, para podermos gerar um replay de mercado de forma adequada, não será uma tarefa assim tão fácil. Lá acredito que ficou claro, que não iremos conseguir ir abaixo de 10 milissegundos, pelo menos, não usando aquela abordagem. Em muitos casos, este tempo é bastante baixo. Mas se você deu uma olhada nos arquivos anexos, presentes naquele artigo, deve ter notado que este tempo de 10 milissegundos, não é baixo o suficiente. Mas então, será que não existira um outro método, que nos permitiria ir abaixo deste tempo de 10 milissegundos, alcançando um tempo de 1 ou 2 milissegundos ?!?!

Antes de começar a cogitar qualquer coisa relacionada a usar tempos, na casa de uns poucos milissegundos, preciso lembrar a todos que isto não é de fato uma tarefa realmente simples. O principal motivo para isto, é que o próprio temporizador, que nos é fornecido pelo sistema operacional, não consegue chegar a tais valores. Então temos um grande, para não dizer GIGANTESCO problema em mãos. Neste artigo irá tentar responder, e mostrar como poderemos tentar fazer isto. Descer abaixo do tempo que o sistema operacional nos permite temporizar. Sei que muitos podem imaginar que uma CPU moderna consegue fazer bilhões de cálculos por segundo. Mas uma coisa é a CPU fazer cálculos, a outra é você conseguir fazer com que tudo que está rodando no seu computador, consiga dar conta de efetuar a tarefa que precisamos fazer. Lembrando que a proposta aqui, é usar puramente o MQL5 para fazer isto, não será usado nenhum código externo, ou DLLs, apenas MQL5 puro.


Planejamento

Bem, para verificar isto teremos que fazer algumas mudanças na metodologia. Já que se o processo de fato der certo, não iremos precisar mexer novamente no sistema de criação de replay. Iremos focar em outras coisas, para nos ajudar a fazer um estudo ou treinamento, usando seja valores reais de ticks, seja valores simulados. A forma de fazer a montagem das barras de 1 minuto irá se manter. Este será o foco principal deste artigo.

Desta forma, vamos usar uma abordagem o mais genérica quanto possível, e a melhor forma que encontrei, foi usando um mesmo modelo que já comentei em outro artigo. Vamos usar um sistema parecido com cliente servidor. O artigo onde expliquei esta técnica é o Desenvolvendo um EA de negociação do zero (Parte 16): Acessando dados na WEB (II). Já mostrei 3 formas de transmitir informações dentro do MetaTrader 5. Aqui iremos pegar emprestado uma daquelas formas, para ser mais preciso, iremos usar um SERVIÇO, isto mesmo o replay de mercado será um serviço do MetaTrader 5.

Você pode estar pensando, que irei criar tudo do zero, mas ai pergunto: " Por que eu faria tal coisa ?!?! ". O sistema já esta funcionando, só que não esta alcançando o tempo de 1 minuto como desejamos que ele faça. E você me olha com uma cara de quem esta dizendo: " E você acha que mudar a coisa para um serviço irá resolver ?!?! ". Na verdade o simples fato de mudar o sistema para um serviço, não irá resolver o nosso problema. Mas se isolarmos, agora no inicio, a criação das barras de 1 minuto, do restante do sistema do EA, teremos muito menos trabalho depois, já que o próprio EA, irá gerar uma leve latência na execução na construção das barras, e o motivo será visto muito mais para frente.

Perceberam agora o motivo de usar um serviço ?!?! Ele é mais prático que outros meios que foram discutidos no passado. Poderemos controlar ele, da mesma forma que foi feito no artigo, onde expliquei como fazer a troca de mensagens entre um EA e um serviço, Desenvolvendo um EA de negociação do zero (Parte 17): Acessando dados na WEB (III). Mas aqui não iremos focar na geração deste controle, queremos apenas que o serviço gere as barras, que serão colocados no gráfico. Mas para que as coisas se tornem mais interessantes, iremos usar a plataforma de uma maneira mais criativa, não somente usando um EA e um serviço.

Somente para recordar, na ultima tentativa de reduzir o tempo conseguimos o que esta mostrado abaixo:

Esta foi a melhor marca. Aqui iremos esmagar este tempo. E logo de cara. No entanto, não quero que você fique totalmente e completamente, ligado a tais valores, ou aos testes aqui mostrados. Esta sequência de artigos, relacionada a criação de um sistema de replay / simulador, já se encontra em um estágio bastante mais avançado, onde mudei diversas vezes alguns conceitos a fim de conseguir de fato fazer com que o sistema viesse a funcionar de maneira como seria esperado. Mesmo que neste momento, o que será visto aqui, pareça ser adequado, e animador. Lá no fundo, aqui, cometi alguns erros relacionados aos testes de temporização. Tais erros, ou equívocos, não são de forma alguma, simples de serem notados, ou mesmo compreendidos, em um sistema tão inicial. Conforme esta sequência de artigos, for se desenvolvendo, você irá notar que esta questão relacionada a temporização, é algo muito, mas muito mais complexo, e que envolve muito mais coisas do que simplesmente conseguir fazer com que a CPU, e a plataforma MetaTrader 5, consiga lançar no gráfico, uma certa quantidade de dados, a fim de que se possa ter uma imersão no sistema de replay / simulador.

Então, não leve de forma alguma ao pé da letra, algumas coisas que serão vistas aqui. Acompanhe esta sequência de artigos. Pois o que será feito aqui, de fato, está longe de ser algo simples e fácil de ser feito.


Implementação

Vamos começar criando as bases do nosso sistema. Teremos nela:

  1. Serviço de criação das barras de 1 minuto;
  2. Script para iniciar o serviço;
  3. EA para poder fazer as simulações ( este será desenvolvido depois );


Definindo o Serviço Market Replay

Para trabalhar com o serviço, de forma adequada, precisamos atualizar a nossa classe C_Replay. Mas são mudanças tão pequenas, que não irei entrar em detalhes, mas basicamente são códigos de retorno. No entanto existe um ponto que merece destaque, já que ele irá fazer algo a mais, e este código é visto abaixo.

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                {
                                        m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                        m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                        m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                        m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                        isNew = false;
                                        m_ReplayCount++;
                                }
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Tempo decorrido : ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

As partes em destaque, foram adicionadas ao código original da classe C_Replay. O que estamos fazendo, é definindo um tempo de delay, ou melhor dizendo, iremos usar exatamente, o que esta sendo informado pelo valor da linha, só que em milissegundos. Lembre-se este tempo não será exato, ele depende de algumas coisas. Mas vamos tentar fazer a coisa funcionar em um tempo o mais próximo possível a 1 milissegundo.

Feitas estas mudanças. Vamos ver a o código do serviço, ele esta abaixo na integra:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //Arquivo com ticks
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        Print("Aguardando permissão para iniciar replay ...");
        GlobalVariableTemp(def_GlobalVariable01);
        while (!GlobalVariableCheck(def_SymbolReplay)) Sleep(750);
        Print("Serviço de replay iniciado ...");
        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
                if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);
        Print("Serviço de replay finalizado ...");
}
//+------------------------------------------------------------------+

O código acima, é responsável por criar as barras.  O fato de colocar este código aqui, irá fazer com que o sistema de replay, funcione de forma independente. Sendo pouco influenciado ou influenciando o desempenho da plataforma MetaTrader 5. Assim poderemos trabalhar em paz nas demais coisas relacionadas ao sistema de controle, analise e simulação do replay. Mas isto será feito mais para frente.

Agora uma coisa interessante: Vejam nas áreas destacadas, que temos uma função GetTickCount64. Esta irá nos fornecer um equivalente ao sistema que foi visto no artigo anterior. Mas com uma vantagem, aqui a resolução, irá cair para um tempo de 1 milissegundo. Mas a precisão não é exata, é algo aproximado, mas o nível de aproximação, é bem próximo do que seria o movimento real do mercado. Este irá independer do hardware que esta sendo usando. Apesar de tudo, você pode até criar algum tipo de laço, que garanta uma precisão maior, mas será bastante trabalhoso, já que irá depender do tipo de hardware que esta sendo usado.

A próxima coisa a ser feita, é o script. Este é visto na integra logo a seguir:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        Print("Aguardando Serviço de Replay ...");
        while((!GlobalVariableCheck(def_GlobalVariable01)) && (!IsStopped())) Sleep(500);
        if (IsStopped()) return;
        Replay.ViewReplay();
        GlobalVariableTemp(def_SymbolReplay);
        while ((!IsStopped()) && (GlobalVariableCheck(def_GlobalVariable01))) Sleep(500);
        GlobalVariableDel(def_SymbolReplay);
        Print("Script de Replay finalizado...");
        Replay.CloseReplay();
}
//+------------------------------------------------------------------+

Ambos os códigos, são bastante simples, como se pode ver. Mas eles conversam um com o outro, via variáveis globais que são mantidas pela plataforma. Desta forma temos o seguinte esquema:

Este esquema, será mantido pela própria plataforma. Caso o Script seja finalizado, o Serviço irá parar. Caso o serviço pare, o símbolo que estamos usando para executar o replay, irá parar de receber dados. Isto torna a coisa super simples e altamente sustentável. Já que qualquer melhoria, seja na plataforma, seja o seu hardware, irá automaticamente refletir na performance geral. Isto não por milagres, mas por conta de pequenas latências, que acontecem durante cada uma das operações executadas, pelo processo de serviço. Somente ele irá de fato afetar o sistema de forma geral, não precisamos nos preocupar com o script, e nem com o EA que será montado no futuro. Toda e qualquer melhoria, irá afetar apenas o serviço.

Para poupar vocês do trabalho de testar o sistema. Você pode visualizar o resultado na imagem abaixo. Isto para que você, caro leitor, não precise ficar um minuto inteiro esperando que o resultado venha a parecer no seu gráfico.

Notem que o resultado ficou muito próximo do ideal. Os 9 segundos extras, podem ser facilmente removidos com ajustes no sistema. O ideal é que o tempo seja inferior a 1 minuto, assim fica mais fácil ajustar as coisas, já precisaremos apenas adicionar um delay no sistema. É mais fácil adicionar uma latência, do que reduzir esta mesma latência. Mas se você acha que o sistema, não pode ter seu tempo reduzido, vamos ver isto com uma ótica mais profunda.

Se você observar, existe um ponto que irá gerar um retardo no sistema, e este esta no serviço. O ponto que de fato irá gerar um retardo, esta destacado no fragmento abaixo. Mas e se você tornar esta linha um comentário ?!?! O que aconteceria com o sistema ?!?!.

        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
// ...  COMENTARIO ...  if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);

A linha em destaque, agora não será mais executada. Agora neste caso, irei lhe poupar de testar localmente o sistema, tendo que aguardar novamente um minuto. Você pode ver o resultado da execução no vídeo abaixo. Podendo ver o mesmo integralmente, ou saltar para a parte onde apenas demonstro o resultado final. Fique a vontade de fazer a sua escolha.



Ou seja, a grande dificuldade de fato, é gerar um atraso de forma adequada. Mas o pequeno desvio no tempo de 1 minuto, para que a barra seja criada, acredito não ser de fato um problema. Já que mesmo em uma conta real, não teremos um tempo exato, existe um pequeno delay por conta da latência na transmissão das informações. Este é bem pequeno, e isto é fato, mas ainda sim existe.


Velocidade máxima. Será mesmo ?

Aqui vamos fazer uma ultima tentativa de deixar o sistema operando abaixo de 1 minuto.

Quando você observa os valores de milissegundos, irá perceber que as vezes, temos uma variação de apenas 1 milissegundo, entre uma linha e outra. Mas estaremos tratando tudo dentro do mesmo segundo. Então podemos fazer uma pequena mudança no código. Adicionando um laço dentro dele, e pode ser, que este faça uma diferença muito grande no sistema, de forma geral.

A mudança é vista abaixo:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
inline int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                do
                                {
                                        while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                        {
                                                m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                                m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                                m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                                m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                                m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                                isNew = false;
                                                m_ReplayCount++;
                                        }
                                        mili++;
                                }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Tempo decorrido : ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

Notem que agora, teremos um laço externo, que irá fazer este teste de 1 milissegundo. Já que é muito difícil fazer um correto ajuste dentro do sistema, a ponto de tirar proveito do uso deste único milissegundo, talvez seja melhor tirar ele da jogada.

Bem, foi feita apenas esta mudança, mas veja o resultado no video abaixo.



Para quem de fato deseja algo ainda mais rápido, vejam só o resultado:

Acredito que é suficiente, agora temos a criação da barra de 1 minuto, abaixo deste tempo, podemos fazer os ajustes para alcançar o tempo perfeito, adicionando delays no sistema. Mas não vou fazer isto, a ideia é ter um sistema que nos permita fazer estudos simulados. Qualquer coisa próxima de 1 minuto, estará muito bom, para que o operador treine e pratique. Não precisa ser algo exato.


Conclusão

Agora já temos o sistema básico da criação do replay, podemos avançar para os próximos pontos. Vejam que tudo foi resolvido, apenas ajustando e usando funções presentes na própria linguagem MQL5, mostrando que ela de fato consegue fazer muito mais do que muitos acreditam ser possível.

Mas acredite o trabalho esta apenas começando.

Arquivos anexados |
Replay.zip (10746.69 KB)
Desenvolvendo um sistema de Replay — Simulação de mercado (Parte 03):  Ajustando as coisas (I) Desenvolvendo um sistema de Replay — Simulação de mercado (Parte 03): Ajustando as coisas (I)
Vamos dar uma ajeitada nas coisas, pois este começo não está sendo um dos melhores. Se não fizermos isto agora, vamos ter problemas logo, logo.
Matrix Utils, estendendo as matrizes e a funcionalidade da biblioteca padrão de vetores Matrix Utils, estendendo as matrizes e a funcionalidade da biblioteca padrão de vetores
As matrizes servem como base para os algoritmos de aprendizado de máquina e computação em geral devido à sua capacidade de lidar efetivamente com grandes operações matemáticas. A biblioteca padrão tem tudo o que é necessário, mas vamos ver como podemos estendê-la introduzindo várias funções no arquivo utils, ainda não disponível na biblioteca
Ciência de dados e Aprendizado de Máquina (parte 10): Regressão de Ridge Ciência de dados e Aprendizado de Máquina (parte 10): Regressão de Ridge
A regressão de Ridge é uma técnica simples para reduzir a complexidade do modelo e evitar o ajuste excessivo que pode resultar da regressão linear simples
Indicadores não-lineares Indicadores não-lineares
Neste artigo, vamos considerar algumas formas de construir indicadores não-lineares e seu uso na negociação. Existem alguns indicadores disponíveis na plataforma de negociação MetaTrader que utilizam abordagens não-lineares.