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

Desenvolvendo um sistema de Replay — Simulação de mercado (Parte 01): Primeiros experimentos (I)

MetaTrader 5Testador | 3 março 2023, 09:55
1 889 0
Daniel Jose
Daniel Jose

Introdução

Durante a serie de artigos, Desenvolvendo um EA do zero, houve vários momentos, que percebi ser possível fazer muito mais, do que estava sendo feito programando MQL5. O momento de fato, que me fez pensar, foi quando desenvolvi um sistema de Times & Trade gráfico, naquele artigo, comecei a me questionar, se era possível ir além do que estava sendo construído.

Bem, uma das coisas que pessoas que muitos iniciantes, se queixarem é a falta de alguns recursos bem específicos na plataforma MetaTrader 5. Entre estes recursos está um que ao meu ver faz todo sentido. Um sistema de simulação ou replay de mercado. É bem adequado novos participantes do mercado, terem um seu poder, algum tipo de mecanismo ou ferramenta, que lhes permita, testar, verificar, ou mesmo estudar ativos, ou algum ativo especifico. Entre estas ferramentas uma é justamente o sistema de replay e simulação.

O MetaTrader 5, não conta com tal recurso, sendo distribuído como parte integrante em uma instalação padrão. Ficando assim a cargo de cada um providenciar meios de efetuar tais estudos. No entanto, o MetaTrader 5, é uma plataforma bastante adequada para efetuar diversos tipos de trabalho. Mas para que você consiga de fato usá-la com o seu máximo potencial, é preciso que você tenha uma boa experiência em programação. E não estou falando necessariamente em saber programar na linguagem MQL5. Estou falando em programação, em termos gerais.

Se você não tem muita experiência na área, irá ficar apenas no básico do básico. Ficando totalmente desprovido de meios mais adequados, ou melhores para conseguir de fato se desenvolver no mercado. Isto falando em termos de se tornar um operador excepcional. Então a menos que você tenha um bom nível de conhecimento sobre programação, você não conseguirá de fato usar tudo que o MetaTrader 5 tem a oferecer. E mesmo programadores com uma boa experiência, ainda assim talvez não venham a se interessar em produzir determinados tipos de programas, ou aplicações para o MetaTrader 5.

Mas o fato é que pouca gente, irá conseguir fazer um sistema, que seja viável de ser usado por alguém que esta iniciando. Até existem algumas propostas gratuitas para se criar um sistema de replay de mercado. Mas ao meu ver, elas de fato não exploram os recursos que o MQL5 fornece. Elas muitas das vezes, exigem o uso de DLLs externas, e sempre com o código fechado. O que ao meu ver, não é uma boa ideia. Ainda mais pelo fato de você não saber de fato a origem, ou o conteúdo presente em tais DLLs, o que coloca em risco todo o sistema.

Nesta serie aqui, ainda não sei quantos artigos serão necessários de fato para se desenvolver um replay funcional. Irei mostrar como você pode criar um código, que permita fazer o tal replay. Mas não é somente isto, vamos em paralelo, desenvolver um sistema que permita simular qualquer situação de mercado. Não importando o quanto ela seja estranha ou rara.

Um fato curioso, é que muita gente, quando fala em trade quanti, na verdade não tem a real ideia do que esta falando, mesmo por que não existe uma forma prática de fazer estudos envolvendo este tipo de coisa. Mas se você entender os conceitos que irei passar aqui nesta serie, você conseguirá transformar o MetaTrader 5, em um sistema de analise quanti, ou seja, as possibilidades irão se estender muito além do que de fato irei expor aqui.

Para não ficar muito repetitivo e cansativo, irei tratar o sistema apenas como replay. Apesar do correto, seria chamar de replay / simulador, já que além de poder analisar movimentos passados, também poderemos desenvolver os nossos próprios movimentos para estudo. Então não encare este sistema apenas como um replay de mercado, mas como um simulador de mercado, ou mesmo um "Game" de mercado, já que a coisa irá envolver muito de programação de jogos também. Este tipo de programação envolvida em jogos, será necessária em alguns pontos. Mas iremos ver isto com calma, conforme o sistema vai sendo desenvolvido.


Planejamento

A primeira coisa a se fazer, é procurar saber com o que estaremos lidando. Pode parecer estranho falar isto, mas será que você sabe realmente o que deseja fazer, ao usar um sistema de replay / simulador ?!?!

Bem, existem algumas questões bastante complicadas em se fazer um replay de mercado. Uma delas e talvez a principal, seja o tempo de vida dos ativos, e das informações sobre eles. Se você não entendeu, quero que você perceba o seguinte: O sistema de negociação grava todas a informações, tick a tick, de cada um dos negócios executados, e isto para todos os ativos, um por um. Pois bem, você tem noção, da quantidade de dados, que isto representa ?!?! Você já parou para pensar no quanto isto irá lhe tomar em termos de tempo para organizar e catalogar cada um dos ativos ?!?!

Pois bem, alguns ativos típicos, em seus movimentos diários, podem conter perto de 80 MBytes de dados. Podendo em alguns casos, ter um pouco mais ou um pouco menos. Isto apenas, 1 único ativo, em um único dia. Agora pensem em ter que armazenar, este mesmo ativo, durante 1 mês .... 1 ano .... 10 anos ... ou quem sabe, para sempre. Pensem na quantidade enorme de dados que terão que ser armazenados, e depois encontrados. Pois se você os jogar simplesmente no disco, logo, logo não conseguirá achar mais nada. Existe uma frase que identifica bem isto. Ela diz:

Quanto maior o espaço, maior é a bagunça.

Para facilitar as coisas, depois de um tempo, os dados são comprimidos em barras. Estas são em um tempo de 1 minuto, e conterão o mínimo de informações necessárias, para que possamos fazer algum tipo de estudo. Mas quando aquela barra, for de fato criada, os ticks que a construíram irão desaparecer, não sendo mais acessíveis. Desta forma, não será mais possível fazer um replay de mercado real. A partir deste momento, o que teremos é um simulador. Já que o movimento real, não esta mais acessível, e teremos que criar alguma forma de simular ele, com base em algum movimento plausível de mercado.

Para entender, veja as figuras abaixo:

                       

A sequencia acima, mostra como os dados vão se perder, ao longo do tempo. A imagem a esquerda, mostra os valores reais dos tick. Quando a compactação acontece, o que teremos é a imagem do centro. Mas com base nela, não conseguiremos obter os valores da esquerda, é IMPOSSÍVEL fazer isto. Mas podemos criar algo parecido com a imagem da direita, onde estaremos simulando os movimentos do mercado, com base no conhecimento sobre como o mercado normalmente se move. Mas vejam que não se parece em nada, com a imagem original.

É preciso se ter isto em mente ao se trabalhar com um replay. Se você não tem os dados originais, não poderá fazer um estudo real. Você apenas poderá fazer um estudo estatístico, que pode ou não se próximo, e apenas próximo, de um possível movimento real. Lembre-se sempre disto. Durante toda esta sequencia iremos explorar, e irei explicar mais sobre como fazer isto. Mas isto vai se dar aos pouco.

Com isto, vamos passar para a parte realmente desafiadora. Implementar um sistema de replay.


Implementação

Esta parte apesar de parecer simples, é bastante complicada. Já que existem problemas e limitações envolvidas em termos de hardware. Além de outros problemas envolvidos na parte de software. Então temos que tentar criar algo, pelo menos o mais básico, e que faça algo, de alguma forma funcional e aceitável. Pois nada irá adiantar tentar fazer algo mais complexo, quando as bases são fracas e estão sempre a desmoronar.

Nosso principal e maior problema, por incrível que pareça, é o tempo. Pois é isto mesmo, o tempo é um grande, para não dizer imenso, problema a ser vencido.

No anexo, irei sempre deixar ( nesta primeira fase ) pelo menos 2 conjuntos REAIS de ticks, de algum ativo, em qualquer tempo passado. Estes não podem mais ser adquiridos, por conta que os dados se perderam, não podendo assim ser baixados. Isto servirá para que você possa estudar, cada detalhe envolvido. Mas mesmo assim, você pode conseguir criar a sua própria base de ticks REAIS.


Criando a sua própria base de dados

Felizmente o MetaTrader 5, nos fornece meios de fazer isto. É algo bastante simples de ser feito, mas você terá sempre que estar fazendo, caso contrário poderá perder os valores e depois não será mais possível fazer isto.

Para fazer isto, abra a plataforma MetaTrader 5, e por padrão, as teclas de atalho serão CTRL + U. Irá surgir uma tela na sua frente. Nesta você deverá informar qual o ativo, e as datas de início e fim da captura dos dados, pressionar o botão para solicitar os dados e aguardar uns instantes. O servidor irá retornar todos os dados para você, feito isto, você simplesmente irá exportar estes dados, e os guardar com cuidado, pois eles são muito valiosos.

A tela que você usará para fazer a captura, é mostrada abaixo.

Apesar de se poder criar um programa para fazer isto, acho melhor fazer isto de forma manual. Tem coisas que não dá para confiar cegamente, temos que de fato ver o que esta sendo feito, caso contrário, não teremos a devida confiança no que estará sendo usado.

Esta é a parte fácil, e acreditem, é a parte mais fácil de todo o sistema que irei mostrar como fazer. A partir de agora, a coisa irá ficar muito, mas muito mais complicada.


Primeiro teste de replay

Alguns podem pensar que isto será uma tarefa simples. Esta ideia logo cairá por terra. Outros podem se perguntar : Por que você não usa o testador de estratégias do MetaTrader 5 para fazer o Replay ??? O motivo é que ele não nos permite de fato fazer um replay, como se você estivesse operando o mercado, existem uma serie de limitações, e dificuldades em se fazer um replay pelo testador, e no final, não teríamos uma perfeita imersão no replay, como se você estivesse de fato operando o mercado.

Teremos grandes desafios, mas precisamos dar os primeiros passos para começar esta longa caminhada. Pois vamos começar, fazendo uma implementação bem simples, para isto precisamos de um evento OnTime, que irá gerar o fluxo de dados para criar as barras ( CandleSticks ). Este evento é fornecido a Expert Advisors e a indicadores, mas não devemos usar indicadores neste caso, já que se ocorrer um travamento, irá comprometer muito mais do que apenas o sistema de replay. Com isto iniciamos o código da seguinte forma:

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(60);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
}
//+------------------------------------------------------------------+

Muito bem, só que o código em destaque, não é adequado para nosso proposito, já que neste caso, o menor período que podemos usar, é de 1 segundo, e isto é muito tempo, muito tempo mesmo. Já que os eventos de mercado, aparecem em um valor muito menor, temos que descer a casa dos milissegundos, e para isto, teremos que usar uma outra função: EventSetMillisecondTimer, mas temos um problema aqui.


Limitações da função EventSetMillisecondTimer

Vamos ver o que a documentação diz:

"...  Ao trabalhar em modo de tempo real, os eventos de timer são gerados não mais do que uma vez em 10-16 milissegundos, devido a limitações de hardware... "

Isto talvez não seja um problema, mas temos que testar, para verificar o que de fato acontece. Então vamos implementar um código simples para verificar os resultados.

Começaremos com o código do EA, que pode ser visto abaixo na integra:

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
#include "Include\\C_Replay.mqh"
//+------------------------------------------------------------------+
input string user01 = "WINZ21_202110220900_202110221759";       //Arquivo de ticks
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
int OnInit()
{
        Replay.CreateSymbolReplay(user01);
        EventSetMillisecondTimer(20);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
        Replay.Event_OnTime();
}
//+------------------------------------------------------------------+

Observem que teremos um evento OnTime, a cada aproximadamente 20 milissegundos, e isto é indicado pela linha em destaque, no código do EA. Mas você pode achar que isto é muito rápido, mas será mesmo ?!?! Bem, temos que verificar, lembre-se que a documentação, informa que não teremos como descer abaixo de 10 a 16 milissegundos. Então não adianta você colocar um valor de 1 milissegundo, pois o evento não será gerado com este tempo.

Notem que temos apenas duas referencias externas ao código do EA. Então vamos conhecer a classe, na qual estes códigos estão realmente implementados.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_MaxSizeArray 134217727 // 128 Mbytes de posições
#define def_SymbolReplay "Replay"
//+------------------------------------------------------------------+
class C_Replay
{
};

É importante notar, que a classe tem uma definição de 128 MB, que é indicado no ponto em destaque acima. Isto significa que o arquivo contendo os dados referentes a todos os ticks ,não deverá, ter mais que este tamanho. Você pode aumentar este tamanho caso deseje, ou precise, mas pessoalmente, não tive problemas com este valor.

A próxima linha, diz o nome do ativo que será usado como replay. Muito criativo da minha parte, chamar o ativo de REPLAY não é mesmo ?!?! 😂 Bem, mas vamos continuar nossa exploração da classe. O próximo código que merece destaque, é visto abaixo:

void CreateSymbolReplay(const string FileTicksCSV)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\Replay\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);
        LoadFile(FileTicksCSV);
        Print("Executando teste de velocidade.");
}

As duas linhas em destaque, fazem coisas bastante curiosas. Para quem não sabe, a função CustomSymbolCreate, irá criar um ativo personalizado por nos. No caso podemos ajustar diversas coisas, mas já que se trata apenas de um teste, não irei me aprofundar muito neste momento. Já ChartOpen, irá abrir o gráfico do nosso ativo personalizado, que no caso será o replay. Tudo muito legal, mas precisamos carregar nosso replay do arquivo, e isto é feito pela função a seguir.

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Carregando dados para Replay.\nAguarde ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                m_ArrayCount++;
                                        }
                                        FileClose(file);
                                        Print("Carregamento concluido.\nIniciando Replay.");
                                        return;
                                }
                                Print("Falha no acesso ao arquivo de dados de ticks.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

Esta irá carregar todos os dados dos ticks, linha por linha, caso o arquivo, não exista, ou não possa ser acessado, ExpertRemove, irá finalizar o EA.

Todos os dados serão armazenados temporariamente em memória, para agilizar ao máximo o processamento que virá depois. Pois pode ser que você esteja utilizando uma unidade de disco, que com toda a certeza, irá ser mais lenta que a memória do sistema. Então melhor garantir que todos os dados estejam presentes logo de inicio.

Mas existe uma coisa bastante curiosa na rotina acima, que é a função FileReadString. Esta irá ler os dados até encontrar algum tipo de delimitador. Vejam só que curioso, quando olhamos dos dados binários de um arquivo de ticks, que foi gerado pelo MetaTrader 5, e salvo em formato CSV, da forma como foi explicado no inicio deste artigo, temos o seguinte resultado visto abaixo.


A região amarela, é o cabeçario do arquivo, ele nos diz a organização da estrutura interna que estará por vim. A região em verde, é a primeira linha de dados. Agora observe os pontos em azul, eles são os delimitadores presentes nesta modelagem, 0D e 0A, são indicadores de nova linha, e 09 é a tabulação ( TECLA TAB ). Usando a função FileReadString, não preciso acumular dados para testar eles. A própria rotina faz isto para mim. Tudo que tenho que fazer, é converter os dados para o tipo correto. E isto é mão na roda, agora atenção a um fragmento encontrado no código.

if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;

Este fragmento, irá evitar que tenhamos dados desnecessários na nossa matriz de dados, mas por que estou filtrando estes valores ?!?! Por que de fato, eles não terão nenhuma utilidade no nosso replay. Mas caso você deseje usar estes valores, pode deixar eles passarem, mas isto irá fazer com que você depois, tenha que filtrar eles, no momento de criar as barras, então prefiro filtrar eles aqui.

E a última rotina do nosso sistema de testes, é vista logo a seguir:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                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;
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                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);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                m_ReplayCount++;
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print(TimeToString(_dt, TIME_DATE | TIME_SECONDS), " ---- ", TimeToString(TimeLocal(), TIME_DATE | TIME_SECONDS));
                                        _dt = 0;
                                }
                        };
#undef macroGetMin

Este código, irá de fato criar as barras com um período de 1 minuto, que é o mínimo, que a plataforma exige, para poder gerar qualquer outro tempo gráfico. As partes em destaque, de fato não fazem parte o código, mas são uteis, para nos permitir analisar quanto a barra de 1 minuto. Esta de fato sendo criado dentro deste prazo, pois se ela demorar muito mais que 1 minuto para ser criada, teremos que fazer algo a respeito. Mas se ela estiver sendo criada, em menos de 1 minuto, pode ser que o sistema já será viável logo de cara.

Ao fazer este sistema executar, teremos o resultado, que e visto no video abaixo:



Apesar de alguns acharem que o tempo esta muito fora do esperado, ainda podemos fazer algumas melhorias no código, e talvez isto faça alguma diferença.


Melhorando o código

Apesar de imensa defasagem, pode ser, que possamos melhorar um pouco as coisas, e ajudar o sistema a ter um performance um pouco mais próxima do esperado. Mas não acredito muito em milagres, sabendo da limitação da função EventSetMillisecondTimer, e o problema nem é por conta do MQL5, é uma limitação de hardware mesmo. Mas vamos ver se podemos ajudar um pouco o sistema.

Se você observar os dados, verá que existem vários momentos, que o sistema simplesmente não se move, o preço fica parado, ou pode ter acontecido de o Book absorver toda e qualquer agressão, e o preço simplesmente não se move. Isto pode ser observado, na imagem abaixo

Observem que temos duas condições diferentes, uma em que o tempo e o preço não mudou. Isto não indica que o dado esta errado, mas que não houve tempo hábil, para que a medida em milissegundos, de fato fizesse a diferença. E temos um outro tipo de evento, que é quando o preço não se moveu, mas o tempo sim, e no caso passou-se apenas 1 milissegundo. Em ambos os casos, pode ser que ao unirmos as coisas, dê alguma diferença no tempo de criação da barra de 1 minuto. Pois isto evitaria uma chamada extra as rotinas de criação, e cada nanosegundo, que for poupado, pode fazer uma grande diferença no final. Pois as coisas vão se somando, de pouco em pouco se obtém muito.

Para verificar se existirá ou não alguma diferença, precisaremos verificar, o quando de informações esta sendo gerada. Isto é uma questão de estatística, não é algo exato. É algo que um pequeno erro, é aceitável. Mas demorar o tempo que pode ser visto no vídeo, é totalmente inaceitável para uma simulação próxima da realidade.

Para verificar isto, fazemos uma primeira modificação no código:

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {

// ... Código interno ...
                                        FileClose(file);
                                        Print("Carregamento concluido.\nFoi gerados ", m_ArrayCount, " posições de movimento.\nIniciando Replay.");
                                        return;
                                }
                                Print("Falha no acesso ao arquivo de dados de ticks.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

Esta parte adicionada, fará isto para nos, agora vamos ver uma primeira execução oque acontece, isto pode ser visto na imagem abaixo:

Bem, agora já temos algum tipo de parâmetro para checar se as modificações estão ou não ajudando. Se você calcular, irá ver que foram gastos quase 3 minutos, para se gerar 1 minuto, ou seja, o sistema esta muito fora do que é aceitável.

Então vamos implementar uma pequena melhoria no código, e esta pode ser vista abaixo:

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Carregando dados para Replay.\nAguarde ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if (m_ArrayInfoTicks[m_ArrayCount].Last != last)
                                                {
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }else
                                                        vol += m_ArrayInfoTicks[m_ArrayCount].Vol;
                                        }
                                        FileClose(file);
                                        Print("Carregamento concluido.\nFoi gerados ", m_ArrayCount, " posições de movimento.\nIniciando Replay.");
                                        return;
                                }
                                Print("Falha no acesso ao arquivo de dados de ticks.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

o fato de adicionar apenas estas linhas em destaque, faz com que os resultados sejam bastante melhorados, veja na imagem abaixo:


Aqui melhoramos a performance do sistema. Pode parecer pouco, mas é demostrando que de fato as primeiras mudanças fizeram diferença. Conseguimos um tempo aproximado de 2 minutos e 29 segundos para gerar a barra de 1 minuto, ou seja, melhorou de forma geral o sistema. Mas mesmo que isto possa parecer animador, temos um problema que de fato complica tudo. NÃO podemos reduzir o tempo, entre os eventos gerados pela função EventSetMillisecondTimer, o que nos faz pensar em uma outra abordagem.

Mas independente disto, o sistema recebeu uma pequena melhoria. Esta é vista abaixo:

                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                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();
                                }

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

                        }

O que as linhas em destaque criam, é visto no gráfico. Sem elas, a primeira barra sempre fica cortada, dificultado a sua correta leitura, mas quando adicionamos estas duas linhas, a coisa fica bastante mais agradável, nos permitindo ver de forma correta as barras geradas. Isto desde a primeira barra. É algo bem simples, mas faz muita diferença no final das contas.

Mas vamos voltar a nossa questão inicial, que é tentar gerar um sistema adequado de apresentação e criação das barras. Mesmo que fosse possível reduzir o tempo, ainda assim, não teremos um sistema adequado, teremos de fato, que mudar a abordagem a ser usada. Isto por que o EA, não é a melhor forma de se criar um sistema de replay. Mas mesmo assim, quero mostrar uma ultima coisa, que talvez possa ser uma curiosidade para alguns. Quanto podemos de fato conseguir reduzir, ou melhorar a montagem da barra de 1 minuto, se colocarmos o menor tempo possível, para gerar o evento OnTime. E se quando o valor não mudar dentro do mesmo 1 minuto, comprimirmos ainda mais as coisas no âmbito dos ticks. Será que isto faria alguma diferença ?!?!


Indo ao extremo

Para fazer de fato isto, temos que fazer uma ultima modificação no código. Esta é vista abaixo:

#define macroRemoveSec(A) (A - (A % 60))
#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                datetime mem_dt = 0;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Carregando dados para Replay.\nAguarde ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if ((mem_dt == macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt)) && (last == m_ArrayInfoTicks[m_ArrayCount].Last)) vol += m_ArrayInfoTicks[m_ArrayCount].Vol; else
                                                {
                                                        mem_dt = macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt);
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }
                                        }
                                        FileClose(file);
                                        Print("Carregamento concluido.\nFoi gerados ", m_ArrayCount, " posições de movimento.\nIniciando Replay.");
                                        return;
                                }
                                Print("Falha no acesso ao arquivo de dados de ticks.");
                                ExpertRemove();
                        };
#undef macroRemoveSec
#undef macroGetMin

O código em destaque, corrige um pequeno problema que existia antes, mas não era notado. Quando o preço era o mesmo, mas era feita a troca de uma barra para outra, ela demorava um pouco para de fato ser criada. Mas no entanto, o problema mesmo, era que o preço de abertura, ficava diferente do que era mostrado no gráfico original, mas isto foi corrigido. Agora caso todos outros parâmetros sejam iguais, ou com uma pequena diferença, na casa dos milissegundos, teremos apenas uma única posição sendo armazenada.

Feito isto, testamos o sistema com um EventSetMillisecondTimer de 20, e recebemos o seguinte resultado:

 

Neste caso, o resultado foi de 2 minutos e 34 segundos, isto para um evento de 20 milissegundos .... Depois trocamos o valor EventSetMillisecondTimer para 10, que seria o menor valor que a documentação informa, e o resultado foi:

 

Aqui o resultado, foi de 1 min e 56 segundos para um evento de 10 milissegundos. O resultado melhorou. Mas ainda esta muito longe do que precisamos. E agora não tem como diminuir ainda mais o tempo, por este método adotado neste artigo, já que a própria documentação, informa que isto não será possível, ou não teremos estabilidade o suficiente para podermos dar o próximo passo.


Conclusão

Neste artigo mostrei os princípios básicos para quem deseja criar um sistema de replay / simulador. Tais princípios são a base de todo o sistema, mas para quem não tem nenhuma experiência em programação, compreender como a plataforma MetaTrader 5 funciona. Ao ver estes mesmos princípios sendo colocados em prática, pode acabar por motivar enormemente a você começar a estudar e se dedicar a aprender programação. Pois a coisa somente é interessante, quando as vemos funcionando, apenas olhar código e mais código, não tem graça, nos é nada motivador.

Mas a partir do momento em que você vê o que se pode fazer, e entende como a coisa funciona. Tudo muda. É como se uma porta mágica se abrisse, e um novo mundo inteiramente desconhecido e cheio de possibilidades viesse a tona. Nesta sequencia você irá ver muito isto. Estarei desenvolvendo este sistema, enquanto os artigos vão sendo criados. Então tenham um pouco de paciência, mesmo quando ele parece não avançar, sempre existirá algum avanço. E conhecimento nunca é demais. Talvez possa nos deixar menos felizes, mas nunca é demais.

No arquivo anexo, existem as 2 versões que foram tratadas aqui. Você também terá 2 arquivos de ticks reais, para que você possa experimentar, e ver como o sistema irá se comportar no seu hardware. Mas os resultados não serão muito diferentes do que mostrei. Mas pode ser bastante interessante, você ver como o computador lida com algumas questões, que muitas vezes são solucionados de forma bastante criativa.

Mas o próximo artigo, iremos mudar as coisas de forma a tentar ter um sistema mais adequado. Iremos para isto tentar uma solução diferente e bastante curiosa além de ser algo interessante para ser aprendido por aqueles que estão começando na área de programação. Então como alguns dizem:  O trabalho está apenas começando.


Arquivos anexados |
Replay.zip (10910.23 KB)
Teoria das Categorias em MQL5 (Parte 1) Teoria das Categorias em MQL5 (Parte 1)
A Teoria das Categorias é um ramo diverso da Matemática e em expansão, sendo uma área relativamente recente na comunidade MQL. Esta série de artigos visa introduzir e examinar alguns de seus conceitos com o objetivo geral de estabelecer uma biblioteca aberta que atraia comentários e discussões enquanto esperamos promover o uso deste campo notável no desenvolvimento da estratégia dos traders.
DoEasy. Controles (Parte 27): Continuamos a trabalhar no objeto WinForms "ProgressBar" DoEasy. Controles (Parte 27): Continuamos a trabalhar no objeto WinForms "ProgressBar"
Neste artigo, continuaremos desenvolvendo o controle ProgressBar. Criaremos a funcionalidade para gerenciar a barra de progresso e os efeitos visuais.
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.
Guia Prático MQL5 — Serviços Guia Prático MQL5 — Serviços
O artigo descreve os recursos versáteis dos serviços — programas em MQL5 que não necessitam de gráficos para serem anexados. Eu ambém destacarei as diferenças dos serviços de outros programas em MQL5 e enfatizarei as nuances do trabalho do desenvolvedor com os serviços. Como exemplos, são oferecidas ao leitor várias tarefas que abrangem uma ampla gama de funcionalidades que podem ser implementadas como um serviço.