English Русский 中文 Español Deutsch 日本語
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 10): Usando apenas dados reais na replay

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 10): Usando apenas dados reais na replay

MetaTrader 5Exemplos | 12 maio 2023, 10:21
427 5
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 09): Eventos Customizados, vimos como fazer para disparar eventos customizados. Ao mesmo tempo que produzimos um sistema bastante interessante, com relação a forma como o indicador, passou a se comunicar com o serviço. Neste artigo ire mostrar a importância de você fazer algo. Armazenar tickets negociados em arquivos. Se você não está fazendo isto, deveria pensar seriamente em começar a fazer, todos os dias. Gravando todos os valores que foram negociados de um determinado ativo, que você pretende estudar e conhecer a fundo.

Não adianta tentar achar uma solução mágica, para tentar recuperar as informações perdidas. Uma vez que elas foram perdidas, já era. Você não conseguirá recriá-las, por nenhum meio, seja ele qual for. Se pretende mesmo estudar, e se aprofundar em um ativo especifico, não deixe para depois. Comece o mais breve possível, a guardar os tickets negociados, e os guarde em um local bastante seguro, pois eles tem um valor inestimável. Depois de um tempo fazendo isto. Você irá notar que os dados das barras, não estarão coincidindo com os valores dos tickets negociados. O fato de isto estar ocorrendo, torna a coisa um pouco mais complexa de ser entendida, por parte dos que não tem tanto conhecimento, sobre o mercado financeiro.

Acontece, que de tempos em tempos, algumas coisas acontecem. Estas fazem com que os valores, mesmo as barras de 1 minuto, não correspondam aos reais negócios ocorridos. Algumas destas coisas são: Lançamentos de proventos. O fato do ativo sofrer agrupamento ou desdobramento. Vencimento do ativo ( no caso dos futuros ), onde de tempos em tempos o valor é ajustado. E o lançamento de bonificações ou subscrição. Todos este, são exemplos, de coisas que podem ocorrer com o ativo, e que faz com que o valor presente nas barras de 1 minuto, não venham de fato a corresponder a realidade negociada, em um dado momento ou período.

Isto por conta, que quando estas coisas acontecem, o ativo tem seu preço ajustado, de uma forma bem especifica. Por mais que tentemos, não conseguiremos gerar um reajuste, de forma que as coisas fiquem iguais. Entre as barras de 1 minuto e os tickets negociados. Por isto que os tickets são inestimáveis.

NOTA: Alguns podem dizer que se você lançar o valor da diferença do ajuste, irá fazer com que os valores venham a se tornarem iguais. Mas isto não é de fato a questão. O problema é que se o mercado vê o preço em uma faixa, ele tem uma reação. Ao ver o preço em outra faixa, a reação será diferente.

Desta forma, temos então uma coisa a ser feita no nosso sistema de replay/simulador, que é fazer o uso de forma única, e exclusivamente dos arquivos de tickets negociados. Apesar de isto, esta sendo feito, desde o inicio desta serie de artigos, até o momento. Não estamos de fato fazendo com que os dados contidos nos tickets negociados, venham a ser utilizados, a não ser para gerar o replay. Mas agora iremos mudar isto, vamos também permitir, que estes arquivos, e por consequência os dados contidos neles, venham a ser também utilizados como uma forma de gerar as tais barras previas. Seria uma forma de substituição, dos dados em que utilizamos apenas e somente arquivos, contendo barras previas de 1 minuto.


Desenvolvendo a implementação do código

Apesar de tudo, grande parte do código, já se encontra implementado. Então não teremos muito trabalho com relação a isto. Precisamos pensar um pouco com relação a uma outra coisa: No arquivo de configuração utilizado no replay/simulação. Pois como ficou claro, desde o momento que este arquivo começou a ser utilizado, é que se trata de um arquivo de suma importância para nos. O problema, se bem que não se trata exatamente de um problema. É que neste arquivo, temos atualmente dois tipos de estrutura: Uma para informar os tickets negociados, que serão usados para se criar o replay/simulação. E uma outra, que usamos para informar as barras a serem utilizadas, como sendo uma previa do movimento, e que de fato não farão parte da simulação. Apenas serão adicionadas ao ativo, e ponto final.

Isto nos permite separar e ajustar de forma adequada as coisas, mas agora temos um pequeno problema, que é o seguinte: Se você adicionar um arquivo de tickets, dentro da estrutura de barras, o sistema irá gerar um aviso de erro. Queremos de fato que isto se mantenha, de uma forma que não iremos correr o risco, de criar um estudo de replay/simulação, onde as coisas saiam do controle. Isto por causa, de algum erro de digitação, no momento em que estamos criando o arquivo de configuração. A mesmo coisa acontece, se você tentar adicionar um arquivo de barras de 1 minuto, como sendo um arquivo de tickets negociados. O sistema simplesmente irá ver isto como um erro.

Então como podemos solucionar parte disto, de forma, a poder utilizar arquivos de tickets, como sendo barras previas ?!?! Sem que o sistema veja isto, como um erro ?!?! Pois bem, esta é a questão. Aqui irei propor, uma entre tantas soluções possíveis e passiveis de serem desenvolvidas e criadas. A primeira coisa que vamos fazer, será adicionar uma nova definição no arquivo de cabeçalho C_Replay.mqh:

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_STR_TicksToBars     "[TICKS->BARS]"

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

Esta definição, será utilizada para identificar, quando um ou mais arquivos de tickets, deverão ser utilizados como sendo arquivos de barras. Desta maneira, ficará bem mais simples, começar a produzir as próximas etapas. Mas tem um detalhe extra aqui. Não vamos apenas adicionar esta definição. Vamos também permitir que o usuário a modifique. Assim como as demais definições, só um pouco. De forma que para o usuário, pode ser que as coisas fiquem mais simples de serem lidas e entendidas. Mas para o sistema, não irá fazer grandes diferenças, já que ele irá continuar entendendo as coisas da forma correta.

Então, se por um acaso o usuário digitar, no arquivo de configuração, a seguinte definição:

[ TICKS -> BARS ]

Ela ainda será, e deverá ser compreendida, como sendo uma definição válida. Assim abrimos um pouco mais o leque, de maneira, que para o usuário, será mais simples entender algo. O motivo é que alguns não gostam de amontoar os dados. Gostam de separar eles, de uma forma lógica. Porém perfeitamente aceitável, a ponto que podemos permitir isto. Para fazer isto, permitir que o usuário "modifique" um pouco as definições esperadas. Temos que adicionar algumas pequenas coisas ao código de serviço. Isto para que o código fique mais fácil de ser entendido e ao mesmo tempo, nos permita, ampliar as funcionalidades futuras, da forma o mais simples possível. Para fazer isto, vamos criar um enumerador a fim de nos ajudar. Mas tem um detalhe nesta criação. Vejam abaixo do que se trata:

class C_Replay
{
        private :
                enum eTranscriptionDefine {Transcription_FAILED, Transcription_INFO, Transcription_DEFINE};
                int      m_ReplayCount;
                datetime m_dtPrevLoading;

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

Este enumerador, é privativo da classe C_Replay, ou seja você não conseguirá acessá-lo fora da classe. Ao mesmo tempo, que garantimos fazer isto, colocando ele dentro do corpo de declarações privativas, também teremos a capacidade de ampliar, de forma muito fácil, a complexidade do arquivo de configuração. No entanto, isto será visto nos próximos artigos desta sequencia, já que é algo cuja explicação é bastante extensa, para ser dada aqui, neste artigo.

Depois de ter feito isto, podemos focar no próximo ponto a ser implementado. Este ponto é na verdade, uma rotina que nos permite saber qual a classificação dos dados contidos no arquivo de configuração. Então vamos ver, como funciona este processo. Para isto, iremos utilizar o código abaixo:

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
        
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                
        return Transcription_DEFINE;
}

O que estamos, e queremos fazer aqui, é centralizar todo o trabalho de pré tradução dos dados, que serão lidos de dentro do arquivo de configuração. Não importa como será o trabalho a posterior. Esta função acima, irá fazer todo o trabalho de pré checagem, a fim de garantir, que os dados lidos, estarão dentro de um determinado padrão. Para isto, a primeira coisa a ser feita, é tornar todos os caracteres em seus correspondentes em maiúsculos. Logo depois, limparmos qualquer coisa extra, desnecessária aos próximos passos. Uma vez feito isto, verificamos o primeiro carácter, na cadeia de caracteres, e se for diferente de um [ teremos a indicação de que a cadeia de caracteres, é alguma informação e não uma definição.

Neste caso especifico e neste atual momento, iremos simplesmente retorna o resultado da limpeza feita acima. Caso contrário, começaremos a procurar os dados da definição. Já que eles podem estar com uma formatação diferente, mas com um conteúdo adequado e correto. Fazemos a leitura, ignorando qualquer carácter, cujo valor seja inferior, ao valor do carácter espaço, ou seja, mesmo que você digite: [ B A R S ] este formato de leitura, irá fazer com que o sistema entenda [BARS]. Desta forma, a coisa toda, pode sofre pequenas modificações, no que diz respeito a forma como será escrita. Desde que o conteúdo seja o esperado, teremos o resultado adequado.

Com isto, agora teremos um novo sistema de leitura, e configuração do sistema, com base no conteúdo do arquivo de configuração:

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. Aguarde....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        while ((!FileIsEnding(file)) && (!_StopFlag)) switch (GetDefinition(FileReadString(file), szInfo))
        {
		case Transcription_DEFINE:
			if (szInfo == def_STR_FilesBar) isBars = true; else
                        if (szInfo == def_STR_FilesTicks) isBars = false;
                        break;
		case Transcription_INFO:
			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;
			}
			break;
	}
        FileClose(file);

        return (!_StopFlag);
}

Mas não fique tão empolgado. Já que a única coisa, que foi feita aqui, foi a mudança da estrutura, que existia antes para esta daqui. No entanto, a forma e o jeito de se fazer as coisas, continua igual. Não temos ainda, a possibilidade de usar arquivos, contendo tickets negociados, como se fosse barras previas de 1 minuto. Precisamos fazer algumas mudanças no código acima.

Se você observar e prestar um pouco de atenção, irá ver que ganhamos a capacidade de fazer algo, que antes não era possível. Pense um pouco, os dados são enviados para dentro de um procedimento, que esta centralizando todo o pre tratamento, dos dados lidos do arquivo de configuração. Os dados usados no código acima, já estão limpos. Então, será que podemos adicionar comentários no arquivo de configuração ?!?! E a resposta é : SIM. Já podemos fazer isto. Será preciso apenas escolher, qual será a formatação de um comentário. Se ele será de linha, ou multilinhas. Mas vamos implementar, primeiramente, um do tipo linha. Vejam que o trabalho para fazer isto, é bem simples e direto:

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
                                
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                                
        return Transcription_DEFINE;
}

Quando o carácter indicado aqui, for encontrado no início de uma linha, esta linha será tratada como sendo um comentário, e todo o conteúdo da mesma será ignorado. Desta maneira, agora você poderá adicionar comentários, dentro do arquivo de configuração, legal não é mesmo. O simples fato de adicionarmos, uma única linha em todo o nosso código, nos permitiu agora adicionar comentários. Mas voltemos para a questão principal. Nosso código, não é capaz ainda, de tratar os arquivos de tickets negociados, como sendo barras de 1 minuto. Para fazer isto, devemos fazer algumas mudanças no código. Antes de fazer tais mudanças, vamos garantir que o sistema de fato continuará funcionando, conforme estava sendo feito anteriormente. Mas agora com algumas novas funcionalidades. Com isto teremos o seguinte código:

bool SetSymbolReplay(const string szFileConfig)
        {
#define macroERROR(MSG) { FileClose(file); if (MSG != "") MessageBox(MSG, "Market Replay", MB_OK); return false; }
                int     file,
                        iLine;
                string  szInfo;
                char    iStage;
                                
                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;
                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
                                                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(StringFormat("Este arquivo esta declarado na linha %d", iLine));
                                                        break;
                                                case 2:
                                                        if (!LoadTicksReplay(szInfo)) macroERROR(StringFormat("Este arquivo esta declarado na linha %d", iLine));
                                                        break;
                                                case 3:
                                                        break;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                return (!_StopFlag);
#undef macroERROR
        }

Quanto este procedimento é chamado, a primeira coisa que fazemos é inicializar algumas coisas para nosso uso. Reparem que estaremos contando as linhas, e isto é importante, para que o sistema nos diga em que linha se encontra a falha. Por conta disto, temos uma macro que nos permite, ter uma saída genérica em caso de erro, que é utilizada em diversos pontos. Observem que agora, que nosso sistema será baseado em estágios. Isto nos força a primeiro definir de forma explicita, o que será tratado nas próximas linhas. Não fazer isto, será considerado um erro. Neste caso, o primeiro erro, sempre será o zero. Já que indicamos na inicialização do procedimento, que estaremos entrando no estagio zero de analise. 

Este tipo de estruturação, apesar de parecer complicada, torna possível ampliar e de forma bastante rápida, e eficiente, qualquer coisa que desejamos. Já que as adições, dificilmente irão provocar alguma mudança no código anterior. Mas o simples fato de fazer as coisas desta forma, já nos permite utilizar o seguinte arquivo de configuração, usando uma formatação interna conforme é vista logo abaixo:

#Primeiro colocamos as barras previas imagino que 3 arquivos seja o suficiente ....
[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

#Tenho um arquivo de tickets mas vou usa-lo como barras previas ... com isto teremos 4 arquivos de barras
[ Ticks -> Bars]
WINQ21_202108050900_202108051759

#Agora vamos usar o arquivo de tickets negociados para executar o replay ...
[Ticks]
WINQ21_202108060900_202108061759

#Fim do arquivo de configuração...

Ou seja, agora você poderá além de esclarecer o que esta acontecendo, deixando assim as coisas mais simples de serem montadas. Usar uma formatação mais eficaz. Notem que ainda, não implementamos de fato o que queremos. O que estou fazendo, é apenas mostrando, como as coisas podem ficar, bem mais interessantes, apenas fazendo pequenas mudanças no código. Estas nem deram tanto trabalho assim, como você mesmo pode ver.

Mas chega de embromação. Vamos agora de fato, implementar as mudanças necessárias, para usar arquivos de tickets negociados, como sendo barras previas. Antes que você fique ai desesperado, imaginando um código maluco. Devo dizer que o código que precisamos, já esta pronto, e já esta no serviço de replay/simulador. Apenas está no meio, de um grande emaranhado. Temos de fato, é que retirar ele deste covil, e trazer o código para a luz. Esta tarefa, tem que ser feita com bastante atenção. Caso contrário, você pode acabar estragando todo o sistema, que já esta montado e funcionando. Para que você entenda do que estou falando. Veja o código abaixo, que é justamente o que já se encontra no artigo anterior:

bool LoadTicksReplay(const string szFileNameCSV)
{
        int     file,
                old;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate;
                        
        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 = 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;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = m_Ticks.nTicks;
                                        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...");
                        return false;
                }
        }else
        {
                Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                return false;
        }
        return (!_StopFlag);
};

Apesar deste código ser dedicado a leitura, e a armazenagem dos tickets negociados, para que eles possam ser utilizados depois, como replay/simulação. Temos um ponto importante neste código acima, em um dado momento, vamos de fato criar um correspondente a uma barra de 1 minuto. Isto utilizando, os apenas e somente, dados de tickets negociados.

Agora pensem um pouco: O que queremos fazer, não é justamente isto ?!?! Ler um arquivo de tickets negociados e assim criar uma barra de 1 minuto. Para logo em seguida, armazenar esta mesma barra, no ativo utilizado para o replay/simulação. Mas não como um ticket de operação, e sim como um dado, que será visto como sendo uma barra previa ?!?! Pois bem. Então se ao invés de armazenar estes tickets, nos fizermos algumas pequenas mudanças no código acima. E iremos de fato conseguir fazer com que a barra que foi criada, durante o processo de leitura dos tickets negociados, seja colocada dentro do ativo a ser analisado no replay/simulador. Mas como sendo uma barra previa.

Mas temos um pequeno detalhe a ser considerado durante esta tarefa. No entanto, isto será visto já na explicação da implementação. Pois é algo que você somente entenderá, ao ver de fato o código montado e funcional. Mas primeiro vamos ver o código, que irá ser o responsável por fazer a chamada para a transformação dos tickets negociados, em barras previas. Este pode ser visto logo abaixo:

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;
                                
                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;
                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
                                                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("");
                                                        break;
                                                case 2:
                                                        if (!LoadTicksReplay(szInfo)) macroERROR("");
                                                        break;
                                                case 3:
                                                        if (!LoadTicksReplay(szInfo, false)) macroERROR("");
                                                        break;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                        return (!_StopFlag);
#undef macroERROR
        }

Apesar de ter sido feita, uma pequena mudança no sistema de aviso de erro, isto para que as mensagens tivessem um certo padrão. Este não é o objeto de interesse aqui. O real interesse neste código acima, é justamente a chamada que irá fazer a confecção das barras de 1 minuto, a partir dos tickets negociados. Notem que a chamada, é a mesma que já usávamos antes, só que contém um parâmetro extra. Por conta deste simples fato, o de se ter, este parâmetro extra, irá fazer toda a diferença. Evitando assim, que tenhamos que reprogramar toda a função, como você pode ver no código abaixo.

Já que na função original, existe toda a lógica necessária para que o serviço de replay/simulador, consiga de fato utilizar dados contidos em um arquivo de tickets negociados, e transformá-lo em um sistema de barras de 1 minuto. Tudo que precisamos de fato fazer, é tornar estas barras, em barras previas. Mas sem prejudicar o funcionamento do sistema. Já que se você não fizer certos ajustes, irá acabar por gerar coisas estranhas no momento que for utilizar o serviço de replay/simulador. Assim, para que você consiga, de fato entender, como fazer isto, e qual é a minha proposta neste caso, já que não seria o único meio ou método possível. Vamos analisar os pontos, que foram adicionados ao código de leitura dos tickets negociados. De forma, que o sistema consiga utilizar os tickets negociados, como sendo barras previas.

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 = 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;
                                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);
};

A primeira coisa que você pode notar, é que foram adicionadas algumas variáveis extras. Estas variáveis locais, irão armazenar de forma temporária, algumas coisas que são importantes, para que possamos restaurar o status inicial do sistema depois. Caso isto seja necessário. Atenção agora, ao seguinte fato: Todo o código, irá prosseguir de forma completamente idêntica ao original. É importante notar isto. Já que poderíamos ir adicionando as barras de 1 minuto, conforme elas iriam surgindo. Mas preferi fazer a coisa de uma forma um pouco diferente. Claro que tive que fazer uma mudança e esta é justamente em um ponto bem especifico, onde analisamos se os dados serão ou não usados como barras previas. Caso eles venham a ser usados, iremos garantir que a coisa não fique muito estranha.

Assim, garanto que a leitura é feita, sem que aja nenhuma interferência, no que estava sendo feito anteriormente. Ou seja, os tickets negociados serão lidos, e transformados em barras de 1 minuto, nada disto mudou. Mas assim que o arquivo tenha sido totalmente lido, e tudo estiver de acordo com o esperado, passamos a uma nova fase. É neste ponto que a mágica acontece. Caso o arquivo de tickets negociados, tenha sido marcado para servir como um sistema de barras previas, e o usuário não tenha feito um pedido de encerramento do serviço de replay/simulador durante a leitura do arquivo. Teremos uma condição verdadeira. Assim, fazemos algumas coisas bem especificas. de forma a garantir. que os dados sejam utilizados de forma adequada, e que o sistema. seja restabelecido a sua condição inicial. Então não teremos problemas ou condições estranhas, durante a execução na fase de play.

A primeira coisa a ser feita, é alocar um espaço na memória, para colocar as barras de 1 minuto, de forma temporária. Logo depois de ter feito isto, copiamos as barras construídas, durante a leitura do arquivo, para dentro deste ponto temporário. Isto é importante, para que a próxima etapa, que é colocar as barras no ativo de replay/simulação, de fato venha a ter existo. Sem fazer esta movimentação anterior, seria muito complicado de fazer a correta colocação das barras de 1 minuto, dentro do ativo, de forma que elas viessem a ser interpretadas, como sendo barras previas. Lembrando que se fossemos utilizar um método direto, onde as barras seriam adicionadas durante o processo de criação, esta lógica toda não seria necessária. Mas da forma como foi feito. Primeiro lendo o arquivo, para depois gravar as barras como sendo previas. Precisamos destes artificies, para conseguir de fato, fazer a coisa funcionar.

Mas ao alocar e transferir os dados, da forma como foi feita, a coisa se torna bem mais simples. Já que não precisaremos criar, um laço para fazer a transferência. Uma vez que a transferência tenha sido feita, ajustamos o valor da posição limite das barras previas, e repomos os valores que foram armazenados lá no inicio do procedimento de volta. Assim no final, o sistema irá agir, como se nada tivesse ocorrido. Para ele, seria como se a leitura das barras previas, tivesse ocorrido diretamente de um arquivo de barras de 1 minuto, e não de um arquivo de tickets negociados. As barras previas, irão aparecer como o esperado. Por fim liberamos a memória temporária.

Virão que com muito pouco trabalho, conseguimos resolver um grande problema. Mas novamente, esta solução que propôs não é a única possível, mas talvez seja a que envolve, menos mudanças no código original.


Removendo todos os gráficos de replay

Existe um pequeno detalhe, no sistema, que é no momento em que ele é encerrado. Isto NORMALMENTE, se dá, quando fechamos o gráfico, onde se encontra o indicador de controle. Pois bem, o problema não de fato serio, é apenas um pequeno aborrecimento. Muitas pessoas gostam, de ter mais de um gráfico aberto, e isto do mesmo ativo, e ao mesmo tempo. Tudo bem, faz parte, e em alguns cenários, é até mesmo interessante fazer isto. Mas pensem só no seguinte: Quando o serviço de replay/simulação, tenta remover toda e qualquer evidencia do ativo usado, ele simplesmente não irá conseguir fazer isto. O motivo: Tem algum outro gráfico aberto, com o ativo de replay.

Assim ,o sistema não irá conseguir limpar as evidencias. Ficando ali um ativo, na janela de observação de mercado, que não faz sentido algum. Apesar de você poder retirar ele, de lá de forma manual, não é esta a intenção, e não deveria ser assim. O serviço de replay/simulação, deveria remover qualquer evidencia, de forma totalmente automática. Para corrigir isto, teremos que fazer uma mudança no código. Para entender de fato, o que irá ser adicionado. Veja o código original logo abaixo:

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

Observem o seguinte comportamento, neste código: Quando este procedimento é chamado, apenas e somente o gráfico que foi aberto pelo serviço será encerrado. Se não houver nenhum outro gráfico aberto, que esteja referenciando o ativo de replay, este poderá ser removido do janela de observação de mercado. Mas como foi dito, o usuário pode ter feito a abertura de outros gráficos, que utilizam como símbolo, justamente o ativo de replay de mercado. Neste caso, quando tentarmos remover o ativo da janela de observação de mercado, não iremos conseguir fazer isto. Para solucionar este inconveniente, temos que trocar a forma como o serviço é encerrado. Precisaremos apenas modificar as linhas em destaque, para algo um pouco mais complexo, no entanto mais eficiente. O código para se fazer isto pode ser visto logo a seguir:

void CloseReplay(void)
{                       
        ArrayFree(m_Ticks.Info);
        ArrayFree(m_Ticks.Rate);
        m_IdReplay = ChartFirst();
        do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                        ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
        GlobalVariableDel(def_GlobalVariableIdGraphics);
}

Este código pode parecer estranho para grande parte de vocês. Mas o que ele faz é algo bastante simples. Primeiro capturamos qual a ID do primeiro gráfico. Vale notar, que este não necessariamente, é o primeiro que foi aberto. Logo depois, entramos em um laço. Dentro deste laço, temos um teste, em que verificamos qual o ativo que esta sendo referenciado no gráfico. Caso este seja o ativo de replay, iremos fechar o gráfico. Para saber se o laço será ou não encerrado, fazemos um pedido a plataforma, para que ela nos diga, qual a ID do próximo gráfico na lista. Se a lista não contiver mais gráficos, será retornado um valor menor que zero, então o laço irá ser encerrado. Caso contrário, iremos executar mais uma vez o laço. Isto garante, que todas as janelas cujo ativo presente, seja oque estamos usado como replay, serão fechadas. Não importa a quantidade de janelas. Todas serão fechadas. Logo depois, fazermos 2 tentativas de remover o ativo usado como replay, da janela de observação de mercado. O fato de fazer 2 tentativas de remoção, é por conta que quando temos, apenas a janela, que foi aberta pelo serviço de replay/simulação, uma tentativa, já bastará para que o ativo seja removido da janela de observação. Mas quando existia outras janelas, que foram abertas pelo usuário, pode ser necessário, fazermos uma segunda tentativa.

Normalmente uma será o bastante, caso não tenhamos sucesso em remover o ativo, da janela de observação, não conseguiremos remover o símbolo customizado. Mas independente disto, iremos remover qualquer conteúdo presente no ativo customizado, pois ele não tem nenhuma utilidade fora do serviço de replay/simulação. Não queremos que fique nenhum tipo de informação ali. Mesmo que não consigamos remover de fato o ativo customizado, não existirá grandes problemas, já que ele não irá conter absolutamente nada dentro dele. Apesar de que a ideia deste procedimento, é de fato remover ele completamente da plataforma.


Conclusão

No vídeo abaixo, você pode ver o resultado do trabalho feito neste artigo. Se bem, que algumas coisas, não serão de fato vistas. Já que são informações não visuais. Mas ao ver o vídeo, você já terá uma boa ideia do progresso do sistema de replay/simulador, durante cada um destes artigos. É só olhar os vídeos, e comparar as mudanças desde o inicio, até aqui.



No próximo artigo, iremos continuar o desenvolvimento deste sistema. Já que o mesmo ainda não está fazendo algumas coisas, que precisamos de fato.


Arquivos anexados |
Market_Replay.zip (13061.7 KB)
Últimos Comentários | Ir para discussão (5)
fernandomsoares
fernandomsoares | 11 set 2023 em 04:15

Ei Daniel, boa noite !

Daniel, estou com a seguinte situação, eu consigo rodar e debugar o serviço. Mas não consigo debugar o indicador, pois quando rodo o serviço já carrega o executavel do indicador.

Não sei o que estou fazendo de errado, mas pelo que entendi, não dá para rodar o código do indicador para debugar. Seria isto mesmo ? Poderia me dar uma dica ? 

Daniel Jose
Daniel Jose | 11 set 2023 em 19:07
fernandomsoares #:

Ei Daniel, boa noite !

Daniel, estou com a seguinte situação, eu consigo rodar e debugar o serviço. Mas não consigo debugar o indicador, pois quando rodo o serviço já carrega o executavel do indicador.

Não sei o que estou fazendo de errado, mas pelo que entendi, não dá para rodar o código do indicador para debugar. Seria isto mesmo ? Poderia me dar uma dica ? 

Você NÃO está fazendo absolutamente nada de errado.😁👍

De fato o serviço inicializa o indicador de controle, assim que tudo está devidamente preparado para que o usuário venha a dar play no serviço.

Não entendendo bem, o motivo pelo qual você deseja debugar o indicador. Já que nele não é executado praticamente nada. Ele apenas serve como uma forma de interação entre o usuário e o serviço. Já que sem ele seria algo bastante complicado controlar quando queremos dar play ou pausar a execução do serviço. De qualquer maneira você não está fazendo nada de errado, em tentar estudar como o sistema funciona. Mas como dica, lhe aconselho a ler com calma os artigos. Pois neles explico em detalhes como o sistema funciona, o que irá lhe poupar muito esforço tentando entender a forma como as interações acontece. 😁👍

fernandomsoares
fernandomsoares | 1 out 2023 em 15:47

Ei Daniel, bom dia ! 

Daniel, fiz alguns estudos do seu codigo e do desenvolvimento na plataforma Metatrader 5, a fim de conseguir colocar um robo rodando no replay / simulador proposto nos seus artigos.

O Robo se baseia nos fluxo dos dados que estão chegando, isto é, vai contabilizando os ticks que chegam e fazendo calculos, para indicar se executa as operações.

Eu já interagi com este mesmo problema em outros posts, inclusive de antemão, peço desculpas pela insistencia, fato é que estudei mais um pouco e fiz as implementações que julguei necessárias para que o robo (EA) pudesse receber os ticks do serviço de Replay.

Mas estou com o seguinte problema: O robo recebe certinho os primeiros ticks, um a um, mas depois dos primeiros 47 ticks recebidos (coloquei um contador), passa a receber de forma muito espaçada os ticks, e não consegui compreender o porque.

Gostaria de te mostrar as implementações e te pedir uma ajuda para resolver o problema, se possível.

Segue as alterações que fiz:

- Na classe C_Replay (linhas em amarelo):

  - No metodo Event_OnTime:

     - Criei uma variavel do tipo MqlTick para receber o tick que será enviado para o grafico;

     - Após ser enviado a atualização da barra (CustomRatesUpdate), coloquei um codigo para enviar o tick para o grafico e ficar esperando se o mesmo foi processado atraves da variavel global "def_GlobalVariableTick";

inline int Event_OnTime(void)
                        {
                                bool    bNew;
                                int     mili, iPos;
                                u_Interprocess Info;
                                MqlTick TickToAdd[1];
                                static MqlRates Rate[1];
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_Ticks.nTicks) return -1;
                                if (bNew = (_dt != m_Ticks.Info[m_ReplayCount].time))
                                {
                                        _dt = m_Ticks.Info[m_ReplayCount].time;
                                        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;
                                                bNew = false;
                                                m_ReplayCount++;
                                        }
                                        mili++;
                                }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
                                Rate[0].time = m_Ticks.Info[m_ReplayCount].time;
                                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);
                                }
				// Apenas plotará o tick no grafico se o robo tiver criado a variavel "def_GlobalVariableTick"
                                if (GlobalVariableCheck(def_GlobalVariableTick))
                                {
                                	TickToAdd[0]=m_Ticks.Info[m_ReplayCount];
             				Print("Tick enviado: Time: ", TickToAdd[0].time, " Time_msc: ", TickToAdd[0].time_msc, " Bid: ", TickToAdd[0].bid, " Ask: ", TickToAdd[0].ask, " Last: ", TickToAdd[0].last, " Volume: ", TickToAdd[0].volume_real);
                                	GlobalVariableSet(def_GlobalVariableTick, 1); // Quando o EA receber o tick (OnTick) irá alterar para ZERO
             		  		CustomTicksAdd(def_SymbolReplay, TickToAdd);
                                        short ctd=0;
                                	while (GlobalVariableGet(def_GlobalVariableTick) > 0 && (!_StopFlag) && ctd <= 50) { ctd++; Sleep(50) };
                                }
                                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);
                        }


  - Codigo do robo (EA) que recebe o tick para processar:

#define def_GlobalVariableTick       "ReplayTick"
//+------------------------------------------------------------------+
int OnInit()
{
        GlobalVariableTemp(def_GlobalVariableTick);
        Print("Variavel Criada: ", def_GlobalVariableTick);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason) 
{
   GlobalVariableDel(def_GlobalVariableTick);
}
//+------------------------------------------------------------------+
void OnTick() 
{
   MqlTick mTick;
   static int ctd=0;
   SymbolInfoTick(Symbol(),mTick);
   ctd++;
   Print(Symbol(), "   Tick Recebido ", ctd, ": Time: ", mTick.time, " Time_msc: ", mTick.time_msc, " Bid: ", mTick.bid, " Ask: ", mTick.ask, " Last: ", mTick.last, " Volume: ", mTick.volume);
   // .
   // .
   // Codigo de processamento do tick
   // .
   // .
   // Altera o valor da variavel global para zero, para receber o proximo tick
   GlobalVariableSet(def_GlobalVariableTick, 0);
}
fernandomsoares
fernandomsoares | 1 out 2023 em 15:53

continuando ....

Desta forma quando precisasse parar o robo para debugar, o replay estaria esperando até que passasse pela linha que seta a variavel global  "def_GlobalVariableTick" para ZERO;

Entendeu ?

Daniel Jose
Daniel Jose | 3 out 2023 em 12:59
fernandomsoares #:

continuando ....

Desta forma quando precisasse parar o robo para debugar, o replay estaria esperando até que passasse pela linha que seta a variavel global  "def_GlobalVariableTick" para ZERO;

Entendeu ?

O detalhe é que este mecanismo, passou por mudanças. Veja os artigos mais novos, pois neles mostro como os ticks serão lançados de forma correta. Inclusive sendo possível acompanhar os mesmo na janela de observação de mercado, o tal DOM. No momento já estamos com o artigo da parte 28 já publicado em português. Então este código que você está tentando ajustar e manipular já está completamente obsoleto. Você precisa acompanhar os artigos para conseguir acompanhar o desenvolvimento do sistema. Não se apegue a nenhum dos código em nenhum dos artigos, até que a serie esteja completamente publicada. Já que eles irão sofrer diversas mudanças ao logo do tempo, para ser possível cumprir o que é prometido: DESENVOLVER UM REPLAY / SIMULADOR e este deverá possibilitar usar qualquer coisa que você já tenha, ou esteja desenvolvendo para usar em conta DEMO ou conta REAL. Seja no mercado de BOLSA ou FOREX.

NÃO TENTE combinar seu Expert Advisor, indicador ou script ao REPLAY / SIMULADOR, pelo menos por enquanto. Pois ele vai sofrer grandes mudanças ainda no código ... Apenas crie o seu código sem se preocupar com o REPLAY / SIMULADOR ... vai chegar um momento, em que darei uma breve parada no desenvolvimento dele para mostrar como criar alguns modulos de suporte. Quando isto acontecer, ai você poderá começar a integrar seus códigos a ele. E mesmo assim bem devagar, pois quando os modulos que eu mostrar como desenvolver estiverem em um dado nível de funcionalidade, eles serão integrados ao REPLAY / SIMULADOR ... é nesta hora que você deverá de fato entender como todo o sistema estará funcionando. Caso contrário, não conseguirá integrar os seus código ao SERVIÇO DE REPLAY / SIMULADOR.

Então tenha CALMA e estude os artigos, sempre acompanhando o que está sendo desenvolvido... 😁👍

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.
Desenvolvimento de uma DLL experimental com suporte a multithreading em C++ para MetaTrader 5 no Linux Desenvolvimento de uma DLL experimental com suporte a multithreading em C++ para MetaTrader 5 no Linux
Este artigo descreve o processo de desenvolvimento para a plataforma MetaTrader 5 exclusivamente em Linux. O produto final funciona tanto no Windows quanto no Linux sem nenhum problema. Veremos o Wine e o Mingw, ferramentas importantes para o desenvolvimento entre plataformas. O Mingw apresenta threads (POSIX e Win32), que você deve levar em conta ao escolher uma ferramenta adequada. Criaremos também uma DLL para testar o conceito e usá-la no código MQL5, comparando o desempenho das duas implementações de threading. O artigo tem como objetivo ser um ponto de partida para a realização de seus próprios experimentos. Depois de ler este artigo, você será capaz de criar ferramentas para o MetaTrader no Linux.
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.
Mais sobre o sistema Murray Mais sobre o sistema Murray
Os sistemas gráficos de análise de preços são amplamente reconhecidos e apreciados pelos traders. Neste artigo, irei abordar o sistema Murray em sua totalidade, que engloba não apenas os renomados níveis, mas também outras técnicas úteis para avaliar a posição atual do preço e tomar decisões de negociação.