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

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

MetaTrader 5Testador | 11 abril 2023, 09:01
364 0
Daniel Jose
Daniel Jose

Introdução

Nosso sistema de replay de mercado, tem sido documentado em artigos, enquanto ele vai sendo criado, o motivo disto, é mostrar a todos como realmente a coisa vai nascendo, até tomar uma forma mais definitiva. Por conta disto, você podem até achar que o progresso, esta sendo lento e muitas coisas, estão sendo criadas, e retiradas, adicionadas e depois modificadas. Isto de fato é verdade, mas o motivo disto esta acontecendo, é pelo fato de que o sistema de replay de mercado, esta sendo criado, ao mesmo tempo que os artigos estão sendo montados. São feitos alguns testes, antes de começar a documentar, o que aconteceu no sistema, de forma que ele se mantenha estável e funcional, enquanto vai sendo modelado e ajustado.

Dito isto, vamos ao que interessa de fato, no artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 05): Adicionando Previas, fizemos a construção do sistema de carregamento de barras prévias. Apesar de aquele sistema funcionar, e de forma até adequada, temos alguns problemas nele. O mais gritante, é o fato de que você precisa criar um arquivo de barras prévias, contendo muitas vezes, vários dias e não conseguirá intercalar os dados, de forma a ter uma base destes mesmos dados, bem mais utilizável.

Quando você criar um arquivo de barras prévias, contendo por exemplo, dados de uma semana. Iniciando na segunda e terminando na sexta. Não irá conseguir utilizar, esta mesma base de dados, para fazer um replay da quinta, por exemplo. Será preciso criar uma nova base de dados, apenas para satisfazer este fato. E isto é TERRIVEL, se você parar para pensar.

Além deste inconveniente, temos outros problemas. Como a total e completa falta de testes adequados, de forma a assegurar de que estamos usando uma base de dados adequada. Assim você pode acidentalmente, utilizar arquivos de barras, como se fosse dados de ticks de negócios executados, ou vice-versa. O que traz enormes transtornos para o nosso sistema, não permitindo que ele funcione de forma adequada. Temos outras pequenas mudanças, que também serão feitas durante o decorrer deste artigo, mas então vamos ao que interessa.

Lembrando que irei explicar cada um dos pontos, de forma a vocês entenderem o que estará acontecendo, ou pelo menos esta é a minha intensão.


Implementando as melhorias.

A primeira mudança que de fato precisamos fazer, será adicionar duas novas linhas no arquivo de serviço. Estas são vistas logo abaixo:

#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence

E por que fazer isto ?!?! Pelo simples fato, de que o sistema é composto de módulos, e temos que garantir, de alguma forma, que estes módulos estejam presentes no momento que o serviço de replay for ser utilizado. O módulo mais importante de todos, é justamente este indicador, que é o responsável por nos permitir, ter algum controle sobre o que será feito.

Devo confessar, que não é uma das melhores formas de se fazer isto. Talvez no futuro, os que mantém a plataforma MetaTrader 5, juntamente com a linguagem MQL5, faça alguma adições como diretivas de compilação, de forma que possamos de fato forçar, ou melhor dizendo, garantir que um arquivo seja compilado, ou que de fato, ele deva existir. Mas na falta de algo melhor, fazemos assim.

Definimos uma diretiva, que informa que o serviço depende de alguma outra coisa. Logo depois, adicionamos este mesma coisa, como sendo um recurso dentro do serviço. Um detalhe: O fato de tornar esta coisa, como sendo um recurso, não necessariamente, irá nos permitir usar isto, como sendo um recurso. Este é um caso especifico.

Estas duas singelas linhas, irão garantir que o indicador, que esta presente dentro do template, a ser usado pelo replay, seja de fato compilado. Isto quando o serviço de replay for compilado. Pois pode ser que você se esqueça de fazer isto, e quando o template for carregar o indicador, e ele não for encontrado, teremos uma falha, que somente será notada, quando percebermos que o indicador usado para controlar o serviço, não se encontra no gráfico.

Para evitar isto, já garantimos que o indicador, seja compilado junto com o serviço. Mas caso você venha a utilizar um template pessoal, para depois adicionar de forma manual o indicador controlador, pode retirar estas duas linhas acima do código do serviço. Pois a ausência das mesmas, não irá fazer diferença no código, ou no funcionamento do serviço.

NOTA: Apesar de forçarmos uma compilação, ela de fato somente acontecerá se o executável do indicador não existir. Se o indicador for modificado, compilar apenas o serviço não irá fazer com que o indicador seja compilado.

Alguns podem dizer, que isto seria solucionado usando o modo projeto do MetaEditor. Mas este modo projeto, não nos permite trabalhar da mesma forma, que acontece em linguagens do tipo C / C++. Onde usamos um arquivo MAKE para controlar a compilação. Até daria para fazer isto via arquivo BATCH. Mas isto nos forçaria a sair do MetaEditor, para apenas poder compilar o código.

Continuando, temos agora duas novas linhas:

input string            user00 = "Config.txt";  //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Tempo gráfico inicial.

Aqui temos algo realmente útil para nosso serviço de replay de mercado, esta string, é na verdade, o nome do arquivo que irá conter as configurações do ativo de replay. Estas configurações incluem no momento, quais são os arquivos usados para gerar as barras prévias e qual é o, ou quais são, os arquivos a serem utilizados que conterão os tick negociados.

Agora você poderá usar mais de um arquivo ao mesmo tempo. Além disto, sei que muitos gostam de utilizar um tempo gráfico especifico ao operar no mercado, e ao mesmo tempo, gostam de usar o gráfico, em modo de tela cheia. Então esta linha ajuda a você, logo de inicio, especificar qual será o tempo gráfico que deverá ser utilizado. Algo bem simples, mas nos traz muito mais conforto, pois podemos salvar estas configurações, para uso posterior. Podemos adicionar mais coisas aqui, mas por hora, já esta bom.

Agora temos mais algumas coisas a serem entendidas.  Estas podem ser vistas no código logo abaixo:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id = 0;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        if (!Replay.SetSymbolReplay(user00))
        {
                Finish();
                return;
        }
        Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
        id = Replay.ViewReplay(user01);
        while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750);
        if ((_StopFlag) || (ChartSymbol(id) == ""))
        {
                Finish();
                return;
        }
        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = true;
                }else
                {
                        if (bTest)
                        {
                                delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay);
                                bTest = false;
                                t1 = GetTickCount64();
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) break;
                                t1 = GetTickCount64();
                        }
                }
        }
        Finish();
}

Originalmente, o que fazíamos, era simplesmente esperar que as coisas estivessem funcionando. Mas a partir de agora, iremos garantir que de fato elas estejam funcionando, e isto é feito testando as coisas. Então, agora iremos testar, se os arquivos que serão usados para efetuar o replay, de fato são adequados para isto. Testamos o retorno da função, que executa a leitura dos dados contidos nos arquivos, e caso tenha ocorrido uma falha, teremos uma mensagem do que aconteceu presente na caixa de ferramentas do MetaTrader 5,  e o serviço de replay, simplesmente será encerrado. Já que não temos dados adequados para o uso do serviço.

Caso os dados tenham sido carregados de forma adequada, uma mensagem na caixa de ferramentas irá indicar isto, e poderemos continuar. Então abrimos o gráfico o ativo de replay, e esperamos a permissão para podermos executar as próximas etapas. Mas pode ser, que durante esta espera, o usuário finalize o serviço, ou feche o gráfico do ativo de replay, caso isto aconteça deveremos finalizar o serviço de replay. Se tudo ocorrer de forma adequada, iremos entrar no loop do replay, mas ainda assim, iremos garantir que o usuário não feche o gráfico ou finalize o serviço. Pois caso isto aconteça, o replay também deverá ser encerrado.

Notem que agora, não estamos simplesmente imaginando que as coisas irão funcionar. Agora estamos garantindo que elas de fato estarão funcionando. Parece estranho que este tipo de testagem não estava acontecendo nas versões anteriores do sistema. Mas lá as questões eram outras, então sempre que o replay era encerrado, ou tinha suas atividades finalizadas por algum motivo, ficava alguma coisa para traz. Mas agora não, vamos ter certeza de que as coisas de fato estão funcionando, e que não ficará sobras para traz.

Em ultima analise, temos o seguinte código ainda dentro do arquivo de serviço:

void Finish(void)
{
        Replay.CloseReplay();
        Print("Serviço de replay finalizado...");
}

Não é algo nada complicado de entender. Aqui estamos simplesmente finalizando o replay, e informando isto ao usuário pela caixa de ferramentas. Desta forma podemos passar para o arquivo que implementa a classe C_Replay. Pois lá também implementamos mais testes, para garantir o correto funcionamento das coisas.

Se você reparar bem, não irá notar grandes mudanças na classe C_Replay. Mas ela esta sendo construída, de forma a manter o serviço de replay, o mais estável e confiável quanto for possível. Então as coisas irão aparecer aos poucos, para que as mudanças não destrua todo o trabalho já criado.

Então a primeira coisa a de fato chamar a atenção, são as linhas que são mostras abaixo:

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_Header_Bar          "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>"
#define def_Header_Ticks        "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"

Apesar de parecer de pouca importância, estas 4 linhas são muito curiosas, pois elas efetuam exatamente os testes que precisamos. Estas duas definições daqui, na verdade são usadas no arquivo de configuração, que será visto logo mais, já esta, contém exatamente os dados, que você irá encontrar na primeira linha de um arquivo, que contem barras a serem utilizadas, neste momento, como sendo barras prévias. Esta definição, contem exatamente o conteúdo da primeira linha do arquivo, que contem os ticks negociados.

Mas espera um pouco, estas definições não são exatamente iguais, ao encontrado no cabeçalho dos arquivos, está faltando as tabulações. SIM, de fato esta faltando as tabulações, que existem nos arquivos originais. Mas aqui existe um pequeno detalhe, que é a forma como os dados são lidos.

Mas antes de entrar neste detalhe, vamos ver como é um arquivo de configuração do serviço de replay, no atual estágio de desenvolvimento. Um exemplo deste arquivo é visto logo abaixo:

[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

[Ticks]
WINQ21_202108050900_202108051759
WINQ21_202108060900_202108061759

A linha definida como [Bars], pode ser digitado assim, já que não irei usar case sensitive no sistema, indica que todas a linhas a seguir, serão usadas como barras prévias. Então temos 3 arquivos distintos, que serão carregados na sequencia indicada. Atenção a isto, pois se você os colocar fora da sequencia, terá um replay completamente diferente do esperado. Todas as barras presentes nestes arquivos serão adicionadas, uma a uma, ao ativo que estará sendo usado como replay. Não importa quantidade de arquivos, ou barras, todos arquivos serão adicionados como sendo barras prévias, até que algo diga para mudar este comportamento.

No caso é a linha [Ticks], esta irá informar ao serviço de replay, que todas as linhas a seguir, serão ou deveram, conter ticks negociados, e estes serão usados para montar o replay que desejamos estudar. Igual o que acontece com as barras, novamente o mesmo aviso é válido aqui também, o de que você deve tomar o cuidado de colocar os arquivos na ordem correta. Caso contrário, o replay irá ficar diferente do esperado, a leitura sempre se dará do inicio, até o final do arquivo. Assim você pode mesclar barras, com ticks.

Mas existe uma pequena limitação neste momento. Talvez não seja bem uma limitação, já que não faz sentido você adicionar ticks negociados, fazer um replay destes, para logo em seguida, ver surgir mais barras prévias, que serão usadas em uma outra chamada de replay. Porém em uma posição diferente. Mas o fato de você colocar os ticks, antes das barras prévias, no arquivo de configuração, não irá fazer nenhuma diferença para o sistema de replay. Independente da ordem, para o sistema de replay, as barras prévias sempre irão vim primeiro, para somente depois os ticks de negócios.

Importante: No exemplo acima, não considerei um fato que é possível ser feito, caso você deseje organizar melhor as coisas, pode utilizar uma arvore de diretórios, de forma a separar as coisas e organizá-las, de uma maneira mais adequada. Isto sem fazer nenhum tipo de modificação extra no código da classe. Tudo que você precisará fazer, é tomar cuidado, em seguir uma certa lógica nas estruturas, que se encontra no arquivo da classe. Para ficar mais claro, vejamos um exemplo de uso de uma arvore de diretórios, a ponto de separar as coisas em termos de ativos, meses ou anos.


Para compreender o que foi dito acima, veja as imagens abaixo:

                   

Notem que temos como RAIZ, o diretório MARKET REPLAY, que é a base que deveremos usar, uma vez dentro deste diretório. Podemos organizar as coisas, separando elas em ativos, anos e meses, onde cada mês, terá dentro de si, os arquivos correspondentes ao que aconteceu naquele mês em especifico. Da forma como o sistema esta sendo criado, você poderá usar uma estruturação como mostrado acima, e sem fazer nenhuma mudança no código. Acessar os dados específicos, apenas informando isto no arquivo de configuração, que você estará usando.

Lembrando que este arquivo de configuração, poderá ter qualquer nome. Apenas o seu conteúdo, é que será estruturado. O nome é livre para que você escolha o mais adequado.

Muito bem. Então no momento que você for chamar um arquivo, suponhamos o arquivo de ticks, no mini dólar, do ano de 2020, referente ao mês de junho no dia 16. Você irá usar a seguinte linha no arquivo de configuração:

[Ticks]
Mini_Dolar_Futuro\2020\06-Junho\WDO_16062020

isto irá informar ao sistema, para ler exatamente o arquivo em questão. Claro que isto daqui, é apenas um exemplo, de como você poderá organizar as coisas.

Mas para entender o por que disto acontecer, e se torna possível, é preciso ver a rotina que cuida da leitura deste arquivo de configuração. Então vamos a ela.

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                        
        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.\nAguarde....");
        while ((!FileIsEnding(file)) && (!_StopFlag))
        {
                szInfo = FileReadString(file);
                StringToUpper(szInfo);
                if (szInfo == def_STR_FilesBar) isBars = true; else
                if (szInfo == def_STR_FilesTicks) isBars = false; else
                if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
                {
			if (!_StopFlag)
	                        MessageBox(StringFormat("O arquivo %s de %s\nnão pode ser carregado.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
                        FileClose(file);
                        return false;
                }
        }
        FileClose(file);
        return (!_StopFlag);
}

Começamos tentando ler o arquivo de configuração, que deverá estar em uma localização bem especifica. Esta não poderá ser modificada, pelo menos depois que o sistema tenha sido compilado. Caso este arquivo não possa ser aberto, uma mensagem será mostrada, informando o erro e a função irá retornar. Caso o arquivo possa ser aberto, iniciamos a leitura do mesmo, mas observem que estaremos o tempo todo, observando se o usuário pediu para o MetaTrader 5 finalizar o serviço de replay.

Caso isto aconteça, do usuário finalizar o serviço, a função irá retornar como se tivesse falhado. Efetuamos a leitura de linha por vez, e tornamos todos os caracteres lidos em seus correspondentes maiúsculos. Desta forma fica mais fácil analisar as coisas. Então analisamos e chamamos a rotina adequada, para ler os dados do arquivo indicado no script de configuração. No caso da leitura de algum destes arquivos falhem, por algum motivo, uma mensagem de erro irá informar isto ao usuário e a função termina em erro. Quanto todo o arquivo de configuração já estiver sido lido, a função simplesmente irá terminar. E caso ela não tenha sido encerrada pelo usuário, em um pedido de finalizar o serviço, teremos um retorno indicando que esta tudo ok.

Agora que já vimos como o arquivo de configuração é lido. Vamos dar uma passada nas rotinas responsáveis, pela leitura dos dados, e entender o por que de agora elas informarem que o arquivo requisitado, não se enquadra no que é esperado, ou seja, se você tentar usar uma arquivo, que contém barras no lugar de um arquivo que deveria conter ticks negociados, ou vice versa, um erro é reportado pelo sistema. Então vamos ver como isto acontece.

Vamos começar com a rotina mais simples. Que é a responsável pela leitura e carregamento das barras prévia. O código responsável por isto pode ser visto logo abaixo:

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
        string  szInfo = "";
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Bar)
                {
                        Print("Arquivo ", szFileNameCSV, ".csv não é um arquivo de barras prévias.");
                        return false;
                }
                Print("Carregando barras previas para Replay. Aguarde ....");
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        Rate[0].time = StringToTime(FileReadString(file) + " " + FileReadString(file));
                        Rate[0].open = StringToDouble(FileReadString(file));
                        Rate[0].high = StringToDouble(FileReadString(file));
                        Rate[0].low = StringToDouble(FileReadString(file));
                        Rate[0].close = StringToDouble(FileReadString(file));
                        Rate[0].tick_volume = StringToInteger(FileReadString(file));
                        Rate[0].real_volume = StringToInteger(FileReadString(file));
                        Rate[0].spread = (int) StringToInteger(FileReadString(file));
                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                        dt = (dt == 0 ? Rate[0].time : dt);
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
                m_dtPrevLoading = Rate[0].time + iAdjust;
                FileClose(file);
        }else
        {
                Print("Falha no acesso ao arquivo de dados das barras previas.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return (!_StopFlag);
}

A primeira vista este código, não parece muito diferente do código contido no artigo anterior, Desenvolvendo um sistema de replay ( Parte 05 ). Mas sim, ele contém diferenças, e estas são bastante significativas em termos gerais, e estruturais.

A primeira das diferenças, é o fato de que agora nos estamos capturando os dados do cabeçalho do arquivo que esta sendo lido. Este cabeçalho, é comparado com um valor definido e esperado pela rotina de leitura das barras prévias, se este mesmo cabeçalho, for diferente do esperado, um erro será gerado e a rotina irá retorna. Mas caso ele seja o esperado, iremos entrar em um loop.

Anteriormente a saída deste loop era controlado, apenas e somente pelo fim do arquivo que estava sendo lido. Caso o usuário fizesse a finalização do serviço, por qualquer motivo, este não seria finalizado. Então agora isto foi corrigido, e caso o usuário finalize o sistema de replay, durante a leitura de um dos arquivos que correspondem as barras prévias que estão sendo carregadas, o loop irá ser finalizado, e um erro será gerado, indicando que o sistema falhou. Mas isto é apenas uma mera formalidade, para que as coisas saiam de uma forma um pouco mais suave e não de forma abruta.

Todo o restante da rotina, continua executando da mesma forma. Já que não houve necessidade de alterações na forma de ler os dados.

Agora vamos ver a rotina de leitura dos ticks negociados, pois esta teve o seu funcionamento modificado, de forma que ela ficou ainda mais curiosa, do que a rotina de leitura das barras prévias. Seu código pode ser visto logo a seguir:

#define macroRemoveSec(A) (A - (A % 60))
        bool LoadTicksReplay(const string szFileNameCSV)
                {
                        int     file,
                                old;
                        string  szInfo = "";
                        MqlTick tick;
                                
                        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                        {
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                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 = macroRemoveSec(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;
                                                m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0);
                                                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...");
                                        return false;
                                }
                        }else
                        {
                                Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                                return false;
                        }
                        return (!_StopFlag);
                };
#undef macroRemoveSec

Até no artigo anterior, esta rotina de leitura de ticks negociados, tinha uma limitação que era constituída na seguinte linha de definição:

#define def_MaxSizeArray        134217727 // 128 Mbytes de posições

Esta linha ainda se mantém, mas a limitação foi desfeita. Pelo menos em parte. Já que existia o interesse de se montar um sistema de replay, que pode ser capaz de trabalhar com mais de uma base de dados de ticks negociados. Desta forma você poderia adicionar 2 ou mais dias ao sistema, e assim fazer um estudo de replay um pouco mais amplo. Além do mais, existem casos, muito, mas muito específicos, onde você pode vim a ter um arquivo, onde teremos mais que 128 Mbytes de posições a serem trabalhadas. Tais casos são raros, mas pode acontecer. Então a partir de agora, você pode usar um valor menor para esta definição acima, de forma a otimizar um pouco melhor o uso da memória.

Mas espera um pouco. Eu disse: REDUZIR ?!?! SIM. E se você observar a nova definição, ira ver o seguinte código:

#define def_MaxSizeArray        16777216 // 16 Mbytes de posições

Você pode estar pensando: Você é louco, isto irá prejudicar o sistema ... Na verdade não. Se você observar na rotina de leitura de ticks negociados, irá ver duas linhas bastante curiosas, e que não estavam lá anteriormente. Estas são responsáveis por garantir que possamos ler e armazenar, um máximo de 2 elevado a 32 posições. Isto é garantido pelo teste feito na entrada do laço. 

Para garantir que as primeiras posições, não venham a ser perdidas, subtraímos 2, para que o teste não venha a falhar por qualquer motivo. Você poderia adicionar um laço mais externo, de forma a ampliar esta capacidade de armazenamento. Mas pessoalmente, não vejo motivos para isto. Já que se 2 Giga de posições, não são o bastante, não sei o que seria. Mas vamos entender com calma, como o fato de reduzir o valor da definição, nos garante uma melhor otimização, usando duas linhas para isto. Vamos olhar com mais calma e em detalhes, o fragmento responsável por isto.

// ... Código anterior ....

if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
{
        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
        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);

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

A primeira vez que alocamos a memória, iremos alocar todo o tamanho definido, mais um valor de reserva. Este valor de reserva irá ser nossa salva guarda. Então quando entrarmos no loop de leitura, iremos ter uma sequencia de realocações, mas somente quando for necessário de fato fazer isto.

Agora quero que observem o fato de que nesta segunda alocação, estaremos utilizando o valor atual do contador de ticks já lidos mais 1, e por que disto ?!?! Quando estava testando o sistema, notei que ele executava esta chamada com um valor igual a 0, o que gerava um erro em tempo de execução. Doideira, você pode pensar, já que a memória já havia sido alocada anteriormente com um valor maior. E a própria documentação da função ArrayResize, nos informa que iremos redefinir o tamanho do array, e é justamente ai que mora a questão.

Quando usamos esta segunda chamada, a função irá REDEFINIR o array, como valor de zero. Já que este é o valor da atual variável na primeira chamada da rotina, e o motivo é que ela não foi incrementada. Não estou aqui para explicar o por que disto acontecer, mas é preciso que você tenha este cuidado ao se trabalhar com alocação dinâmica no MQL5. Pois pode ser se seu código, esteja aparentemente correto, mas o sistema não o entenderá da forma como você esta imaginando.

Aqui temos mais um pequeno detalhe a ser observado. Por que estou usando INT_MAX e não UINT_MAX no teste ?!?! De fato o ideal seria usar UINT_MAX o que nos daria 4 Gigas de espaço alocado, mas observem que ArrayResize trabalha com um sistema INT, ou seja um inteiro com sinal.

E mesmo que você deseje alocar 4 Gigas, que seriam possíveis ao usar o tipo com 32 bits de comprimento. Iremos sempre perder 1 bit no comprimento dos dados por conta do sinal. Então de fato estaremos usando 31 bits o que nos garante 2 Giga de espaço possível de ser alocado usando a função ArrayResize. De certa forma, podemos contornar esta limitação usando um esquema de swap, o que nos garantiria os 4 Gigas de alocação, ou até mais, mas não vejo motivo para fazer uso de tal recurso. Dois Giga de dados, já são bastante coisa.

Feita a explicação, vamos voltar ao código. Por que ainda temos mais algumas coisas para a serem vistas sobre a rotina de leitura de ticks negociados. Para que possamos testar se os dados do arquivo são de fato ticks negociados, fazemos a leitura e armazenamos os valores que se encontram no cabeçalho do arquivo. Uma vez que isto foi feito, podemos em fim testar se o cabeçalho é o mesmo que o sistema espera encontrar, no caso de um arquivo de ticks negociados. Caso isto não aconteça, um erro será informado e o sistema irá finalizar.

Da mesma forma que na leitura das barras, aqui também testamos se foi feito o pedido de encerramento do sistema por parte do usuário. Isto garante que tenhamos uma saída mais suave e de forma mais limpa. Pois caso o usuário encerre ou finalize o serviço de replay, não queremos que algumas das mensagens de erro sejam impressa, gerando assim algum tipo de confusão.

Além de todos estes teste executados aqui, temos mais algumas coisas a serem executadas. Na verdade, até que não precisaria fazer tais coisas, mas não quero deixar tudo nas mão da plataforma. Quero garantir que algumas coisas de fato sejam executadas. Por conta disto surge uma nova linha no nosso código. E ela pode ser vista logo abaixo:

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

Você talvez pense que executar esta chamada não é de todo importante. Mas uma das boas praticas da programação, é fazer uma limpeza de todas as coisas que foram criadas, ou devolver de forma explicita, toda a memória que alocamos. E é justamente o que é feito neste ponto, garantimos que a memória alocada durante o carregamento dos ticks negociados, seja devolvida para o sistema operacional. Isto normalmente é feito quando fechamos a plataforma, ou quando encerramos o programa no gráfico. Mas é bom garantir que de fato isto será feito, mesmo que a plataforma faça isto por nos, temos que ter certeza de tal coisa.

Se ocorrer alguma falha, e o recurso não for devolvido para o sistema operacional, pode acontecer de no momento que tentarmos utilizar novamente o recurso, ele estará indisponível. Não por falha da plataforma ou do sistema operacional. Mas por um esquecimento no momento de programar, algum utilitário que será executado.


Conclusão

No vídeo logo abaixo, você poderá observar o sistema funcionando no atual estagio de desenvolvimento. Observem que as barras previas irão terminar no dia 4 de agosto. Teremos o inicio do primeiro dia de replay, iniciando com o primeiro tick do dia 5 de agosto. Mas poderemos avançar o replay para o dia 6 de agosto, e depois retornar ao inicio do dia 5 de agosto. Este tipo de coisa não era possível ser feita até a versão anterior do sistema de replay, mas agora contamos com esta possibilidade.

Se vocês observarem bem, irão notar uma falha no sistema. Esta será corrigida no próximo artigo. Onde iremos melhorar um pouco mais o nosso replay de mercado, o tornando ainda mais estável e intuitivo de ser utilizado.



No arquivo anexo, vocês terão acesso a todo o código fonte e aos arquivos utilizados no vídeo, para que possam entender e praticar a forma de montar o arquivo de configuração. É bom que você inicie os estudos nesta etapa. Pois este arquivo de configuração, irá mudar com o tempo, mas não de forma a destrutiva, mas aumentando as suas capacidades, então entenda ele desde agora, do inicio ...


Arquivos anexados |
Market_Replay.zip (13057.37 KB)
Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada
Continuamos a estudar os algoritmos de aprendizado Q distribuído. Em artigos anteriores, já discutimos os algoritmos de aprendizado Q distribuído e de quantil. No primeiro, aprendemos as probabilidades de determinados intervalos de valores. No segundo, aprendemos intervalos com uma probabilidade específica. Em ambos os algoritmos, utilizamos o conhecimento prévio de uma distribuição e ensinamos a outra. Neste artigo, vamos examinar um algoritmo que permite que o modelo aprenda ambas as distribuições.
DoEasy. Controles (Parte 31): Rolando o conteúdo do controle "ScrollBar" DoEasy. Controles (Parte 31): Rolando o conteúdo do controle "ScrollBar"
Neste artigo, criaremos a funcionalidade para rolar o conteúdo do contêiner usando os botões da barra de rolagem horizontal.
Algoritmos de otimização populacionais: algoritmo de vaga-lumes Algoritmos de otimização populacionais: algoritmo de vaga-lumes
Vamos considerar o método de otimização de vaga-lumes (Firefly Algorithm, FA). Esse algoritmo evoluiu de um método desconhecido por meio de modificações para se tornar um líder real na tabela de classificação.
DoEasy. Controles (Parte 30): Animando o controle "ScrollBar" DoEasy. Controles (Parte 30): Animando o controle "ScrollBar"
Neste artigo continuaremos a desenvolver o controle ScrollBar e começaremos a fazer a funcionalidade de interação com o mouse. Além disso, vamos expandir as listas de bandeiras de status e eventos com o mouse.