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

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

MetaTrader 5Testador | 22 maio 2023, 14:21
416 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay - Simulação de mercado ( Parte 11 ) - Nascimento do SIMULADOR ( I ), tornamos o no sistema de replay / simulador, capaz de utilizar as barras de 1 minuto. De forma a simular possíveis movimentos de mercado. Se bem que lá você podem ter percebido, que a movimentação não parece ser tão similar ao do mercado. Durante aquele artigo, mostrei os pontos que você precisaria mexer de forma a ter um sistema, ainda mais próximo do que seria de fato encontrado em um mercado real. Entretanto, por mais que você tente e experimente, não irá conseguir, utilizando métodos simples, criar algo que seja similar aos movimentos possíveis e prováveis de um mercado.


Iniciando a implementação

Para fazer o que é preciso, a fim de deixar o sistema um pouco mais complexo. Iremos utilizar a geração de números aleatórios. Desta forma vamos deixar a coisa um pouco menos previsível, ao mesmo tempo que tornamos o sistema de replay / simulador, mais interessante. Seguindo a dica contida na documentação do MQL5, para geração de números aleatórios, teremos que seguir alguns passo, bem simples no começo. Não há motivos para alarde, a coisa realmente é bem simples mesmo. Vejam o que iremos adicionar inicialmente ao código:

void InitSymbolReplay(void)
        {
                Print("************** Serviço Market Replay **************");
                srand(GetTickCount());
                GlobalVariableDel(def_GlobalVariableReplay);
                SymbolSelect(def_SymbolReplay, false);
                CustomSymbolDelete(def_SymbolReplay);
                CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
                CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
                CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
                SymbolSelect(def_SymbolReplay, true);
        }

O que estou fazendo aqui, é seguindo exatamente a dica presente na documentação. Esta pode ser vista ao olhar a função srand, que é a função que inicializa a geração de números pseudo-aleatórios. Como a própria documentação deixa claro, se utilizarmos um valor fixo na chamada, como por exemplo:

srand(5);

Iremos sempre ter a mesma sequência numérica. Assim, você deixará de ter uma geração aleatória, e passará a ter uma sequência "previsível". Observe que coloquei entre aspas, já que a sequência será sempre a mesma. Mas até que todo o loop de geração seja alcançado, você não saberá com certeza o próximo valor. De certa forma, isto pode interessante, caso você deseje criar uma simulação, onde a sequência simulada será sempre a mesma. Por outro lado, este tipo de coisa facilita muito, ou melhor dizendo, impossibilita que você de fato, tenha um bom treinamento ao usar o sistema.

No caso de estar utilizando o simulador, para gerar estudos randômicos, não faz sentido, criar uma quantidade enorme de arquivos diferentes. Podemos criar apenas um único arquivo, e este será usado, para criar toda a aleatoriedade. Por conta este motivo, não irei colocar um valor fixo na chamada a srand. Vou deixar o acaso tomar conta. Mas isto fica a critério de cada um.


Experimentando uma forma de fazer a coisa um pouco mais complexa

A primeira coisa que iremos fazer, será retirar o fato de sabermos que começaremos, buscando a mínima. Saber disto facilita muito. Você apenas espera a nova barra abrir, e vende. Se ela ultrapassar a abertura, você virá a mão, e compra. Isto não é aprendizado. É trapaça.

Nota: Alguns EAs conseguem analisar e perceber este tipo de coisa. Isto quando acontece no testador de estratégia. O fato do EA conseguir notar isto, invalida qualquer teste feito.>

Para fazer isto: Complicar as coisas. Vamos usar um método super simples, ao mesmo tempo que bastante eficiente. Veja o código abaixo.

inline int SimuleBarToTicks(const MqlRates &rate, MqlTick &tick[])
                        {
                                int t0 = 0;
                                long v0, v1, v2, msc;
                                bool b1 = ((rand() & 1) == 1);
                                double p0, p1;
                                                                
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                p0 = (b1 ? rate.low : rate.high);
                                p1 = (b1 ? rate.high : rate.low);
                                Pivot(rate.open, p0, t0, tick);
                                Pivot(p0, p1, t0, tick);
                                Pivot(p1, rate.close, t0, tick, true);
                                v0 = (long)(rate.real_volume / (t0 + 1));
                                v1 = 0;
                                msc = 5;
                                v2 = ((60000 - msc) / (t0 + 1));
                                for (int c0 = 0; c0 <= t0; c0++, v1 += v0)
                                {
                                        tick[c0].volume_real = (v0 * 1.0);
                                        tick[c0].time = rate.time + (datetime)(msc / 1000);
                                        tick[c0].time_msc = msc % 1000;
                                        msc += v2;
                                }
                                tick[t0].volume_real = ((rate.real_volume - v1) * 1.0);
                                
                                return t0;
                        }

Por favor, não fiquem com medo, do que esta função acima esta fazendo. Pois a coisa continua sendo, da mesma forma como era antes. A única mudança, é que agora você não irá saber, se a barra começará buscando a mínima, ou a máxima. O primeiro ponto, é que iremos verificar se o valor gerado aleatoriamente, é um valor par ou impar. Uma vez que sabemos disto, podemos simplesmente, fazer a troca dos valores que irão criar o nosso pivô. Mas observem que ainda assim, o pivô irá ser criado da mesma forma. A única coisa que não saberemos, é se a barra estará subindo, pois já alcançou a mínima, ou descendo, por já ter alcançado a máxima.

Bem isto, já é um começo. Mas vamos fazer outra modificação. Antes de partir para a próxima fase. A modificação que faremos é a seguinte: Na versão acima, temos a formação usualmente de 9 pernas entre a abertura e o fechamento da barra. Mas adicionando pouquíssimo código, iremos fazer com que estas 9 pernas, passem a ser 11 pernas. Como ?!?! Veja como o código deverá ficar no fragmento abaixo:

#define def_NPASS 3
inline int SimuleBarToTicks(const MqlRates &rate, MqlTick &tick[])
                        {
                                int t0 = 0;
                                long v0, v1, v2, msc;
                                bool b1 = ((rand() & 1) == 1);
                                double p0, p1, p2;
                                                                
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                p0 = (b1 ? rate.low : rate.high);
                                p1 = (b1 ? rate.high : rate.low);
                                p2 = floor((rate.high - rate.low) / def_NPASS);
                                Pivot(rate.open, p0, t0, tick);
                                for (int c0 = 1; c0 < def_NPASS; c0++, p0 = (b1 ? p0 + p2 : p0 - p2)) Pivot(p0, (b1 ? p0 + p2 : p0 - p2), t0, tick);
                                Pivot(p0, p1, t0, tick);
                                Pivot(p1, rate.close, t0, tick, true);
                                v0 = (long)(rate.real_volume / (t0 + 1));
                                v1 = 0;
                                msc = 5;
                                v2 = ((60000 - msc) / (t0 + 1));
                                for (int c0 = 0; c0 <= t0; c0++, v1 += v0)
                                {
                                        tick[c0].volume_real = (v0 * 1.0);
                                        tick[c0].time = rate.time + (datetime)(msc / 1000);
                                        tick[c0].time_msc = msc % 1000;
                                        msc += v2;
                                }
                                tick[t0].volume_real = ((rate.real_volume - v1) * 1.0);
                                
                                return t0;
                        }
#undef def_NPASS

Você pode achar que eles são iguais. Mas na verdade, existe uma diferença muito grande aqui. Apesar de ter sido adicionada, apenas mais uma variável, para indicar um ponto intermediário. Encontrando este ponto, passamos a ter a possibilidade, de adicionar mais 2 pernas. Reparem que para adicionar estas duas pernas, iremos continuar a executar praticamente o mesmo código. Notem que a complexidade que criando na formação da barra, ao criar uma simulação, vai aumentando rapidamente, e não na mesma velocidade que aumentamos o código. Um pequeno detalhe que você pode atendar, é com relação ao fato, de que a definição, não deve ser definida como Zero. Caso isto aconteça, teremos um erro de divisão por zero. Então neste caso, o mínimo que você deverá usar, é o valor 1 na definição. Mas se você definir qualquer valor, entre 1 a um máximo qualquer, poderá adicionar mais pernas. Como normalmente não temos movimentos amplos o suficiente, para que mais pernas sejam de fato criadas, o valor 3 me parece adequado.

Para entender o que aconteceu aqui, veja as seguintes imagens.

Antes de adicionar novas pernas.


Apesar das coisas darem certo, da forma como estava sendo feito. Quando utilizamos a versão, que nos permite dividir a amplitude, em faixas teremos o seguinte cenário:

Figura 02

Depois da mudança onde passamos a dividir a amplitude da barra por 3


Vejam que melhorou um pouco a complexidade. Mas não notei nenhuma grande vantagem, em fazer uma divisão por mais de 3 regiões. Portanto apesar da coisa, já ter ficado bastante interessante, o sistema não é gera tanta complexidade, quanto deveria. Temos que passar para uma outra abordagem. Isto não irá fazer, com que o código exploda na sua frente, em termos de complexidade matemática. A ideia, é de fato fazer, um aumento exponencial da complexidade, criando o mínimo de complexidade no código.

Para de fato fazer isto, irei utilizar uma abordagem totalmente diferente. Mas antes vamos entender uma coisa, que merece ser explicada. Assim você poderá realmente compreender o motivo da mudança, na forma de tentar solucionar o problema.

Se você prestou atenção as modificações feitas na fase anterior, deve ter notado no último código, uma coisa curiosa. Durante um momento, teremos todo o corpo da barra sobre nosso poder, podendo fazer o que quisermos com ela. E diferente dos demais momentos, em que teremos um movimento relativamente direcional, seja da abertura para a máxima ou mínima, seja no momento que estamos encerrando a barra, saindo da máxima ou mínima para o preço de fechamento. No momento em que temos todo o corpo da barra para trabalhar, estamos executando muito pouco trabalho dentro dele.  Por mais que tentemos, sempre ficamos presos a mesma situação. Mas se você reparar, irá notar que temos sempre 2 valores, que podem ser trabalhados. Um ponto de inicio e um ponto final. E por que estou chamando atenção para este ponto ?!?! Pense um pouco: Temos 60 mil milissegundos, para criar a barra de 1 minuto, se deixarmos 5 milissegundos de folga, no inicio da barra, ainda assim teremos um tempo considerável. Se fizermos alguns cálculos simples, notaremos que estamos deixando muito tempo sendo desperdiçado, e que poderia ser usado de forma a tornar a simulação da barra extremamente mais complexa.

Então podemos pensar em uma possível solução: Se deixarmos 1 segundo livre, para que o preço saia do ponto de abertura, e vá para a máxima ou mínima. E igualmente deixarmos 1 segundo, para que o preço saia de onde estiver, e vá para o ponto de fechamento. Teremos 58 segundos, para criar a complexidade que desejarmos. Porém preste atenção, ao que foi dito, a respeito do ultimo segundo: Que o preço saia de onde estiver, e vá para o ponto de fechamento. É importante que você perceba, e entenda exatamente o que foi dito. Não importa o que esteja acontecendo durante a maior parte do tempo, deveremos sempre reservar um período de tempo, para que o preço alcance no final o seu ponto de fechamento.

De uma forma notória, você somente irá perceber um movimento, que aconteça em um tempo superior, a pouco mais de 33 milissegundos ou 30 Hz. Então se fizermos com que cada tick, tenha um tempo de no máximo 30 milissegundos, você irá achar que o movimento, estará bastante similar ao encontrado em um ativo. Um detalhe importante: esta noção é muito relativa, já que tem pessoas que acham difícil operar um ativo, que se move muito rapidamente, por conta de ele ter uma volatilidade relativamente alta.

Por estes motivos, é que um sistema de replay / simulação, não deve de fato ser considerado como um bom treinamento. Salvo o fato de você, realmente utilizar um arquivo que contem ticks reais negociados. Quando se faz a simulação de tais ticks, pode acontecer, de você vim a ter a falsa sensação, de que todas as faixas de preços serão visitadas. Este sistema até o momento, não permite que barras de 1 minuto, sejam simuladas de forma a gerar gaps, apesar de que em um mercado real, tais gaps de fato aconteçam, em momentos bastante específicos. Este são momentos muito perigosos, para se abrir ou fechar uma operação, pois a probabilidade de que a ordem seja executada fora do preço desejado, é muito grande, e as chances de que ela simplesmente venha a ser pulada, também é enorme, dado o fato de que a volatilidade, pode ser muito forte, fazendo com que as coisas funcionem de uma forma bastante inesperada.

Acredito que você possa estar pensando, que irei utilizar um método, de forma a gerar sempre uma quantidade mínima de ticks. Mas não irei usar esta abordagem agora. Mas você deve se lembrar do seguinte: Não é de forma alguma, possível recriar os reais movimentos do mercado, através de uma simulação. Tudo que podemos fazer, é estimar quais seriam os possíveis movimentos. Mas antes de prosseguirmos, precisamos focar em corrigir alguns problemas específicos. Vamos começar com um tópico um pouco mais complicado. Mas que irá fornecer as bases para nosso simulador.


Se não existem ticks por que o serviço está ativo ?!?!

Apesar de tudo, e de toda a complicação que teremos que resolver. Precisamos antes de realmente passarmos para algo próximo da realidade, corrigir alguns problemas que venho procrastinando a algum tempo, em de fato resolver. O primeiro destes problemas, é que quando iniciamos o sistema, sem que nenhuma barra previa tenha sido carregada, ficamos sem poder acessar o indicador de controle. Esta falha vem acompanhando o sistema, já a algum tempo. Mas como em todos os momentos anteriores, sempre existia a presença de barras previas, fui deixando de lado esta correção, que o sistema tinha que sofrer. Mas agora vamos corrigir isto. Para resolver este o problema, teremos que fazer algumas pequenas adições, em um ponto bastante especifico do nosso sistema. Isto para facilitar ao máximo as coisas, veja abaixo o que iremos fazer:

                bool SetSymbolReplay(const string szFileConfig)
                        {
#define macroERROR(MSG) { FileClose(file); MessageBox((MSG != "" ? MSG : StringFormat("Ocorreu um erro na linha %d", iLine)), "Market Replay", MB_OK); return false; }
                                int     file,
                                        iLine;
                                string  szInfo;
                                char    iStage;
                                bool    bBarPrev;
                                MqlRates rate[1];
                                
                                if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
                                {
                                        MessageBox("Falha na abertura do\narquivo de configuração.", "Market Replay", MB_OK);
                                        return false;
                                }
                                Print("Carregando dados para replay. Aguarde....");
                                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                                m_Ticks.nRate = -1;
                                m_Ticks.Rate[0].time = 0;
                                iStage = 0;
                                iLine = 1;
                                bBarPrev = false;
                                while ((!FileIsEnding(file)) && (!_StopFlag))
                                {
                                        switch (GetDefinition(FileReadString(file), szInfo))
                                        {
                                                case Transcription_DEFINE:
                                                        if (szInfo == def_STR_FilesBar) iStage = 1; else
                                                        if (szInfo == def_STR_FilesTicks) iStage = 2; else
                                                        if (szInfo == def_STR_TicksToBars) iStage = 3; else
                                                        if (szInfo == def_STR_BarsToTicks) iStage = 4; else
                                                        if (szInfo == def_STR_ConfigSymbol) iStage = 5; else
                                                                macroERROR(StringFormat("%s não é algo reconhecido dentro do sistema\nna linha %d.", szInfo, iLine));
                                                        break;
                                                case Transcription_INFO:
                                                        if (szInfo != "") switch (iStage)
                                                        {
                                                                case 0:
                                                                        macroERROR(StringFormat("Comando não reconhecido na linha %d\ndo arquivo de configuração.", iLine));
                                                                        break;
                                                                case 1:
                                                                        if (!LoadPrevBars(szInfo)) macroERROR("");
                                                                        bBarPrev = true;
                                                                        break;
                                                                case 2:
                                                                        if (!LoadTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                                case 3:
                                                                        if (!LoadTicksReplay(szInfo, false)) macroERROR("");
                                                                        bBarPrev = true;
                                                                        break;
                                                                case 4:
                                                                        if (!LoadBarsToTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                                case 5:
                                                                        if (!Configs(szInfo)) macroERROR("");
                                                                        break;
                                                        }
                                                        break;
                                        };
                                        iLine++;
                                }
                                FileClose(file);
                                if (m_Ticks.nTicks <= 0)
                                {
                                        MessageBox("Não existem ticks para serem usados.\nServiço sendo encerrado...", "Market Replay", MB_OK);
                                        return false;
                                }
                                if (!bBarPrev)
                                {
                                        rate[0].close = rate[0].open =  rate[0].high = rate[0].low = m_Ticks.Info[0].last;
                                        rate[0].tick_volume = 0;
                                        rate[0].real_volume = 0;
                                        rate[0].time = m_Ticks.Info[0].time - 60;
                                        CustomRatesUpdate(def_SymbolReplay, rate, 1);
                                }
                                
                                return (!_StopFlag);
#undef macroERROR
                        }

A primeira coisa que faremos será definir duas novas variáveis para uso local. Então fazemos a inicialização como um valor falso, indicado que não temos nenhuma barra previa carregada. Agora se em algum ponto tivermos o carregamento de alguma barra previa, esta variável irá indicar um valor verdadeiro. Assim o sistema irá saber que temos barras previas carregadas. Com isto resolvemos parte do nosso primeiro problema. Mas ainda precisamos testar, se foi carregado algum arquivo, que irá montar os ticks a serem usados. Se não existir nenhum tick presente, não faz sentido termos o serviço sendo executado. Desta forma o serviço será finalizado. Agora se existirem ticks, vamos verificar se foi carregado algum tipo de barra previa. Caso isto não tenha acontecido, iremos inicializar uma barra " vazia ". Sem que isto seja feito, ficaremos impedidos de ter acesso ao indicador de controle. Mesmo que o serviço esteja indicado, que está disponível para uso.

No entanto, efetuando estas correções vistas acima, tudo será resolvido. 


Implementando o VOLUME DE TICKS

Na sequência de coisas as serem corrigidas, a próxima que iremos corrigir, e que ainda não estava sendo implementada, é com relação ao sistema que indica o volume de ticks negociados. Muita gente gosta de ter no gráfico, o indicador de volume, e até o momento, o único volume realmente implementado, estava sendo o do volume real. Ou seja, aquele volume em termos de numero de contratos executados. Mas da mesma forma, também é importante termos o volume de ticks, mas você sabe a diferença entre eles ?!?! Observem a imagem abaixo:

Nela podemos ver dois valores de volume. Um que é o volume de ticks, e outro que é o volume ( neste caso o volume real ). Mas olhando esta imagem, você conseguiria me dizer, qual é a diferença entre o volume real e o volume de ticks ?!?! Se você não sabe a diferença, chegou a hora de aprender definitivamente a diferença, entre estes dois volumes.

O VOLUME ou VOLUME REAL, é de fato, a quantidade de contratos negociados, naquele momento especifico. Ele será sempre um múltiplo de um valor que depende do ativo em questão. Alguns ativos, não permitem você operar valores menores que 5 por exemplo. Já outros aceitam valores fracionados. Não tente entender por que isto é possível, apenas saiba que é possível operar valores fracionados. Pois bem, este valor, é um valor simples de entender, e talvez por isto muitos utilizam ele. Agora se você pegar este valor, do VOLUME REAL, e multiplicar ele pelo valor mínimo de cada contrato, você irá obter um outro valor, o chamado VOLUME FINANCEIRO. O MetaTrader 5, não fornece este valor diretamente, mas como você viu, ele é simples de ser obtido. Desta forma o servidor de negociação, entende que não precisa informar este tal VOLUME FINANCEIRO para os terminais de negociação. Fica a cargo dos programadores, ou usuários da plataforma, implementar tal calculo.

Agora o VOLUME DE TICKS é um volume totalmente diferente. Ele é fornecido e apenas no conteúdo das barras, por um motivo simples: Você não conseguirá saber, o que aconteceu durante a negociação, apenas observando o volume real. Precisamos de um outro dado, e este é o volume de ticks. Mas por que o volume de ticks esta presente quando requisitamos as barras, mas não está presente, quando fazemos a requisição dos ticks ?!?! E que volume é aquele, que aparece quando requisitamos os ticks ?!?! Se você nunca prestou atenção a isto, ou ainda não viu isto. Veja a imagem abaixo:

Os valores informados no campo VOLUME não e repito NÃO, representam o volume de ticks. Este valor é de fato o VOLUME REAL. Mas então como posso saber o volume de ticks, se ele não é informado, quando faço o requerimento buscando os ticks ?!?! Ele somente aparece quando faço o requerimento de barras. O fato é que, da mesma forma, que o servidor subentende, que não precisa informar o VOLUME FINANCEIRO, ele também subentende, que ao fornecer os ticks negociados, você será capaz de calcular o VOLUME DE TICKS. Diferente do que aconteceria, se o requerimento solicitado fosse o de barras. Onde não temos o acesso aos ticks reais negociados.

Você ainda não entendeu ?!?! Quando você tem os dados de ticks reais negociados, você pode calcular o volume de ticks. Mas como ?!?! Existe alguma formula secreta ?!?! Algo místico ou esotérico para se fazer isto ?!?! Pois sempre que tento, não consigo fazer os valores coincidirem. Calma meu caro amigo leitor. Não, não existe nenhuma formula mágica. O que acontece, é que talvez você não entenda, é que o VOLUME DE TICKS realmente representa. No artigo passado, e neste, nos utilizamos um método para simular o movimento dentro da barra de 1 minuto. Apesar de o movimento fazer com que todos os preços sejam visitados. O volume de ticks, que estamos de fato criando, é muito abaixo do volume de ticks, que esta sendo informado na barra de 1 minuto.

Mas como assim ?!?! Calma. Isto será melhor entendido no próximo artigo. Onde iremos de fato simular o mesmo volume de ticks. Mas falando isto, acredito que você tenha percebido do que se trata o tal volume de ticks. O volume de ticks, é o numero de negócios, que de fato aconteceram dentro daquela barra. Da forma como estamos fazendo, este volume fica por volta de 150 em média. Quando na verdade, ele muitas das vezes, gira próximo dos 12890 em média.

Mas você pode estar pensando: Como então posso calcular este volume de ticks ?!?! Bem, é muito simples fazer isto. Vamos ver com o nosso sistema consegue fazer este calculo. Pois para entender, é preciso você de fato ver o calculo acontecendo.

Atualmente este calculo, é efetuado em dois locais por motivos diferentes. O primeiro local, é visto logo abaixo:

inline bool BuiderBar1Min(MqlRates &rate, const MqlTick &tick)
                        {
                                if (rate.time != macroRemoveSec(tick.time))
                                {
                                        rate.real_volume = 0;
                                        rate.tick_volume = 0;
                                        rate.time = macroRemoveSec(tick.time);
                                        rate.open = rate.low = rate.high = rate.close = tick.last;
                
                                        return true;
                                }
                                rate.close = tick.last;
                                rate.high = (rate.close > rate.high ? rate.close : rate.high);
                                rate.low = (rate.close < rate.low ? rate.close : rate.low);
                                rate.real_volume += (long) tick.volume_real;
                                rate.tick_volume += (tick.last > 0 ? 1 : 0);

                                return false;
                        }

Exatamente neste ponto, fazemos o calculo do volume de ticks, que estarão presentes na barra. O segundo ponto, é visto logo a seguir:

inline int Event_OnTime(void)
                        {
                                bool    bNew;
                                int     mili, iPos;
                                u_Interprocess Info;
                                static MqlRates Rate[1];
                                static datetime _dt = 0;
                                datetime tmpDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                
                                if (m_ReplayCount >= m_Ticks.nTicks) return -1;
                                if (bNew = (_dt != tmpDT))
                                {
                                        _dt = tmpDT;
                                        Rate[0].real_volume = 0;
                                        Rate[0].tick_volume = 0;
                                }
                                mili = (int) m_Ticks.Info[m_ReplayCount].time_msc;
                                do
                                {
                                        while (mili == m_Ticks.Info[m_ReplayCount].time_msc)
                                        {
                                                Rate[0].close = m_Ticks.Info[m_ReplayCount].last;
                                                Rate[0].open = (bNew ? Rate[0].close : Rate[0].open);
                                                Rate[0].high = (bNew || (Rate[0].close > Rate[0].high) ? Rate[0].close : Rate[0].high);
                                                Rate[0].low = (bNew || (Rate[0].close < Rate[0].low) ? Rate[0].close : Rate[0].low);
                                                Rate[0].real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                                Rate[0].tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                                bNew = false;
                                                m_ReplayCount++;
                                        }
                                        mili++;
                                }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
                                Rate[0].time = _dt;
                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                if (Info.s_Infos.iPosShift != iPos)
                                {
                                        Info.s_Infos.iPosShift = (ushort) iPos;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                }
                                return (int)(m_Ticks.Info[m_ReplayCount].time_msc < mili ? m_Ticks.Info[m_ReplayCount].time_msc + (1000 - mili) : m_Ticks.Info[m_ReplayCount].time_msc - mili);
                        }

Neste ponto, fazemos a mesma coisa, calculamos o volume de ticks. Mas é só isto ?!?! Sim. O volume de ticks, é calculado desta forma, ou seja apenas os ticks que de fato informam a ocorrência de um negocio é que serão computados. Um para cada negocio efetivado. Ou seja os ticks cujo o flag BID ou ASK, estiverem ligados não participam do calculo, apenas os que tiverem o flag SELL ou BUY, irão ser calculados. Mas por conta que estes flags somente estarão ligados quando o valor do preço, ou o volume real for maior que zero, não fazemos o teste dos flags, pois o teste seria desnecessário.

NOTA: Quando formos tratar sobre o FOREX iremos mudar isto. Mas isto será tratado em artigos específicos apenas ao FOREX.

Então a partir de agora, o sistema de replay / simulação, contará com os volume de ticks. Mas tem um detalhe: No atual momento, ao usarmos uma barra para simular os ticks, o volume será sempre diferente, do que realmente é informado no arquivo de barras. No próximo artigo, iremos corrigir isto. Mas isto, é algo que merece um artigo a parte. Para que eu consiga explicar, com calma o que teremos que fazer.


Ajustando o ponto de controle

O próximo problema a ser corrigido, se bem que não é de fato um problema. É fazer com que o sistema, possa saber, o que cada unidade de posicionamento, representa. O problema é que este sistema, até o presente momento, estava utilizando uma forma muito pouco adequada, de fazer o posicionamento indicado pelo usuário. Então quando se torna possível utilizar, e principalmente desejado pelo usuário, o uso de mais de um arquivo, para obter os dados dos tickets, a coisa fica totalmente inviável de ser mantida pelo sistema anterior. Passamos assim a ter problemas, com relação a conversão, entre o que é posto no indicador de controle, e o que é produzido pelo replay. 

Para resolver isto, será preciso remover uma linha especifica no sistema de carregamento.

                bool LoadTicksReplay(const string szFileNameCSV, const bool ToReplay = true)
                        {
                                int     file,
                                        old,
                                        MemNRates,
                                        MemNTicks;
                                string  szInfo = "";
                                MqlTick tick;
                                MqlRates rate,
                                        RatesLocal[];
                                
                                MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
                                MemNTicks = m_Ticks.nTicks;
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(m_Ticks.Rate, def_BarsDiary, def_BarsDiary);
                                        old = m_Ticks.nTicks;
                                        for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                                        if (szInfo != def_Header_Ticks)
                                        {
                                                Print("Arquivo ", szFileNameCSV, ".csv não é um arquivo de tick negociados.");
                                                return false;
                                        }
                                        Print("Carregando ticks de replay. Aguarde...");
                                        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                                        {
                                                ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                                                szInfo = FileReadString(file) + " " + FileReadString(file);
                                                tick.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                                tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                tick.bid = StringToDouble(FileReadString(file));
                                                tick.ask = StringToDouble(FileReadString(file));
                                                tick.last = StringToDouble(FileReadString(file));
                                                tick.volume_real = StringToDouble(FileReadString(file));
                                                tick.flags = (uchar)StringToInteger(FileReadString(file));
                                                if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                                        m_Ticks.Info[old].volume_real += tick.volume_real;
                                                else
                                                {                                                       
                                                        m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                        if (tick.volume_real > 0.0)
                                                        {
                                                                m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                                                rate.spread = (ToReplay ? m_Ticks.nTicks : 0);
                                                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                                                                m_Ticks.nTicks++;
                                                        }
                                                        old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                                                }
                                        }
                                        if ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                                                FileClose(file);
                                                return false;
                                        }
                                        FileClose(file);
                                }else
                                {
                                        Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                                        return false;
                                }
                                if ((!ToReplay) && (!_StopFlag))
                                {
                                        ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
                                        ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
                                        CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
                                        m_dtPrevLoading = m_Ticks.Rate[m_Ticks.nRate].time;
                                        m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
                                        m_Ticks.nTicks = MemNTicks;
                                        ArrayFree(RatesLocal);
                                }
                                return (!_StopFlag);
                        };

O fato de fazermos esta remoção, já irá liberar a variável spread, para ser devidamente ajustada em um outro momento. Não iremos fazer isto agora, não neste artigo, já que ainda não existe tão necessidade. Mas uma vez feito isto, temos que corrigir o sistema de conversão. Pois a partir deste momento, o sistema de controle de posição, estará sempre indicado um ponto não valido. Melhor dizendo, diferente do que realmente é o desejo do usuário.

Para fazer a correta conversão, precisaremos mudar um procedimento bem especifico. Este é visto logo abaixo:

                long AdjustPositionReplay(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 0;
                                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);) Event_OnTime();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);

                                return Event_OnTime();
                        }

A versão de conversão apresentada acima, é bem diferente das versões encontradas nos artigos anteriores. Isto por que ela de fato faz a conversão dos valores percentuais, configurados pelo usuário, no indicador de controle e o sistema de posicionamento, de forma que não importa como, e de que maneira os tickets estão organizados. Este procedimento irá procurar o ponto correto, e inicializar a apresentação dos dados encontrados nos tickets a partir deste ponto.

Para fazer isto de forma adequada, primeiramente efetuamos um calculo, para saber em que ponto esta o local desejado em termos percentuais. Esta posição é bastante importante para nos. Se o valor for menor, significa que deveremos retornar para algum ponto. Então deletamos as informações, até próximo daquele ponto. Normalmente sempre será removido alguns dados extras, mas faz parte, e iremos repor estes dados depois. Pode ser que estejamos retornado no inicio da serie de dados. Mas se este não for o caso, iremos fazer com que o contador regrida até um ponto próximo do valor percentual. Esta linha especificamente, corrige o problema de sempre voltarmos mais do que de fato queríamos, sem ela a barra anterior a posição irá ficar errada. O sistema de recuo, é mais complicado do que o sistema de avanço. No caso do avanço, simplesmente verificamos se o usuário deseja ou não ver as barras sendo criadas. Caso deseje, elas serão apresentadas, caso contrário, o sistema avança até o ponto especificado pelo valor percentual. Em grande parte das vezes, precisaremos fazer um ajuste fino entre o valor percentual e a real posição. Este sempre será apresentado, mas como a coisa nestes casos, é feita de forma bastante rápida, no caso de já termos aproximado o valor real do contador, do valor percentual, a transição será praticamente instantânea. Mas se o valor estiver longe, teremos uma pequena animação, mostrando as barras sendo construídas.


Considerações finais para este artigo.

Apesar do sistema parecer bem mais adequado de ser utilizado. Você notará algumas coisas estranha ao executa-lo no modo de visualização de construção das barras. Tais coisas estranhas, podem ser vistas no vídeo abaixo. Mas por conta de que elas exigem uma mudança, em alguns pontos do código, e não queria fazer vocês acharem, que as coisas aparecem assim sem mais nem menos. Decidi deixar a " falha ". Mas o principal motivo talvez seja, por conta que no próximo artigo, irei mostrar como fazer o sistema ser mais adequado, como simulador. Não gostaria que ninguém viesse, por ventura me questionar, o por que de ter programado o simulador conforme será visto no próximo artigo.

Feito esta ressalva, veja o vídeo. Entenda que estou ciente do que esta acontecendo.



No anexo temos os arquivos utilizados aqui. Mas você também contará com um arquivo bônus, que tanto mostra as barras de 1 minuto quanto os tickets negociados em um mesmo dia. Execute ambas configurações, e veja o resultado. Mas principalmente entenda o que esta acontecendo no gráfico.

Arquivos anexados |
Teste e otimização de estratégias para opções binárias no MetaTrader 5 Teste e otimização de estratégias para opções binárias no MetaTrader 5
Testamos e otimizamos estratégias de opções binárias no MetaTrader 5.
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 11): Nascimento do SIMULADOR (I) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 11): Nascimento do SIMULADOR (I)
Para poder usar dados que formam barras, precisamos abandonar o replay e começar a desenvolver um simulador. Não sabemos como ela foi criada. Estaremos utilizando as barras de 1 minuto, justamente pelo motivo, de elas nos darem, um nível de complexidade mínimo.
Como escolher um Expert Advisor: Vinte caraterísticas de um robô de baixa qualidade Como escolher um Expert Advisor: Vinte caraterísticas de um robô de baixa qualidade
Neste artigo, iremos responder à pergunta de como escolher o Expert Advisor correto. Quais são os mais adequados para o nosso portfólio e como podemos filtrar a maioria dos robôs de negociação disponíveis no mercado? Este artigo apresenta vinte caraterísticas evidentes de um EA de baixa qualidade. Ele ajudará você a tomar decisões mais informadas e criar uma coleção de EAs lucrativos.
Receitas MQL5 — Banco de dados de eventos macroeconômicos Receitas MQL5 — Banco de dados de eventos macroeconômicos
Este artigo explora como trabalhar com bancos de dados baseados no mecanismo SQLite. Com o objetivo de oferecer conveniência e utilizar eficientemente os princípios da OOP, foi criada a classe CDatabase. Essa classe é responsável pela criação e gerenciamento de um banco de dados de eventos macroeconômicos. Além disso, são apresentados exemplos de como utilizar diferentes métodos da classe CDatabase.