English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 13): Nascimento do SIMULADOR (III)

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 13): Nascimento do SIMULADOR (III)

MetaTrader 5Testador | 2 junho 2023, 14:05
472 0
Daniel Jose
Daniel Jose


Introdução

O artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 12): Nascimento do SIMULADOR (II), foi uma preparação para este artigo aqui. Neste iremos fazer algumas mudanças no sistema de simulação, de forma a ter uma maior consistência nos dados que são informados. Ao mesmo tempo que iremos fazer algumas mudanças, meio quanto drásticas, de maneira a ter um sistema mais eficiente, isto em termos de processamento. Iremos precisar disto, nas próximas etapas da construção, do nosso sistema de replay/simulador. Mas a grande questão, é que para o sistema de fato ser viável de ser utilizado, seja como replay, seja como simulador. Precisamos que ele tenha um comportamento constante, ou que este seja o mais constante e consistente quanto for possível ser feito. Não podemos simplesmente forçar o sistema, a funcionar de uma forma em alguns momentos, e em outros ele ter um funcionamento totalmente diferente e imprevisível.

Apesar de no artigo, Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 02): Primeiros experimentos (II), tenhamos feito um sistema, que naquele e ate o presente momento, fosse adequado para ser utilizado. Ele não é tão adequado assim, quando a ideia começa a envolver simulações e geração de dados pseudo aleatórios. E para dizer a verdade, até mesmo, se você estiver trabalhando com um replay ( utilizando tickets reais ), o sistema atual, não é de todo adequado. Ainda mais se o ativo, ou dia especifico, contiver muita volatilidade. Neste tipo de cenário, o sistema de apresentação e criação das barras de 1 minuto, até então presente no sistema, é muito pouco eficaz. Causando em alguns momentos, problemas com relação a temporização. Ou seja as barras que tinham que ser construídas em 1 minuto, em alguns momentos, podem ser de fato criadas, em um tempo muito superior. Nos dando a falsa impressão, que um movimento de alta volatilidade, é simples de ser acompanhado ou operado. O que de fato não é verdade.

Resolver isto, está longe de ser uma tarefa simples. Isto por que, precisaremos mudar a forma como esta construção realmente se dá. Você pode estar pensando esta tarefa será simples. Mas ela não será. Ela envolve um tipo de modelagem, que a torna bastante complicada, se você não souber o que esta fazendo. Mesmo eu, que estou mostrando como fazer, demorei todo este tempo, para perceber que havia algo de errado no sistema de construção das barras. Somente fui notar, quando foi iniciado a fase de simulação. Onde as diferenças no tempo, começaram a de fato ser gritante, já que envolve alguns cálculos que iremos ver como fazer. Mas mesmo agora, não pense que irei conseguir resolver este problema. Ele será resolvido em uma outra fase de construção. Mas isto é assunto para o futuro. Mas vamos começar fazendo algumas correções. E a apresentação, do novo sistema de construção das barras de 1 minuto.


Um novo Serviço de replay

Para de fato fazer a construção das barras de 1 minuto, de forma a que possamos auditá-las, se assim desejarmos. Precisaremos mudar algumas coisas no serviço de replay. A primeira coisa a ser modificada, é o arquivo do serviço. Observem abaixo na integra, como será o novo arquivo do serviço de replay.

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Serviço de Replay-Simulador para plataforma MT5."
#property description "Este é dependente do indicador Market Replay."
#property description "Para mais detalhes sobre esta versão veja o artigo:"
#property description "https://www.mql5.com/pt/articles/11034"
#property link "https://www.mql5.com/pt/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Tempo gráfico inicial.
input bool              user02 = true;          //Visualizar a construção das barras.
input bool              user03 = true;          //Visualizar métricas de criação.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Serviço de replay finalizado...");
}
//+------------------------------------------------------------------+

Notem que ele é muito mais simples, pelo menos aparentemente. Toda a complexidade foi jogada para dentro da classe objeto, e isto tem um motivo, e um motivo é bastante forte. O TEMPO. Existem questões sutis aqui, que irei explicar conforme o artigo vai se desenrolando. Mas da forma como o antigo arquivo de serviço estava, algumas operações estavam consumindo milissegundos preciosos do sistema. Mesmo tentando fazer a plataforma MetaTrader 5, executar de forma mais eficiente, estes milissegundos consumidos, estavam no final das contas, degradando a performance, de forma que as barras de 1 minuto, estavam gastando mais tempo para de fato serem processadas e construídas.

Mas se você observar, verá que foi adicionada uma nova variável para o usuário. Esta permite que você crie e faça uma auditoria do tempo, que esta sendo gasto, ou necessário para se criar as barras de 1 minuto. Notem que agora temos apenas, e somente, uma única chamada que irá ficar travando o laço de criação das barras. Esta chamada não irá retornar, salvo em duas situações bem específicas. Mas logo iremos entender quais são estas situações. Este laço, então irá funcionar, como sendo um laço infinito, mas que na verdade, irá ser regulado pelo próprio procedimento, que irá estar dentro da classe objeto. Por conta desta simplificação, o arquivo de serviço de fato, é apenas e somente isto. Já a classe objeto, como muitos já devem estar pensando, teve a sua complexidade aumentada. Com isto algumas funções, que antes eram publicas, deixaram de ser, e passaram a ser privativas da classe. Isto é chamado de ocultação de método. Então de fato, as únicas coisas realmente publicas, são as funções que estão presentes e aparecem no arquivo do serviço, que pode ser visto acima.

Desta forma, vamos começar a ver as mudanças que aconteceram, e que permitirão, ao código presente no arquivo de serviço, uma maior simplificação do mesmo. Como você acabou de ver acima. A primeira das mudanças, é vista logo abaixo:

                bool ViewReplay(ENUM_TIMEFRAMES arg1)
                        {
                                u_Interprocess info;
                                
                                if ((m_IdReplay = ChartFirst()) > 0) do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                        {
                                                ChartClose(m_IdReplay);
                                                ChartRedraw();
                                        }
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
                                ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
                                ChartRedraw(m_IdReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                GlobalVariableTemp(def_GlobalVariableIdGraphics);
                                GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }

Basicamente o código, esta igual era antes. Mas nele, adicionamos os testes que antes eram feitos no código do arquivo de serviço. Desta forma, o arquivo de serviço, deixa de precisar saber qual é a ID do gráfico, que esta sendo utilizado, para apresentar o ativo de replay. Assim este ponto, irá esperar que a variável global de terminal, seja criada pelo indicador. Mas caso o usuário feche o gráfico, ou encerre o serviço, este laço irá terminar. No entanto, caso tudo esteja OK, e a variável seja definida, sem que o gráfico ou o serviço, tenham sido encerrados pelo usuário. Teremos como retorno um valor TRUE, e assim o serviço irá prosseguir para a próxima fase, que é justamente o procedimento visto logo abaixo.

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }

Esta rotina acima, é muito mais do que parece. Você pode estar achando que ela fica entrando e saindo. Mas na verdade, ela só retorna para o código do arquivo de serviço, em dois casos. O primeiro, é se o usuário venha a dar pause na criação das barras de 1 minuto. E o segundo, é quando o serviço é finalizado pelo usuário. Seja pelo fechamento do gráfico, seja por conta que não temos mais tickets para serem usados. Ou resumindo, quando o serviço foi encerrado por algum motivo. De qualquer forma, não existira outro caso. No caso do ter ocorrido o fim dos tickets, teremos um retorno TRUE, em qualquer outra hipótese, teremos um retorno FALSE. E o retorno FALSE irá finalizar o sistema de replay / simulação, como pode ser observado ao analisar o código do serviço.

Mas vamos entender, o que acontece no resto do tempo, em que esta rotina simplesmente, fica pressa dentro dos laços internos contidos nela. Pois é isto mesmo, temos 2 laços. Cada um responsável por algo bem especifico. Vamos dar um foco especial em ambos, para que você compreenda o que esta sendo feito. O primeiro laço que temos, é visto em destaque no fragmento abaixo:

// ... declaração das variáveis ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... restante do código que será visto em seguida ...

Este fragmento acima, conta com um laço, que ficará operante em duas situações. A primeira é que o serviço não tenha sido encerrado, por meio do fechamento do gráfico, mas observem que também estamos testando uma variável. Então o que estamos fazendo ?!?! Reparem que dentro do laço, estaremos testando algumas condições, de forma a modificar o valor da variável. Mas de onde vem estes testes ?!?! Estes testes, eram executados, até na versão anterior, dentro do código do serviço. No entanto, existia um problema. Estes testes consumiam alguns milissegundos, a cada vez que eles eram executados. Mas o que mais consumia ciclos de maquina, é justamente o que analisava, se o gráfico estava ou não aberto.

O fato de fazemos este teste, antes do sistema realmente começar a construir as barras de 1 minuto, nos poupa deste consumo de ciclos de máquina. Entretanto, precisamos de uma forma de sair deste laço. Então caso o usuário venha a dar play no serviço, teremos uma indicação de que o laço precisa terminar. Para garantir que ele sairá, indo em direção ao sistema de construção das barras, colocamos a variável de teste com um valor positivo. Agora, se por qualquer motivo, o laço termine sem que seja pelo motivo do usuário ter dado play na construção das barras, iremos retornar um valor FALSE. Desta forma o serviço irá saber, que o replay / simulação deverá ser encerrado.

Porém, se for dado a condição de play, teremos duas coisas a serem de fato feitas. A primeira, é procurar o ponto de inicio do replay / simulação. Esta rotina, responsável por isto, será vista mais a frente neste artigo. A segunda coisa a ser feita, é zerar o sistema de delay. Desta forma, poderemos entrar no segundo laço, que é visto logo abaixo.

// ... Código do laço anterior ...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... Restante da rotina ...

Este é o segundo laço responsável pelo correto funcionamento do sistema. Neste é que de fato iremos construir as barras de 1 minuto. O sistema não irá sair deste laço, a menos que, não tenha mais dados a serem utilizados, ou que o sistema, tenha sido finalizado. Existe uma outra condição para que saiamos do sistema, que é justamente, quando o usuário vier a dar pause. Neste caso, a rotina será encerrada, para logo depois ser chamada novamente, retornando para o primeiro laço, que foi visto anteriormente. Agora vamos ver, por que este sistema, é mais eficiente do que o sistema presente, na versão anterior do serviço de replay / simulação. Aqui fazemos a chamada a uma função, que será vista depois. Esta irá criar as barras de 1 minuto. Não se preocupem com o funcionamento da mesma agora. Apenas saiba que a construção, é feita em outro local.

Mas agora, é que vem a grande questão: Se você observar com atenção, verá que cada um dos tickets, seja ele realmente negociado, seja ele simulado. Deverá ser criado em um determinado momento. Se você ainda não viu isto, veja na imagem abaixo a área em destaque:

Observem que temos a hora, os minutos, e os segundos, que para nos, não faz muita diferença. O que realmente importa para nos aqui, no replay / simulação, é o numero que vem depois dos segundos, ou seja os milissegundos. Observando estes números, você nota que eles podem até parecer muito tempo. No entanto, o que temos de fato a vim a saber, não é o momento em milissegundos, e sim a diferença, entre o tempo do ultimo ticket, para o que iremos mostrar a seguir. Ao observar isto, você nota que em vários casos, a diferença é muito pequena. As vezes de menos de 2 milissegundos. O sistema anterior, não era capaz de dar cabo disto, precisamos de um sistema mais rápido. E apesar de termos tentado, ele não é capaz de conseguir trabalhar, de forma adequada, quando os tempos são muito pequenos, como pode ser visto na imagem acima. Tais tempos entre uma chamada e outra, é bem abaixo de 10 milissegundos.

Mas usando esta nova forma de montagem das barras de 1 minuto, somos capazes de descer abaixo dos 1 milissegundos. Isto em computadores modernos. E já que o procedimento de construção das barras, se tornou bastante rápido como será visto depois. Não precisamos recorrer ao OpenCL, de forma a ter uma performance adequada. Conseguimos fazer isto, apenas usando a própria CPU, sem necessidade de utilizar a GPU para tanto. No entanto, é preciso observar, que não iremos mais fazer um delay a cada ticket. Vamos acumular um pouco as coisas, para depois fazer uma breve pausa. Este valor acumulado, poderá ser modificado, assim como o valor da pausa. Isto nos permite fazer um ajuste fino. Nos testes o resultado foi bastante adequado. E você pode verificar isto ao visualizar o vídeo, logo abaixo. Onde poderá ver o tempo que o sistema conseguiu fazer, entre as barras de 1 minuto.

Notem que a precisão não é perfeita, mas você pode ajustar os valores, de forma a ter um tempo bem mais perfeito. Lembrando que não é possível utilizar, o contador interno do sistema operacional, de forma a deixar este tempo exato. Já que o tal contador, não conseguirá trabalhar com tempos inferiores a 16 milissegundos, com uma boa precisão. Note que o trabalho aqui é de pesquisa, e não é algo que já se encontra finalizado. Então pode ser que demore algum tempo para encontrar uma forma de deixar as coisas ainda melhores. Mas por hora acredito ser o suficiente.



Mas também precisamos verificar, se o gráfico continua aberto. Isto é feito de tempos em tempos. Mas já que iremos fazer isto em uma quantidade bem menor de vezes por segundo, o retardo causado será bem mais baixo. Atente-se para este fato, toda e qualquer chamada, irá gerar sempre um pequeno retardo. Assim como também precisamos atualizar o valor da posição, e capturar o status atual do indicador de controle, Isto também irá gerar um pequeno retardo na execução. Assim como a verificação de que se gráfico esta ou não aberto, irá fazer com que venhamos a ter um pouco de perda no desempenho. Mas esta será bem menor, justamente por conta que a chamada é executada poucas vezes por segundo.

Com isto, podemos finalizar esta parte por hora. Mas antes vamos ver as duas outras rotinas, que fazem parte deste sistema de construção das barras de 1 minuto. A primeira rotina, é a que irá fazer a procura pelo ponto, onde o replay / simulação, deverá ser inicializado.

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }

Esta não sofreu grandes mudanças em relação a versão anterior. Entretanto, diferente daquela versão. Nesta daqui, não retornaremos mais nenhum valor. A confecção das barras, foi modificada, para uma outra forma mais eficiente. Pode até parecer pouco, mas o simples fato, de se fazer isto, já irá ajudar muito. Agora tem mais um pequeno detalhe: Esta rotina acima, não é mais uma rotina publica. Ela não pode mais ser acessada, fora da nossa classe objeto. Assim com a próxima rotina, será justamente a responsável por criar as barras de 1 minuto.

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Esta rotina praticamente se auto explica. O que temos aqui, é a criação das barras de 1 minuto, ticket a ticket, para logo depois, elas serem enviadas para o gráfico. É bem verdade, que ainda não estamos trabalhando nos tickets, ou seja, ainda não podemos utilizar, certos recursos do MetaTrader 5, para o replay / simulação. Isto será implementado futuramente. Então não se preocupe, com estes detalhes no momento. Ao mesmo tempo que fazemos isto, também fazemos, todos os testes e medições, que são necessárias para saber, se uma nova barra precisa ou não começar a ser construída. Agora esta região, pode ser descartada, se você desejar fazer isto no futuro. Pois, tudo que ela faz, é mostrar as métricas de tempo gasto, entre uma barra e a barra anterior. No momento, isto é bastante útil, a fim de nos ajudar a ajustar, de forma fina, os valores no delay dentro do laço de criação.

Com isto, terminamos esta parte sobre apresentação das barras de 1 minuto. Já que agora o sistema esta conseguindo apresentar elas, em um tempo bastante aceitável. Pelo menos no que diz respeito aos tickets negociados e simulados, com os quais efetue os testes. Então vamos resolver um outro problema, que é relacionado aos tickets simulados. Com relação aos tickets reais, não temos nenhum tipo de pendencia extra neste exato momento.


Visualizando o gráfico do RANDOM WALK

Até agora o trabalho foi interessante, e até mesmo agradável de ser feito. No entanto agora, vamos partir para algo, que para alguns, é realmente muito complicado. Porém que precisa realmente ser feito. Simular o lançamento de todos os tickets, que podem estar presentes de alguma forma, nas barras de 1 minuto. Para que você entenda, o que de fato estará acontecendo, sugiro que estudem todo o conteúdo que estarei mostrando. E para não complicar muito a coisa toda, aqui não irei mostrar a versão final do sistema de simulação. Tal versão final, será mostrada depois. O motivo é que a coisa toda, é bastante densa para ser mostrada de uma única vez.

O que realmente queremos e iremos produzir, é o chamado RANDOM WALK. Este "caminhar de bêbado" tem algumas regras. Diferente do que normalmente é programado, aqui não podemos deixar o sistema ser completamente aleatório. Precisamos criar algumas regras matemáticas, para tentar direcionar o movimento. Não me entendam mal, o random walk, de fato é um movimento completamente aleatório e imprevisível, pelo menos no que diz respeito ao curto prazo. Mas por conta que não estaremos, de fato criando um movimento imprevisível, já que sabemos onde ele inicia, e onde ele de fato termina. O sistema não é de todo aleatório, apesar de que, ainda assim, iremos fazer com que alguma aleatoriedade aconteça, dentro das barras.

Existem algumas ideias, que podem ser utilizadas, de forma a facilitar a criação, e confecção de um movimento, realmente random walk. Porém algumas abordagens, são melhores e outras nem tanto, isto para cada caso especifico. Um programador com pouca experiência, pode achar que bastaria utilizarmos o gerador de números aleatórios, e fazer algum tipo de conversão, de maneira a limitar os valores, a um determinado range. Bem esta abordagem, apesar de não parecer de toda errada, contém algumas falhas. Ao observar os dados gerados, por tal movimento, em um gráfico, você teria algo parecido com a imagem abaixo:

Você pode estar pensando, que este gráfico ( SIM ISTO é um gráfico, que depois irei mostrar como criar ), não se parece em nada, com um movimento aleatório. Parece mais uma grande bagunça. Mas na verdade, ele é de fato um movimento aleatório, só que, o movimento é feito, usando saltos entre os pontos do momento. Para conseguir tal movimento, estaremos utilizando a seguinte linha de dados, obtidos com a ajuda da plataforma MetaTrader 5. Lembrando que cada linha, representa uma barra de 1 minuto.

Estes dados estarão disponíveis nos arquivos de anexo, para que você possam fazer suas próprias analises depois. Mas vamos continuar, para entender, o por que do gráfico acima, ser tão inesperadamente diferente do que seria esperado. Para fazer isto, entender por que do gráfico ser assim, será preciso você saber como ele foi criado. Para começar, dentro do arquivo de serviço, iremos definir duas coisas. Estas serão utilizadas de forma temporária. Futuramente não irão aparecer novamente no código fonte.

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

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

Esta definição, irá nos permitir gerar, um teste de simulação, para que possamos analisar o gráfico do movimento gerado. Já este arquivo, irá conter os dados correspondente a movimentação contida, e simulada dentro de uma barra de 1 minuto. Já já iremos ver onde, e quando este arquivo será de fato criado. Uma vez que isto é definido, diretamente no arquivo de serviço, e antes que declaremos os arquivos de cabeçalho. Poderemos utilizar esta definição, nos arquivos MQH. Então vamos para o arquivo C_Replay.Mqh, para saber o que faremos para conseguir dos dados.

Para de fato capturar as coisas, de forma a se ter uma noção, de como o simulador criou o movimento, dentro da barra de 1 minuto. Iremos utilizar a função abaixo.

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Convertendo barras em ticks. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }

Um detalhe que alguns, menos experientes, podem ficar se perguntando: Será que o simulador, irá sempre fazer o mesmo tipo de simulação, de movimento dentro da barra de 1 minuto ? Na verdade, NÃO. Vamos tentar criar uma forma, de que cada barra, seja e tenha um movimento exclusivo e único. No entanto, se você desejar que a movimentação, seja sempre idêntica ou parecida entre as barras, bastará forçar o sistema a uma condição, que é de sempre iniciar o simulador, a partir de um determinado valor. A forma de se fazer isto, é adicionar uma chamada, antes da chamada do simulador, e colocar um valor fixo nesta chamada, conforme mostrado abaixo:

// ... Código ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Convertendo barras em ticks. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

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

Mas vamos voltar ao nosso código original. Por conta que estaremos com uma definição no arquivo de serviço, quando a execução alcançar este ponto, iremos criar um arquivo, definido lá no serviço, que irá conter todo o conteúdo, presente na matriz de tickets, que no caso é uma matriz simulada. Isto independentemente do que esteja acontecendo dentro da função simulation. Desta maneira, poderemos utilizar o EXCEL, para verificar graficamente o que aconteceu. Detalhe: Ao fazer isto, você irá receber uma mensagem de erro, por parte do serviço. Mas esta mensagem deverá ser ignorada, quando a definição de teste estiver presente. Um outro ponto, e que também você precisa saber, é que iremos utilizar o EXCEL, por ele ser mais simples para gerar o gráfico. Até poderíamos utilizar o MetaTrader 5 pra fazer isto. Mas o resultado irá ficar muito confuso, dada a quantidade de informações geradas, sendo mais simples visualizar ele no EXCEL. Nada impede de você utilizar um outro programa para gerar o tal gráfico. O que de fato é importante, é que você gere e visualize o gráfico que foi criado pelo simulador. Se você não sabe como fazer isto no EXCEL. Veja o vídeo abaixo, onde mostro como fazer.



É de suma importância, que você saiba como fazer o gráfico. Já que você precisará atestar de alguma forma, que o movimento esta sendo criado de uma maneira adequada. Apenas observar o movimento das barras sendo criadas, não será de fato suficiente para que você perceba, se ele esta ou não, com o nível de aleatoriedade adequado. Uma outra coisa muito comum entre programadores, é que apenas tentando complicar os cálculos, que são feitos dentro do procedimento de simulação, não atestam em nada, que de fato estamos tendo um movimento realmente aleatório, e similar ao RANDOM WALK. Então veja o vídeo. Ele é curtinho e pode lhe ajudar muito nas próximas etapas, além de outras coisas também. Já que irei apenas mostrar o gráfico obtido. Você precisa saber por que o gráfico esta tendo aquela ou esta aparecia. Para assim conseguir testar localmente as coisas.


Conclusão

Para não complicar e confundir você, caro leitor, com relação ao que será visto, na implementação, de um modelo RANDOM WALK. Vou encerrar este artigo neste ponto. Então no próximo artigo, iremos implementar um modelo RANDOM WALK, de caminhar livre. Onde o gráfico gerado terá uma outra aparência. Também iremos ver, os problemas que este caminhar livre, nos oferta. E o que estará por detrás da ideia. Mas como aqui já temos algo que para muitos é novidade, e para outros já é bastante complicado. Não vou complicar ainda mais as coisas. Apesar de que no código que estará no anexo, você já terá acesso a algo que somente irei explicar no próximo artigo.

Não tenha pressa. Primeiro estude e compreenda o que foi explicado neste artigo daqui. Pois sem este conhecimento e o correto entendimento deste conteúdo, você não irá conseguir entender absolutamente nada do que irá ser feito em breve. 

Um detalhe: Para compilar o serviço, de forma a ter o sistema de replay / simulador funcional. Ou melhor dizendo, que ele deixe de gerar os dados para analise do gráfico. Desative a diretiva de teste. Da mesma forma, como é mostrado no código logo abaixo. Isto no que diz respeito ao arquivo de serviço.

//#define def_TEST_SIMULATION // <<-- Deixe esta linha desta forma para poder utilizar o serviço de replay / simulador ....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

Caso você não deixe as coisas, da forma como mostrado acima. O serviço sempre irá reportar um erro, no momento que você tentar ver mais dados, além do gráfico. ATENÇÃO então a este detalhe.

Arquivos anexados |
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 14): Nascimento do SIMULADOR (IV) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 14): Nascimento do SIMULADOR (IV)
Neste artigo continuaremos a fase de desenvolvimento do simulador. Mas agora, vamos ver como criar de fato um movimento do tipo RANDOM WALK. Este tipo de movimentação é muito interessante, pois tudo envolvido no mercado de capitais tem como base este tipo de movimentação. Além do mais você vai começar a entender alguns conceitos importantes para quem faz estáticas de mercado.
Ciência de dados e aprendizado de máquina (Parte 11): Classificador Naive Bayes e teoria da probabilidade na negociação Ciência de dados e aprendizado de máquina (Parte 11): Classificador Naive Bayes e teoria da probabilidade na negociação
A negociação com base em probabilidades pode ser comparada a caminhar sobre uma corda bamba - ela requer precisão, equilíbrio e uma compreensão clara do risco envolvido. No mundo do trading, a probabilidade é fundamental. É ela que determina o resultado: sucesso ou fracasso, lucro ou prejuízo. Ao aproveitar as possibilidades da probabilidade, os traders podem tomar decisões mais fundamentadas, gerenciar os riscos de maneira mais eficiente e alcançar seus objetivos financeiros. Não importa se você é um investidor experiente ou um trader iniciante, entender a probabilidade pode ser a chave para desbloquear seu potencial de negociação. Neste artigo, exploraremos o fascinante mundo do trading baseado em probabilidades e mostraremos como levar seu modo de negociar a um nível superior.
Teoria das Categorias em MQL5 (Parte 3) Teoria das Categorias em MQL5 (Parte 3)
A Teoria das Categorias representa um segmento diversificado e em constante expansão da matemática, que até agora está relativamente pouco explorado na comunidade MQL5. Esta sequência de artigos visa elucidar algumas das suas concepções com o intuito de constituir uma biblioteca aberta e potencializar ainda mais o uso deste notável setor na elaboração de estratégias de negociação.
Algoritmos de otimização populacionais: Busca harmônica (Harmony Search, HS) Algoritmos de otimização populacionais: Busca harmônica (Harmony Search, HS)
Hoje, estudaremos e testaremos o algoritmo de otimização mais avançado, a busca harmônica (HS), que é inspirada no processo de procura da harmonia sonora perfeita. Então, qual algoritmo é agora o líder em nossa classificação?