English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 18):  Tiquete e mais tiquetes  (II)

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 18): Tiquete e mais tiquetes (II)

MetaTrader 5Testador | 4 julho 2023, 11:03
601 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tickets e Tickets (I), começamos a ter a possibilidade de ter o gráfico de tickets, sendo plotado na janela de observação de mercado. O que foi algo muito bom. Mas durante aquele artigo, mencionei em alguns momentos, que o sistema contava com algumas falhas. Por conta disto decidi bloquear algumas das funcionalidades do serviço. Isto até que as falhas fossem corrigidas. Pois bem, aqui vamos corrigir várias daquelas falhas, que apareceram quando começamos a ter o gráfico de tickets sendo plotado.

Uma das mais gritantes, e talvez a mais incomoda para mim. É a que esta relacionada ao tempo de simulação, da plotagem das barras de 1 minuto. Quem vem acompanhando e testando o serviço de replay / simulação, deve ter notado que o tempo estará muito longe de ser o ideal. Ainda mais, quando o ativo tem uma determinada liquidez, que faz com que em alguns momentos, possamos ficar alguns segundos, sem que tenha de fato havido nenhum negócio. E desde o começo, tenho tentado presar por isto. Tentando fazer, com que a sensação ao utilizar o replay / simulador, fosse a mesma de que se você estivesse de fato operando um ativo real.

Neste, fica extremamente claro, que as métricas, estão muito longe, do tempo ideal de confecção das barras de 1 minuto. Assim então, a primeira coisa que de fato iremos corrigir, será justamente isto. Corrigir a questão da temporização, não é algo complicado. Por mais incrível que possa parecer, é na verdade até bem simples de ser feito. Porém não fiz a correção no artigo anterior, por que lá o desejo era explicar, como fazer para jogar os dados de tickets, que estavam sendo usados para gerar as barras de 1 minuto no gráfico, para dentro da janela de observação de mercado.

Se fosse feita a correção do temporizador. Aqueles que desejassem saber, como aplicar os tickets reais gravados em arquivo, para dentro do gráfico de observação de mercado, não iriam de fato entender, como isto estava sendo feito. Daquela maneira, focando apenas em mostrar a forma de lançar os tickets, na janela de observação de mercado, acredito ter ficado claro como proceder a fim de conseguir fazer tal coisa. O grande detalhe de fato, foi que não encontrei nenhuma outra referencia de como fazer aquilo. A única referencia era a documentação, e durante a procura encontrei, até mesmo no fórum da comunidade, algumas outras pessoas querendo saber como fazer. Mas não tendo uma resposta, que de fato as ajuda-se a entender como deveria ser o processo. Dai o fato de o artigo anterior, parecer ter acabado em um ponto meio que estranho. Dado a impressão de que eu não soube-se como fazer, para corrigir os problemas que ficaram registrados lá.

Mas aqui vamos de fato fazer as coisas voltarem a funcionar. Ainda não em sua totalidade, já que existem questões, que são bem mais complexas de serem explicadas. Apesar de que a implementação, muitas das vezes, ser relativamente simples. Explicar algumas coisas, que são completamente distintas entre si, mas que de alguma forma, tem algum tipo de ligação. E isto em um mesmo artigo, acho que deixa o artigo muito confuso. E ao invés de esclarecer acaba por complicar ainda mais quem deseja entender como fazer as coisas.

Minha ideia por de trás de cada artigo, é explicar e incentivar as pessoas, a conhecerem e explorarem ao máximo, tanto a plataforma MetaTrader 5, como a linguagem MQL5. E isto muito além do que pode ser visto em códigos espalhados por ai. Quero de fato fazer cada um, produzir, e se sentir motivado, a explorar caminhos nunca antes trilhados. E não ficar sempre fazendo as mesmas coisas. Como se o MQL5 ou o MetaTrader 5 não servisse para mais nada além daquilo que todos fazem uso. Mas vamos voltar ao artigo em si.


Implementando a correção do tempo de confecção da barra de 1 minuto

Vamos começar corrigindo o temporizador. Para fazer isto, vamos mudar um pequeno detalhe. E apenas um pequeno detalhe em todo o código. Esta mudança pode ser vista no código abaixo:

inline bool ReadAllsTicks(const bool ToReplay)
                        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                                string   szInfo;
                                MqlRates rate;
                                
                                Print("Carregando ticks de replay. Aguarde...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        def_Ticks.time_msc = (def_Ticks.time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        def_Ticks.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));                                       
                                        if (def_Ticks.volume_real > 0.0)
                                        {
                                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        }
                                        m_Ticks.nTicks++;
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
                        }

Pronto. Agora o temporizador estará funcionando de forma mais adequada. Você pode estar pensando: Mas como assim ?!?! Não entendi 🤔. O simples fato de retirar uma linha ( Esta esta cortada ) e no lugar dela, adicionar um pequeno calculo, já resolve completamente o problema do temporizador. Mas não é somente isto. Poderíamos também remover o valor do tempo, deixando ele como sendo zero.

Isto iria nos poupar alguns ciclos de máquina, no momento que fossemos adicionar os tickets, ao gráfico da observação de mercado. Mas, ( e este mas realmente me faz pensar ), teríamos que executar um calculo extra. Isto no momento de criar as barras de 1 minuto, que seriam depois plotadas pelo MetaTrader 5. Então no final, acabaríamos tendo que gastar alguns ciclos de máquina, apenas para fazer o cálculo. Sendo que fazendo da forma como esta sendo feito, o gasto seria bem menor.

Por conta desta mudança. Imediatamente poderemos também fazer outra:

inline void ViewTick(void)
                        {
                                MqlTick tick[1];

                                tick[0] = m_Ticks.Info[m_ReplayCount];
                                tick[0].time_msc = (m_Ticks.Info[m_ReplayCount].time * 1000) + m_Ticks.Info[m_ReplayCount].time_msc;
                                CustomTicksAdd(def_SymbolReplay, tick);
                        }

O calculo riscado acima, já não é mais necessário. Uma vez que ele é executado, no momento em que estamos carregando os tickets reais do arquivo. Notem que se este tivesse sido feito, no artigo anterior, muitos não iriam entender, por que os tickets estão aparecendo no gráfico da observação de mercado. Mas, como mencionei, desta forma, acredito ter ficado bem mais claro e simples. O simples fato da mudança mais suave, tornam as mesmas bem mais assimiláveis por todos que vierem a ler estes artigos.

Agora vem uma questão, que talvez faça você ficar preocupado:

Será que em um ativo, de baixa liquidez, onde os negócios podem demorar, vários segundos para de fato ocorrerem, pode vim a " travar o serviço ". Evitando que em caso o encerremos, quando ele estiver parando. O mesmo de fato não venha a ser encerrado ?!?! Isto por conta do temporizador, ficar em modo de espera por vários segundos ?!?!

Esta questão de fato é uma questão valida. Vamos ver por que isto não irá acontecer.

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

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

O real problema, é que no momento que a função Sleep for alcançada, o serviço irá ficar " parado " durante um tempo. E isto é fato. Mas de forma alguma, o serviço não poderá ser encerrado. Ele de fato poderá ser encerrado por um pedido de STOP, quando uma chamada faz a mudança do status do flag Stop. Como eu sei disto ?!?! Pelo simples fato, de ter visto isto, na documentação da função Sleep. Segue o trecho em que isto fica claro.

Observação

A função Sleep() não pode ser chamada por indicadores customizados, porque indicadores são executados na thread de interface e não devem ser atrasados. A função foi incorporada para verificar flag interrompido a cada 0.1 segundos

Desta forma, você não precisará ficar testando, se o serviço foi ou não interrompido. A própria implementação do MetaTrader 5, esta fazendo isto para nos. Algo muito bom, diga-se por sinal. Pois o fato de isto ser feito, já nos poupa bastante trabalho em criar um tipo de testagem, de forma a manter as coisas funcionando e ao mesmo tempo, tendo manter uma interatividade com o usuário.


Implementando as correções no sistema de navegação rápida

Agora vamos resolver o problema, no sistema de navegação. De forma a poder retomar as coisas como eram antes. No entanto existe um pequeno inconveniente, do qual não consegui de fato resolver utilizando apenas e somente o MQL5. E já que não pretendo forçar o uso de uma DLL neste momento. Será preciso que se faça uso de um pequeno detalhe, na plataforma MetaTrader 5. Isto para que as coisas fiquem corretas. Mas não precisa se preocupar achando que é algo muito complicado. Na verdade a coisa a ser feita é bem simples e até de certa forma algo bobo. Mas para entender o que será feito, preciso que você preste atenção ao que será explicado. Pois apesar de a primeira vista ser algo quase intuitivo, pode ser que você não consiga de fato entender, se não prestar atenção.

Mas de uma forma ou de outra, vamos primeiramente ver como a coisa foi codificada.

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.18"
#property description "Serviço de Replay-Simulador para plataforma MT5."
#property description "Este é dependente do indicador Market Replay."
#property description "Para mais detalhes sobre esta versão veja o artigo."
#property link "https://www.mql5.com/pt/articles/11113"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Mini Dolar.txt";      //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M1;             //Tempo gráfico inicial.
input bool              user02 = true;                  //Visualizar a construção das barras.
input bool              user03 = true;                  //Visualizar métricas de criação.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if ((*pReplay).ViewReplay(user01))
        {
                Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
                while ((*pReplay).LoopEventOnTime(user02, user03));
        }
        delete pReplay;
}
//+------------------------------------------------------------------+

Agora temos novamente o sistema, podendo ter a visualização ou não da construção das barras, feitas pelo usuário. Apesar de parecer ter sido uma coisa simples de decidir. Na verdade, esta decisão não cabe apenas a mim. Você pode desativar esta visualização caso queira, não irá fazer nenhuma diferença, em termos de codificação. O motivo é que de uma forma ou de outra, ainda teremos que fazer algo dentro da plataforma MetaTrader 5, caso queiramos de fato, usar a observação de mercado, com um gráfico de tickets. Isto para que o gráfico tenha valores adequados. Mas para o gráfico normal, e as linhas de preço, nenhum tipo de mudança ou intervenção será necessária. Já que a coisa irá se ajustar da forma correta. ( Bem, foi isto que pensei ao fazer as coisas assim. Mas depois você verá que eu estava errado. Existe uma falha de que fato não sei como corrigir. Mas isto será visto em um outro artigo. )

Mas para isto, tive que fazer algumas mudanças na classe C_Replay. A primeira mudança, foi justamente no procedimento de criação das barras. Então veja o código abaixo para entender:

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

                                bool bNew;
                                MqlTick tick[1];

                                if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
                                {                               
                                        if (bViewMetrics) Metrics();
                                        m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                bNew = (def_Rate.tick_volume == 0);
                                def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
                                def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
                                tick = m_Ticks.Info[m_ReplayCount];
                                if (bViewTicks) CustomTicksAdd(def_SymbolReplay, tick);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Foi necessário adicionar um novo argumento a este procedimento. Este argumento, irá ligar ou desligar o envio dos tickets, para dentro da janela de observação de mercado. Mas por que disto ?!?! Isto é por que, de alguma forma, que não consigo explicar, não é possível atualizar o gráfico de tickets, na janela de mercado, ao mesmo tempo que atualizamos o gráfico de barras. Isto no caso de estarmos ajustando o serviço, para começar a executar em um ponto qualquer. Mas durante o uso normal, podemos fazer o envio de tickets, tanto para a janela de observação do mercado, quanto para o gráfico de barras. Sem que de nenhum tipo de problema, o que é muito estranho.

Bem, mas aí você pode pensar: Então não podemos de fato mudar o conteúdo da janela de observação do mercado ?!?! A resposta a esta pergunta é SIM, podemos. Mas não de qualquer maneira. Tudo que de fato podemos e iremos fazer, será remover antigos tickets. Mas isto não vem de graça, pelo menos até que os desenvolvedores da plataforma MetaTrader 5, façam a correção, na questão que envolve o uso de ativos customizados na janela de mercado. Isto por que, os ticks colocados no ativo customizado, não desaparecem da janela, onde podemos ver os ticks customizados. Estranhamente, eles permanecem lá, dificultando o entendimento quando fazemos um movimento de retorno a uma posição mais antiga.

Mas de qualquer forma, a rotina responsável por administrar o sistema de posição, é a que pode ser vista logo abaixo:

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

A única mudança que esta rotina sofreu, frente ao que pode ser vista nas versões anteriores, é justamente este ponto. Apenas uma função que remove os tickets a partir de um determinado ponto no gráfico de tickets, da janela de observação de mercado. Mas por que esta implementação não foi vista antes ?!?! O detalhe é que eu ainda estava tentando, fazer com que os dados fossem atualizados. Isto de forma dinâmica, em ambos os gráficos ( gráfico de barras e de tickets ). Mas não consegui fazer isto, sem que fosse gerado erros e problemas correlacionados ao sistema de atualização. Em um dado momento, simplesmente decidi, que apenas o gráfico de barras seria atualizado. Por conta disto, que esta função agora tem 2 parâmetros. 

Bom, agora que o sistema esta quase da mesma maneira, como estava antes de implementarmos a visualização do gráfico de tickets. E isto fazendo uso da janela de observação de mercado. Irei mostrar uma última coisa, antes de fechar este artigo. Mas espero que você tenha entendido como o replay / simulador funciona, quando estamos trabalhando com dados reais. Pois agora iremos adicionar os tickets na janela de observação de mercado, mas quando os dados são simulados. E este é tema para o próximo tópico.


Usando dados simulados no Market Watch

Já que não quero estender este assunto sobre tickets, na janela de observação de mercado, para um outro artigo. Vamos ver como fazer isto, ou melhor dizendo, vamos ver a minha proposta, para este tipo de coisa. Quando o assunto envolve simulação dos tickets, de uma barra de 1 minuto. A questão aqui, não é tão complicada assim. Ela é na verdade muito mais simples de ser feita, do que foi feito até aqui. Desde que você tenha entendido, todo o restante que foi visto, não terá problema em entender isto daqui.

Diferente do que acontece quando estamos utilizando dados reais de negociação. Quando vamos usar dados simulados, não teremos a principio, alguns tipos de informações. Não que seja impossível de cria-las, mas sim, pelo fato de que ao cria-las, você deve faze-lo, com algum cuidado. As informações das quais estou falando, é quando o preço do último negócio, sai da região limitada pelo BID e ASK. Se você observar com calma o replay, de um dado momento, onde isto ocorreu, irá notar que apesar de acontecer, de fato esta fuga da região limitada pelo BID e ASK. Elas são sempre bem rápidas e de pouca incidência. E de fato, por minha experiência no mercado, este tipo de coisa acontece, quando existe um pico na volatilidade do preço. Mas como eu disse são eventos raros.

NOTA: Então não acredite, de forma ou maneira alguma de que você pode e irá operar sempre dentro do SPREAD. Pois pode acontecer de as vezes o sistema sair de dentro do spread. É importante você saber disto, pois no momento que formos desenvolver o sistema de ordens, esta informação, e o correto entendimento dela, será imprescindível. 

Um fato importante: O preço de fato se desloca para fora do BID e ASK, mas ao ver isto, não significa que houve uma violação ou crash no sistema. Apenas não tivemos o retorno do servidor de negociação nos informando em tempo hábil os novos valores de BID e ASK. Mas se você estiver acompanhando o BOOK irá ver as coisas são um pouco diferentes do que muitos imaginam. Por isto você precisa ter muita experiência com todo o sistema de negociação para de fato saber dos problemas que existem.

Então sabendo disto, você pode até cogitar em colocar no sistema de simulação tais movimentos. Isto tornará a coisa um pouco mais realista. Mas lembre-se, de sempre moderar este tipo de evento. O ideal é que você conheça muito bem o ativo, no qual este tipo de movimento será simulado. Para somente assim, poder fazer com que as ocorrências fiquem, ou cheguem próximo as que de fato ocorreriam em um mercado real. Mas para que você entenda como fazer a inclusão deste tipo de movimento no simulador. Primeiro vamos ver, como deve ser implementado o simulador, para que o preço sempre fique dentro dos limites definidos pelo BID e ASK.

A primeira coisa a ser feita, é adicionar uma nova variável ao sistema.

struct st00
        {
                MqlTick  Info[];
                MqlRates Rate[];
                int      nTicks,
                         nRate;
                bool     bTickReal;
        }m_Ticks;

Esta variável, irá nos ajudar a saber, se os tickets são de dados reais ou de dados simulados. É importante fazer esta distinção, já que não iremos simular, de fato os movimentos de BID e ASK. O que iremos fazer será uma montagem destes limites, baseados no valor do último preço negociado, criado pelo simulador. Mas o principal motivo de fato, é que os valores de BID e ASK, não participam de fato, do valor informado no volume de negócios. Então para não complicar a rotina de simulação, iremos fazer esta manipulação, a fim de gerar o BID e ASK em outro local.

Uma vez que temos esta nova variável. Precisamos inicializada de forma adequada. Então temos dois locais onde ela será de fato inicializada. O primeiro é para indicar que estamos trabalhando com tickets simulados.

                bool BarsToTicks(const string szFileNameCSV)
                        {
                                C_FileBars      *pFileBars;
                                int             iMem = m_Ticks.nTicks;
                                MqlRates        rate[1];
                                MqlTick         local[];
                                
                                pFileBars = new C_FileBars(szFileNameCSV);
                                ArrayResize(local, def_MaxSizeArray);
                                Print("Convertendo barras em ticks. Aguarde...");
                                while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) Simulation(rate[0], local);
                                ArrayFree(local);
                                delete pFileBars;
                                m_Ticks.bTickReal = false;
                                
                                return ((!_StopFlag) && (iMem != m_Ticks.nTicks));
                        }

E o outro local, onde iremos inicializar esta mesma variável, é para indicar que estamos trabalhando com tickets reais.

                datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
                        {
                                int      MemNRates,
                                         MemNTicks;
                                datetime dtRet = TimeCurrent();
                                MqlRates RatesLocal[];
                                
                                MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
                                MemNTicks = m_Ticks.nTicks;
                                if (!Open(szFileNameCSV)) return 0;
                                if (!ReadAllsTicks(ToReplay)) return 0;
                                if (!ToReplay)
                                {
                                        ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
                                        ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
                                        CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
                                        dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
                                        m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
                                        m_Ticks.nTicks = MemNTicks;
                                        ArrayFree(RatesLocal);
                                }
                                m_Ticks.bTickReal = true;
                                                                        
                                return dtRet;
                        };

Agora que já sabemos, se estamos trabalhando com tickets reais ou simulados, podemos começar a trabalhar. Mas antes de passarmos para a classe C_Replay, e começar a ajustar as coisas. Precisamos fazer uma pequena mudança no próprio simulador. Lembre-se do seguinte fato : Quando carregamos os tickets reais, ajustamos o tempo, de forma que o valor no campo de milissegundos, seja ajustado, de forma a representar um momento especifico. No entanto, o simulador ainda não faz este ajuste. Então se você tentar rodar o sistema mesmo modificando a classe C_Replay, não irá conseguir uma real apresentação dos dados simulados. Isto por conta, que o tempo expresso no campo de milissegundos esta incorreto.

Para corrigir isto faremos as seguintes mudanças:

inline void Simulation(const MqlRates &rate, MqlTick &tick[])
                        {
#define macroRandomLimits(A, B) (int)(MathMin(A, B) + (((rand() & 32767) / 32767.0) * MathAbs(B - A)))

                                long     il0, max, i0, i1;
                                bool     b1 = ((rand() & 1) == 1);
                                double   v0, v1;
                                MqlRates rLocal;
                                
                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                max = rate.tick_volume - 1;     
                                v0 = 4.0;
                                v1 = (60000 - v0) / (max + 1.0);
                                for (int c0 = 0; c0 <= max; c0++, v0 += v1)
                                {
                                        tick[c0].last = 0;
                                        tick[c0].flags = 0;
                                        il0 = (long)v0;
                                        tick[c0].time = rate.time + (datetime) (il0 / 1000);
                                        tick[c0].time_msc = (tick[c0].time * 1000) + (il0 % 1000);
                                        tick[c0].time_msc = il0 % 1000;
                                        tick[c0].volume_real = 1.0;
                                }
                                tick[0].last = rate.open;
                                tick[max].last = rate.close;
                                for (int c0 = (int)(rate.real_volume - rate.tick_volume); c0 > 0; c0--)
                                        tick[macroRandomLimits(0, max)].volume_real += 1.0;                                     
                                i0 = (long)(MathMin(max / 3.0, max * 0.2));
                                i1 = max - i0;
                                rLocal = rate;  
                                rLocal.open = rate.open;
                                rLocal.close = (b1 ? rate.high : rate.low);
                                i0 = RandomWalk(1, i0, rLocal, tick, 0);
                                rLocal.open = tick[i0].last;
                                rLocal.close = (b1 ? rate.low : rate.high);
                                RandomWalk(i0, i1, rLocal, tick, 1);
                                rLocal.open = tick[i1].last;
                                rLocal.close = rate.close;
                                RandomWalk(i1, max, rLocal, tick, 2);
                                for (int c0 = 0; c0 <= max; c0++)
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                                        m_Ticks.Info[m_Ticks.nTicks++] = tick[c0];
                                }
#undef macroRandomLimits
                        }

Iremos remover o código riscado, e no local dele, iremos utilizar o código que esta em destaque. Desta forma, o tempo em milissegundos estará compatível com o esperado pela classe C_Replay. Agora podemos passar para ela, e fazer as mudanças de forma a poder visualizar o conteúdo simulado.

Já na classe C_Replay, iremos nos preocupar em fazer as mudanças, em apenas e somente, uma única função. Esta é vista no código abaixo:

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

                                bool bNew;
                                MqlTick tick[1];

                                if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
                                {                               
                                        if (bViewMetrics) Metrics();
                                        m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                bNew = (def_Rate.tick_volume == 0);
                                def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
                                def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
                                if (bViewTicks)
                                {
                                        tick = m_Ticks.Info[m_ReplayCount];
                                        if (!m_Ticks.bTickReal)
                                        {
                                                tick[0].bid = tick[0].last - m_PointsPerTick;
                                                tick[0].ask = tick[0].last + m_PointsPerTick;
                                        }
                                        CustomTicksAdd(def_SymbolReplay, tick); 
                                }
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Aqui temos uma mudança bastante simples. No bem da verdade, ela somente serve para que os valores de BID e ASK, sejam criados e assim apresentados. Mas repare que esta criação, se baseia no valor do ultimo preço negociado, e somente será feita, quando estivermos lidando com valores simulados. Ao executar este código, você terá a visão interna do gráfico gerado pelo sistema de RANDOM WALK. Assim como era feito quando utilizávamos o EXCEL para isto. Um detalhe que vale voltar a mencionar aqui neste momento. Quando expliquei no artigo Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 15): Nascimento do SIMULADOR (V) - RANDOM WALK, que haveria outras formas de fazer aquela mesma visualização, estava me referindo a este modelo. Mas naquele momento, não era adequado dizer como fazer, mas aqui já é outra história.

Se a criação destes valores de BID e ASK, não forem de fato feitos. Você irá poder apenas visualizar, o valor baseado no último preço simulado. Talvez isto já lhe basta. Mas alguns gostam de fato, de observar o valor de BID e ASK. Bem, mas não é de todo adequado, que usemos a coisa desta forma. Já que o fato de que saia negócios, exatamente dentro dos limites BID e ASK, sem que de fato eles venha a ser tocados, indica que na verdade, o que estará ocorrendo no mercado são operações diretas. Onde a negociação se dá sem que o BOOK, ou melhor dizendo, sem que as ofertas apregoadas no book, sejam agredidas. Neste caso, o preço não deveria se deslocar. Apesar de ele se deslocar como estaremos vendo no simulador. Então precisamos corrigir, apenas e somente, esta parte que esta destacada em verde. Isto para que o movimento, seja pelo menos adequado ao que seria de fato esperado.

E para não ficar repetindo todo o código acima. Vamos focar no fragmento em destaque, e modificar o mesmo conforme pode ser visto logo abaixo:

                                if (bViewTicks)
                                {
                                        tick = m_Ticks.Info[m_ReplayCount];
                                        if (!m_Ticks.bTickReal)
                                        {
                                                static double BID, ASK;
                                                
                                                if (tick[0].last > ASK)
                                                {
                                                        ASK = tick[0].ask = tick[0].last;
                                                        BID = tick[0].bid = tick[0].last - m_PointsPerTick;
                                                }
                                                if (tick[0].last < BID)
                                                {
                                                        ASK = tick[0].ask = tick[0].last + m_PointsPerTick;
                                                        BID = tick[0].bid = tick[0].last;
                                                }
                                                tick[0].ask = tick[0].last + m_PointsPerTick;
                                                tick[0].bid = tick[0].last - m_PointsPerTick;
                                        }
                                        CustomTicksAdd(def_SymbolReplay, tick); 
                                }

Riscamos a parte que estava em destaque. E que era o código original, e adicionamos algumas outras coisas. Você precisa notar que os valores BID e ASK, são valores estáticos, e isto é necessário, para que possamos fazer a construção, de um pequeno indicador. Algo bem simples, mas que já será o suficiente para promover uma revolução. E já que ao inicializar o sistema, muito provavelmente tais valores estarão zerados, ( o linkeditor sempre criar uma forma, de que estes valores se inicie zerados ). Teremos inicialmente a execução da chamada, onde a agressão será primeiro no ASK e será criado um canal, bem estreito, com apenas e somente 1 tick de distância. Então até que o ultimo preço negociado, saia deste canal, ele irá se manter.

Como eu disse, é algo bastante simples, porém funcional. Agora pense no seguinte: Você não deve fazer o valor BID, colidir com o valor ASK ( isto no mercado de BOLSA, já no mercado de FOREX a história é outra, mas isto será visto em outro momento ). Quem faz de fato esta colisão, é o valor do último negocio executado. Mas e se fizermos uma pequena mudança no fragmento mostrado acima. Algo bem sutil. De forma que o valor BID ou ASK, mudasse sem que de fato o preço do último negocio, tenha mudado, o que aconteceria ?!?!

Para verificar, iremos modificar novamente o código de forma, que o fragmento iria ficar conforme mostrado abaixo:

                                if (bViewTicks)
                                {
                                        tick = m_Ticks.Info[m_ReplayCount];
                                        if (!m_Ticks.bTickReal)
                                        {
                                                static double BID, ASK;
                                                
                                                if (tick[0].last > ASK)
                                                {
                                                        ASK = tick[0].ask = tick[0].last;
                                                        BID = tick[0].bid = tick[0].last - (m_PointsPerTick * ((rand() & 1) == 1 ? 2 : 1));
                                                }
                                                if (tick[0].last < BID)
                                                {
                                                        ASK = tick[0].ask = tick[0].last + (m_PointsPerTick * ((rand() & 1) == 1 ? 2 : 1));
                                                        BID = tick[0].bid = tick[0].last;
                                                }
                                        }
                                        CustomTicksAdd(def_SymbolReplay, tick); 
                                }

Olha só que coisa curiosa. Ao adicionarmos uma certa aleatoriedade ao sistema, conseguimos ter agora, a inclusão de ordens do tipo direta. Ou seja, ordens que irão acontecer sem que o BID ou ASK, sejam agredidos. O grande detalhe, é que em um mercado real, este tipo de ordens, não é de fato tão comum. Não da forma como o sistema estará mostrando. Mas se você ignorar este fato, já terá um bom sistema, onde em alguns momentos teremos a inclusão de um pequeno spread, entre o BID e o ASK. Ou seja o simulador estará quase se adequando, a uma situação bem mais comum no mercado real. Mas no entanto, o excesso de ordens diretas, é algo a ser observado. No entanto, podemos evitar este excesso, tornando a coisa um pouco menos aleatória.

Para fazer isto, precisamos fazer uma última modificação, que estarei mostrando aqui. Esta pode ser vista logo abaixo:

                                if (bViewTicks)
                                {
                                        tick = m_Ticks.Info[m_ReplayCount];
                                        if (!m_Ticks.bTickReal)
                                        {
                                                static double BID, ASK;
                                                double  dSpread;
                                                int     iRand = rand();
                                                
                                                dSpread = m_PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_PointsPerTick : 0 ) : 0 );
                                                if (tick[0].last > ASK)
                                                {
                                                        ASK = tick[0].ask = tick[0].last;
                                                        BID = tick[0].bid = tick[0].last - dSpread;
                                                        BID = tick[0].bid = tick[0].last - (m_PointsPerTick * ((rand() & 1) == 1 ? 2 : 1));
                                                }
                                                if (tick[0].last < BID)
                                                {
                                                        ASK = tick[0].ask = tick[0].last + (m_PointsPerTick * ((rand() & 1) == 1 ? 2 : 1));
                                                        ASK = tick[0].ask = tick[0].last + dSpread;
                                                        BID = tick[0].bid = tick[0].last;
                                                }
                                        }
                                        CustomTicksAdd(def_SymbolReplay, tick); 
                                }

O que estou fazendo aqui, é controlando o nível de complexidade, no gerado aleatório. Isto é feito de tal maneira, que iremos manter tudo dentro de um certo grau, adequado de imprevisibilidade. De forma que ainda assim, teremos de vez em quando, a incidência de ordens diretas. Mas estas estarão sendo feitas em uma quantidade bem mais controlada. Para fazer isto, bastará que ajustemos estes valores daqui. Ao fazermos o ajuste destes valores, criamos uma pequena janela, onde irá surgir uma possibilidade de um spread, um pouco maior do que o valor mínimo possível. E por consequência, iremos em alguns momentos, ter a incidência de ordens diretas, sendo criadas pelo sistema de simulação. Algo que não era possível nos artigos anteriores, ou até então.


Conclusão

Aqui neste artigo, mostrei como foi feito o sistema de ajuste, e criação de tickets, em um gráfico na observação de mercado. Começamos a fazer isto, no artigo anterior, sem muita pretensão. Acabamos por fim, gerar um sistema se simulação, capaz de gerar até mesmo a simulação de ordens diretas. Isto não era algo, que de forma alguma, passava por minhas metas, ou objetivo a ser feito, ou alcançado pelo sistema de simulação. Apesar de que ainda estamos bastante longe, de que a coisa seja completamente adequada para ser utilizada em alguns tipos de sistema de negociação. Mas isto que foi feito aqui, já é um começo.

No próximo artigo, iremos continuar a nossa saga para a construção de um sistema de simulação / replay de mercado. No arquivo anexo, você terá acesso a 4 ativos diferentes, para poder testar e verificar o funcionamento do sistema. Lembrando sempre, que estarei disponibilizando, tanto os dados de tickets reais, quando de barras previas de 1 minuto, a fim de você ver a diferença, entre os valores simulados e reais. Assim possa começar a analisar as coisas, de uma maneira bem mais profunda. Para entender tudo que foi explicado aqui, você deverá rodar o serviço de replay / simulador, em ambos modos. Ou seja, primeiro olhe como o ativo customizado será apresentando quando for feita a simulação, depois olhe como ele ficará quando for feito o replay. Mas preste atenção a janela de ticks, e não ao gráfico em si. Você noterá que a diferença de fato é GRITANTE. Pelo menos no que diz respeito ao conteúdo na janela de observação de mercado.


Arquivos anexados |
Market_Replay_rvt_18.zip (12899.62 KB)
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 19): Ajustes necessários Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 19): Ajustes necessários
O que de fato vamos fazer aqui, é preparar o terreno, de forma que quando for preciso adicionar algumas novas coisas ao código, isto aconteça de forma suave e tranquila. O código atual ainda não consegue cobrir ou dar cabo de algumas coisas, que serão necessárias para um avanço significativo. Precisamos que tudo seja construído de maneira que o esforço de implementação de algumas coisas seja o menor possível. Se isto for feito adequadamente teremos a possibilidade de ter um sistema realmente bastante versátil. Sendo capaz de se adaptar muito facilmente a qualquer situação que for preciso ser coberta.
Indicadores baseados na classe CCanvas: Preenchendo canais com transparência Indicadores baseados na classe CCanvas: Preenchendo canais com transparência
Neste artigo, abordaremos os métodos de criação de indicadores personalizados que são desenhados usando a classe CCanvas da Biblioteca Padrão no MetaTrader 5. Também discutiremos as propriedades dos gráficos para a transformação de coordenadas. Daremos especial atenção aos indicadores que preenchem a área entre duas linhas usando transparência.
Teoria das Categorias em MQL5 (Parte 4): Intervalos, experimentos e composições Teoria das Categorias em MQL5 (Parte 4): Intervalos, experimentos e composições
A teoria das categorias representa um segmento diversificado e em constante expansão da matemática, que até agora está relativamente pouco explorado na comunidade MQL5. Esta série de artigos tem como objetivo descrever alguns de seus conceitos a fim de criar uma biblioteca aberta e utilizar ainda mais essa seção notável na criação de estratégias de negociação.
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tiquete e mais tiquetes (I) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tiquete e mais tiquetes (I)
Aqui vamos começar a ver como implementar algo realmente bem interessante e curioso. Mas ao mesmo tempo extremamente complicado por conta de algumas questões que muitos confundem. Mas pior do que as confundir, é o fato de que alguns operadores que se dizem profissionais, não fazem ideia a importância de tais conceitos no mercado de capital. Sim, apesar do foco aqui ser programação, entender algumas questões que envolvem operações em mercados, é de extrema valia para o que iremos começar a implementar aqui.