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

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 20): FOREX (I)

MetaTrader 5Testador | 18 julho 2023, 09:32
326 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 19): Ajustes necessários, implementamos algumas coisas, cuja necessidade se mostrava mais urgente. Mas apesar de o foco desde o começo desta serie, ter sido basicamente o mercado de bolsa. Não quero deixar de tentar cobrir o mercado de FOREX também. O motivo da minha falta de interesse inicial no FOREX, se deve ao fato de que as negociações nele, acontecerem continua. Não faria muito sentido, ter um replay / simulador, para testes ou aprendizado.

Você poderia simplesmente utilizar a conta demo para fazer isto. No entanto, existem questões que são próprias deste mercado, e que não são replicadas no mercado de bolsa. Por conta disto, se torna interessante mostrar como fazer os ajustes necessários no sistema, de maneira que você possa utilizar este conhecimento. E quem sabe, adaptar o sistema para outros tipos de mercados, como por exemplo o de criptoativos, caso você tenha acesso aos dados deste mercado especifico.

Assim ficará claro o quanto a plataforma MetaTrader 5, pode ser versátil e adequada para muito mais coisas, do que originalmente os criadores propuseram, a manter e desenvolver. Aqui a sua imaginação, e conhecimento de um dado mercado, é que será o único limitador, para o que se pode fazer.


Conhecendo algumas coisas sobre o mercado de FOREX

A intenção inicial deste artigo, não será cobrir todas as características do FOREX. Mas sim e apenas, adequar o sistema, de forma que você possa fazer no mínimo, um replay de mercado. Já a simulação, ficará para um outro momento. No entanto, caso você não os tenha os ticks, e tenha apenas as barras. Pode com algum trabalho, simular possíveis transações, que possam ter ocorrido no FOREX. Isto até que eu mostre como adaptar o simulador. O fato de se tentar trabalhar com dados vindos do FOREX, dentro do sistema, sem que ele seja modificado. Faz com que ocorra erros de range. Por mais que se tente evitar tais erros, eles sempre irão acontecer. Porém, entretanto e toda via, é possível contornar tais erros, e assim conseguir gerar um replay de FOREX. Mas para fazer isto, teremos que fazer diversos ajustes, e mudar alguns dos conceitos, que vem sendo trabalhados até aqui. Acredito que irá valer a pena, já que isto deixará o sistema, bem mais livre a ponto de conseguir trabalhar com dados bem mais exóticos.

No anexo deste artigo, você irá encontrar um ativo, ou mais conhecido, como par de moedas, do FOREX. Este será com tickets reais, para que você possa visualizar as coisas. Este com certeza não é um mercado simples de trabalhar, com simulações e replay. Apesar do fato de que, você irá ver o mesmo tipo básico de informação, no mercado de FOREX existem algumas peculiaridades próprias. O que o torna interessante de ser observado a analisado.

Este mercado, conta com uma serie de coisas, que são próprias dele. Mas aqui, para poder implementar o sistema de replay. Terei que explicar algumas destas coisas, a fim de que você possa entender, do que estaremos tratando. E do quanto pode ser interessante, conhecer outros mercados.


Como a negociação é FEITA

No mercado de forex, a negociação, normalmente se dá, sem que exista de fato um spread entre o valor BID e ASK. Em grande parte das vezes, estes dois valores poderão ser iguais. Mas como assim ?!?! Como eles podem ser iguais ?!?! Diferente do mercado de bolsa, onde sempre existe um spread entre o BID e o ASK. No forex, não é bem assim. Se bem, que as vezes acontece um spread, e dos grandes. Mas normalmente o valor de BID e ASK poderão ser iguais. Isto já começa a confundir, quem é de mercado de bolsa, e pretende entrar no forex. Já que as estratégias de negociação, muitas das vezes, irá precisar ser modificada tremendamente.

Uma outra coisa, é que no forex, os grandes players são Bancos Centrais. Quem é operador da B3 ( Bolsa do Brasil ), já viu e sabe muito bem, o que o Banco Central, as vezes faz no dólar. Por conta disto, muitos evitam de operar este ativo. Devido ao medo de uma possível intervenção do Banco Central, na moeda. Fazendo com que uma posição, que anteriormente era vencedora, passe rapidamente a ser uma posição fortemente perdedora. Muitos operadores pouco experientes, costumam quebrar nestes momentos. Perdendo todo o capital, sendo em alguns casos inclusive, cobrados de forma judicial pela bolsa e pela corretora. Isto em apenas e somente, uma destas intervenções que o banco central, pode vim a fazer, sem aviso e sem dó de quem esteja posicionado.

Mas para nos isto não importa. O que interessa é o programa em si, então no forex, a plotagem dos preços, se dá, sobre o valor do preço BID. Conforme pode ser visto na figura 01.


Figura 01 

Figura 01 : Plotagem do gráfico no Forex

Que é diferente, por exemplo, do sistema de plotagem da B3. Que utiliza o preço do último negócio realizado. Isto pode ser visto na figura 02 abaixo, onde temos os dados do contrato de dólar vigente, no momento em que este artigo é escrito.

Figura 02 

Figura 02 : Contrato de Mini - Dólar negociado na Bolsa do Brasil ( B3 )

O sistema de replay / simulador, foi desenvolvido a fim de promover o uso deste tipo de analise. Ou seja, quando você utiliza o último preço negociado, teremos uma diferença na forma como as informações dentro do arquivo de tickets negociados estarão. E não somente isto, podemos até mesmo, ter uma grande diferença no tipo de informação, que realmente estará disponível no arquivo de tickets, ou de barras de 1 minuto. Por conta destas variações, irei focar apenas em apresentar a forma de fazer o replay agora. Pois a simulação, envolve outras questões ainda mais complicadas. Mas como foi dito no inicio deste artigo: Você pode utilizar os dados das barras de 1 minuto, para poder simular o que provavelmente pode ter acontecido durante as negociações. Bem, para não ficar, apenas e somente nas palavras. Vamos ver a diferença de informações, entre o mercado de forex e o mercado de Bolsa. No caso a B3, que é a Bolsa do Brasil, para o qual o sistema de replay / simulação tem sido projetado. Na figura 03, temos as informações de um dos pares do forex.

Figura 03

Figura 03 - Informações de tickets reais do mercado de forex

Já na figura 04, temos o mesmo tipo de informações. Só que desta vez, de um dos contratos futuros do mini dólar, que é negociado na B3 ( Bolsa do Brasil ).

Figura 04

Figura 04 : Informações de tickets reais da B3

Ou seja, as coisas são bem diferentes. No forex, não existe os valores de último preço, e nem o de volume negociado. Já na B3, estes valores estão disponíveis, e muitos modelos de negociação, fazem uso exatamente destes valores. O volume negociado e o último preço negociado. Isto tudo que foi dito até aqui, é simplesmente para mostrar, que da forma como o sistema está projetado, não permite atender a outros tipos de mercado, sem que algumas mudanças sejam de fato feitas, Até cogitei separar a coisa, em termos de mercado. Mas de certa forma, isto não seria de fato prático. Não em termos de programação, pois esta separação facilitaria muito a programação. Mas sim em termos de usabilidade, já que você iria sempre precisar ficar ajustando as coisas, para um ou outro mercado. No entanto, podemos tentar chegar ao meio termo. Mas o nascimento do sistema, não ocorrerá sem alguma dor. A verdade, é que irei tentar minimizar ao máximo, tais dores de parto. Já que não quero, e não pretendo, recriar o sistema todo do zero


Iniciando a implementação para cobrir o FOREX

A primeira coisa que precisamos fazer, será corrigir o sistema de numeração de ponto flutuante. Mas o sistema de ponto flutuante, já não esta funcionado, devido ao mudanças feitas no artigo anterior ?!?! Sim. Mas ele não é adequado para o forex. Isto devido ao fato de ele se encontrar travado com a precisão de 4 casas. Precisamos dizer ao sistema, que iremos usar um conjunto com mais casas decimais. Teremos então que corrigir isto, para evitar outros problemas logo depois. Esta correção é feita no seguinte código:

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Exatamente aqui, iremos informa ao MetaTrader 5, que queremos um numero maior de casas decimais, no nosso sistema de numeração de ponto flutuante. No caso iremos utilizar 8 casas, o que é mais que suficiente, para cobrir uma ampla gama de condições. Um detalhe importante: A B3, se dá bem com 4 casas decimais. Mas para trabalhar no FOREX, precisamos de 5 casas. Utilizando 8 deixamos o sistema livre. Mas isto não será assim pelo resto do tempo. Teremos que mudar isto depois, por conta de um detalhe que não dá para explicar aqui no momento. Mas por hora já nos bastará.

Feito isto, vamos começar facilitando a nossa vida de alguma forma. Vamos começar, considerando o seguinte cenário: As barras previas, que estarão presentes no nosso gráfico, serão barras de 1 minuto. Já os tickets, que iremos utilizar, serão tickets reais, presentes em um outro arquivo. Assim voltamos ao sistema mais básico. No entanto, iremos correndo para alcançar um sistema mais amplo.


Trabalhando com o básico

Para que não precisemos forçar o usuário, a selecionar, qual tipo de mercado estará sendo analisado. Ou melhor dizendo: Qual é o tipo de mercado, do qual, os dados vieram, para podermos fazer o replay. Vamos usar o fato, de que em alguns casos, não teremos o valor do último preço, e do volume negociado. E em outros casos, teremos tais valores. Para que o sistema consiga verificar isto para nos. Teremos que adicionar algumas coisas ao código.

Primeiramente vamos adicionar o seguinte:

class C_FileTicks
{
    protected:
        enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
        struct st00
        {
            MqlTick   Info[];
            MqlRates  Rate[];
            int       nTicks,
                      nRate;
            bool      bTickReal;
            ePlotType ModePlot;
        }m_Ticks;

//... Restante do código da classe ...

Esta enumeração, nos ajudará a evitar uma grande confusão em alguns pontos. Basicamente, estaremos reduzindo as coisas, a dois tipos de mercados. Não se preocupe, logo você entenderá o motivo disto. E para evitar um excesso de chamadas a funções, fazemos a adição de uma nova variável no sistema. Agora a coisa começa a ser desenhada. Mas precisamos que o sistema, consiga reconhecer, quando estaremos utilizando um modo, ou outro de plotagem. Não quero complicar a vida do usuário com este tipo de coisa. Para fazer isto, faremos uma pequena mudança 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);
                m_Ticks.ModePlot = PRICE_FOREX;
                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.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));
                        m_Ticks.ModePlot = (def_Ticks.volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
                        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
        }

Começamos dizendo, que o tipo de plotagem, será a do modelo de FOREX. No entanto, se durante a leitura do arquivo de tickets, for encontrado algum ticket, que contenha volume negociado. Este modelo irá ser mudado, para o do tipo BOLSA. É importante que você perceba, que isto estará acontecendo, sem nenhuma intervenção do usuário. Mas agora vem uma questão importante: Este sistema somente irá funcionar, para os casos, em que a leitura será feita, quando o trabalho a ser executado será o de replay. No caso da simulação, a coisa será diferente. Não iremos nos preocupar ainda, com a simulação.

Por este motivo, até que o código de simulação tenha sido criado, você NÃO deverá utilizar, apenas arquivos de barras. Deverá obrigatoriamente, utilizar arquivos de tickets, sejam eles reais ou simulados. Existem formas de criar arquivos de tickets simulados. Mas não irei entrar em detalhes já que isto iria fugir do foco do artigo. Mas não devemos deixar o usuários totalmente as cegas. Apesar do sistema esta conseguindo analisar as coisas. Podemos mostrar para o usuário, qual o tipo de modelagem, que estará sendo feito. Assim, ao abrir a janela de Ativo, ele consiga verificar a forma de plotagem. Da mesma forma como pode ser visto na figura 01 e figura 02.

Para que isto seja possível, deveremos adicionar mais algumas coisas ao nosso código. Entre elas, as linhas mostradas no procedimento abaixo:

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);
        }else
        {
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
        }
        m_Ticks.bTickReal = true;

        return dtRet;
    };

Com a adições destas duas linhas, que estão em destaque, teremos uma informação mais adequada no ativo. Mas quero que você preste bastante atenção, ao que irei explicar, pois se você não entender algum dos conceitos aqui. Irá achar que o sistema estará de sacanagem com você. Na figura 05, você pode ver, o que este código acima faz. Experimente ele com outros ativos para entender. A primeira coisa, é com relação a esta linha especifica. Ela irá mostrar qual será o tipo de calculo que poderemos, ou estaremos usando no ativo. É verdade que existem mais forma de cálculos de ativos. Mas como a ideia aqui, é simplificar a coisa toda, ao máximo, ao mesmo tempo que permitimos que ela funcione. Resumimos tudo, a apenas e somente, estes dois tipos de cálculos.

Quem desejar saber mais detalhes, ou desejar implementar outros tipos de cálculos, pode utilizar a documentação e olhar SYMBOL_TRADE_CALC_MODE. Lá temos uma descrição detalhada, de cada uma das forma de cálculos. Aqui estaremos trabalhando, apenas no modo mais básico da coisa. Agora o detalhe que pode te deixar maluco da vida, e este, é justamente esta segunda linha em destaque. Nesta linha, estamos apenas indicando o tipo de modo de plotagem. Basicamente são apenas, estes dois tipos que existem. A questão aqui, não é esta linha em si. O problema, é no arquivo de configuração, ou melhor, na forma como o arquivo de configuração, esta sendo lido, no atual estágio de desenvolvimento.

Da forma como o sistema esta atualmente codificado. Se você ler um arquivo de barras, e logo depois, ler um arquivo de tickets. Você terá problemas. Não por conta que você estará fazendo algo de errado. Muito pelo contrário. Estará seguindo a lógica correta. No entanto, o fato desta linha linha ser executada, depois que o arquivo de barras tenha sido carregado no gráfico. Faz com que qualquer conteúdo que possa estar presente no gráfico, venha a ser removido. Este problema se deve a falta de uma buferização dos dados, já que eles vão direto para o gráfico. Então temos que resolver isto. No entanto, esta solução, será vista mais para o final do artigo.

Pessoalmente se o sistema fosse apenas para meu uso pessoal. A coisa toda seria feita de forma diferente. Apenas iria gerar algum tipo de alerta, para que o arquivo de ticket, fosse lido antes do arquivo de barras. Mas como o sistema será utilizado, muitas das vezes, por pessoas que não tem conhecimento em programação. Acho adequado resolver este tipo de coisa. É até bom fazer isto, pois você irá aprender a fazer um tipo de truque muito interessante, e bastante legal de ser aprendido.

Figura 05

Figura 05 - Visualizando o reconhecimento automático da leitura de tickets

Agora que o sistema consegue identificar algumas coisas. Precisamos que ele se adapte, em alguns outros aspectos, ao nosso sistema de plotagem.

class C_Replay : private C_ConfigService
{
    private :
        long         m_IdReplay;
        struct st01
        {
            MqlRates Rate[1];
            datetime memDT;
            int      delay;
        }m_MountBar;
        struct st02
        {
            bool     bInit;
        }m_Infos;

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

Bem, para que as coisas seja configuradas, e permaneçam configuradas. Vamos adicionar, neste primeiro momento, esta variável. Esta irá indicar, se o sistema já foi totalmente inicializado. Mas atenção, ao como iremos utilizá-la. Primeiro vamos inicializar ela, com um valor adequado. Isto é feito no seguinte ponto de código:

C_Replay(const string szFileConfig)
{
    m_ReplayCount = 0;
    m_dtPrevLoading = 0;
    m_Ticks.nTicks = 0;
    m_Infos.bInit = false;
    Print("************** Serviço Market Replay **************");
    srand(GetTickCount());
    GlobalVariableDel(def_GlobalVariableReplay);
    SymbolSelect(def_SymbolReplay, false);
    CustomSymbolDelete(def_SymbolReplay);
    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
    CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
    CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
    SymbolSelect(def_SymbolReplay, true);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
    CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
    m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
}

Por que estamos inicializando a variável como sendo falso ?!?! O motivo é que o sistema ainda não foi inicializado. Mas assim que carregarmos o gráfico na tela, ele terá sido inicializado. Então você pode pensar que iremos indicar isto, na rotina que inicializa o gráfico. Certo ??? Errado. Não podemos fazer isto, na rotina que inicializa o gráfico. Temos que esperar, que esta rotina de inicialização retorne, e assim que a próxima rotina for chamada, ai sim, poderemos indicar que o sistema foi inicializado. Mas por que usar tal variável ?!?! O sistema não sabe se já foi ou não inicializado ?!?! Para que esta variável ??? Sim. O sistema sabe que foi inicializado. Mas precisamos desta variável, por um outro motivo. Para ficar claro, vamos ver a rotina que muda o seu estado.

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
        {
                u_Interprocess Info;
                int iPos, iTest;

                if (!m_Infos.bInit)
                {
                        ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
                        m_Infos.bInit = true;
                }
                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, true);
                        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);
        }

Esta rotina acima, nos causa uma certa dor de cabeça. Por conta disto, precisamos da variável que indica se o sistema foi, ou não inicializado. Reparem o seguinte: Na primeira execução desta rotina, a variável, ainda irá esta indicando que o sistema NÃO foi totalmente inicializado. Ele irá termina sua inicialização neste momento. O que estamos fazendo aqui, é forçar com que as linhas corretas de preço, sejam apresentadas na tela. Caso o modo de plotagem identificado pelo sistema, seja o do estilo FOREX. As linhas de preço de BID e ASK, serão apresentadas, e a linha do último preço será ocultada. O contrário acontece, caso o modo de plotagem seja do estilo BOLSA. Neste o BID e ASK, serão ocultadas e a linha de último preço apresentada.

Tudo muito bonito e muito bom, se não fosse o fato de que alguns usuários gostam de fazer uma configuração contrária. Onde mesmo operando com um estilo de plotagem de BOLSA, eles gostam de que as linhas de BID ou ASK, sejam apresentadas. E em alguns casos, ambas as linha. Então caso o usuário pause o sistema, depois de ter configurando as coisas ao seu jeito, e venha novamente a dar play no sistema. O sistema irá ignorar a configuração feita pelo usuário, e retornar a configuração interna do mesmo. No entanto, ao indicar que o sistema já foi inicializado ( e usando uma variável para isto ), ele não irá retornar a configuração interna. Permanecendo assim, da forma como o usuário acabou de configurar.

Mas ai vem uma questão: Por que não fazer esta configuração na rotina ViewReplay ?!?! O motivo é que o gráfico, não estava de fato recebendo esta configuração das linhas. E não é somente isto, temos outros problemas, também bem chatos de serem resolvidos. Precisamos realmente de variáveis extras para nos ajudar. Pura e simples programação não estará resolvendo todos os problemas.


Apresentando as barras

Finalmente chegamos ao ponto, onde podemos apresentar as barras no gráfico. No entanto, se você tentar isto, neste atual momento, irá se deparar com erros de range nas matrizes. Desta maneira, antes de realmente apresentar as barra no gráfico, precisamos fazer algumas correções e ajustes no sistema.

A primeira correção é na rotina 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) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
                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);
        }

Este teste, permite ao sistema saber, se ele deve ou não saltar os primeiros tickets. O problema, é que sem verificarmos se estamos ou não trabalhando com uma modelagem, parecida com a plotagem de bolsa, o laço que o teste habilita, irá falhar. Gerando assim, um erro de range. Entretanto, ao adicionarmos um segundo teste, passaremos por esta etapa. Pois se o modo de plotagem for o do tipo encontrado no forex, o laço não será executado. Assim estaremos tranquilos para a próxima etapa.

Nesta próxima etapa, é que iremos de fato jogar os tickets no gráfico. Aqui a única coisa da qual precisaremos nos preocupar, é informar ao sistema qual será o preço de fechamento da barra, o resto é por conta da rotina de modelagem da barra. No caso, esta será a mesma, tanto para uma plotagem de bolsa, quanto para uma plotagem de forex. O código para fazer isto, pode ser observando logo a seguir:

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

        bool bNew;
        MqlTick tick[1];
        static double PointsPerTick = 0.0;

        if (bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
        {
            PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick);                    
            if (bViewMetrics) Metrics();
            m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
            def_Rate.real_volume = 0;
            def_Rate.tick_volume = 0;
        }
        def_Rate.close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close) :
                                                               (m_Ticks.Info[m_ReplayCount].bid > 0.0 ? m_Ticks.Info[m_ReplayCount].bid : 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)
            {
                static double BID, ASK;
                double  dSpread;
                int     iRand = rand();

                dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 );
                if (tick[0].last > ASK)
                {
                    ASK = tick[0].ask = tick[0].last;
                    BID = tick[0].bid = tick[0].last - dSpread;
                }
                if (tick[0].last < BID)
                {
                    ASK = tick[0].ask = tick[0].last + dSpread;
                    BID = tick[0].bid = tick[0].last;
                }
            }
            CustomTicksAdd(def_SymbolReplay, tick); 
        }
        m_ReplayCount++;

#undef def_Rate
    }

O que esta linha faz, é justamente isto. Ela irá gerar o preço de fechamento da barra, baseado no tipo de plotagem que esta sendo utilizado. Todo o restante da rotina, permanece igual estava antes. Assim conseguimos cobrir o sistema de plotagem do forex. Podendo executar o replay, com dados informados em um arquivo de tick. Visto que ainda, não temos a capacidade de fazer a  simulação.

Você pode estar pensando que o sistema já esta concluído. Mas não. Ainda temos 2 problemas, e bem mais urgentes, antes mesmo, de se quer pensar em simular o forex.

O primeiro problema é que precisamos fazer, com que o arquivo de configuração do replay / simulador, tenha uma certa lógica de criação. Isto forçará o usuário, muitas das vezes, se adequar de forma desnecessária ao sistema. Além deste, temos um outro problema. O sistema de temporizador. O motivo deste segundo problema, é que podemos estar lidando com algum ativo, ou momento de negociação, em que o ativo, pode vim a ficar horas parado. Isto sem que saia nenhum negócio de fato, seja por ele estar em leilão. Seja por ele estar suspenso. Ou por qualquer outro motivo. Não importa, precisamos também corrigir este problema do temporizador.

Já que este segundo problema é mais imediato, e urgente. Vamos começar por ele.


Corrigindo o Temporizador.

O grande e real problema, no sistema e temporizador, é que o sistema, não é capaz de lidar com um tipo de condição, que as vezes acontece, em alguns ativos. Esta condição, pode ser liquidez extremamente baixa. Suspensão de negócios. Leilão, ou qualquer outro motivo. Se por qualquer motivo o arquivo de tickets, indicar para o sistema temporizador, que o ativo, deva ficar em modo de suspensão por 15 minutos, por exemplo. O sistema irá ficar completamente bloqueado, por este tempo.

No mercado real, isto é tratado de uma forma. Normalmente a plataforma poderá nos informar, de que o ativo não esta sendo negociado. Mas mesmo se a plataforma, não nos der esta informação. Ela ainda assim irá receber uma notificação do mercado. Operadores mais experientes, vão ao observar o ativo, e notar que algo aconteceu, e de que não a nada a se fazer, durante aquele período. Agora já no caso de estarmos utilizando o replay, este tipo de coisa é um tormento. Temos que permitir que o usuário, encerre ele, ou tente mudar a posição, na qual o replay ou simulação esta sendo executada.

Este tipo de coisa já foi implementada antes, tanto que você pode usar o indicador de controle, para fazer isto. Mas não havia precedentes, que nos forçava a tomar uma medida, tão mais radical. Isto a ponto, de poder fazer um replay, sair de uma condição onde o ativo se encontrava em leilão, ou a liquidez estava muito baixa, ou mesmo no caso do ativo estar suspenso, por conta de fato relevante. Tudo isto gera o chamado risco de liquidez, mas no sistema de replay / simulação, podemos fugir deste risco de forma relativamente fácil, e assim continuar a fazer os nossos estudos. No entanto, para de fato fazermos isto, precisaremos mudar a forma como o temporizador funciona.

Abaixo temos o novo loop do sistema de plotagem. Sei que é um código aparentemente confuso a primeira vista, mas vamos observa-lo com calma.

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);
        iPos = 0;
        while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
        {
            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);
            iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
            CreateBarInReplay(bViewMetrics, true);
            if (m_MountBar.delay > 400)
            while ((iPos > 200) && (!_StopFlag))
            {
                if (ChartSymbol(m_IdReplay) == "") break;
                if (ChartSymbol(m_IdReplay) == "") return false;
                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(195);
                iPos -= 200;
                Sleep(m_MountBar.delay - 20);
                m_MountBar.delay = 0;
            }
        }                               
        return (m_ReplayCount == m_Ticks.nTicks);
    }

Todas as partes riscadas, foram substituídas por outros códigos. Desta maneira, poderemos resolver esta primeira questão referente a plotagem das barras. Anteriormente, utilizávamos um calculo para trás. Agora vamos utilizar um calculo para frente. Isto evita algumas coisas estranhas no gráfico, enquanto estamos no modo de espera. Atenção, ao fato de que quando iniciamos o sistema, este irá sempre esperar um tempo antes de apresentar o ticket. Anteriormente estava sendo feito justamente o contrário (FALHA MINHA). Agora por conta que o tempo pode ser extremamente longo, podendo chegar a ser de horas. Temos uma outra forma de efetuar a temporização. Para melhor entender, você precisa saber, que antes o sistema ficava no modo de espera, até que todo o tempo fosse finalizado. Se você fizesse, ou tenta-se fazer qualquer mudança. Seja fechando o gráfico, seja tentando mudar o ponto de execução. O sistema, simplesmente não respondia da forma esperada. O motivo é que naqueles arquivo que estavam sendo postados em anexo, não haveria o perigo, de você usar um conjunto, onde o ativo poderia ficar um bom tempo, sem ter nenhum negócio. Mas quando este artigo começou a ser escrito, o sistema mostrou esta falha. Desta forma, as correções começaram a se dar.

Agora o temporizador irá executar até, ou enquanto, o período for maior do que 200 milissegundos. Você pode mudar este valor, mas não se esqueça, de mudar ele também nestes outros pontos. Assim, já começamos a corrigir as coisas, mas apesar de tudo, precisamos fazer uma outra coisa, antes. Caso você fecha-se o gráfico, o sistema saia do laço. Ok. Agora ele retorna ao chamador. Isto garante que as coisas irão funcionar, pelo menos em tese. Isto por conta que o usuário pode vim a interagir novamente com o sistema. As demais funções, praticamente se mantiveram intactas, de forma que tudo continua funcionando como antes. No entanto, se você durante o período de temporização, pedir ao indicador de controle, para mudar a posição. Agora será possível fazer isto, antes não era. E isto é muito bom, já que alguns ativos podem entrar em modo de suspensão, e ficar assim por um período bastante longo. O que em uma negociação real é tolerável, mas para um sistema de replay / simulação: NÃO É.


Conclusão

Apesar de todos os percalços, você já poderá começar a experimentar a usar dados do FOREX no sistema. Isto, a partir desta versão do Replay / Simulador. Para poder experimentar isto, no anexo, você terá acesso a alguns dados de FOREX. O sistema ainda precisa de correções em alguns de seus pontos. Mas já que não quero entrar em detalhes, por talvez isto venham a demandar uma mudança radical em pontos mostrados neste artigo. Vou finalizando as modificações por aqui.

No próximo artigo, irei resolver mais alguns problemas, que ficaram em aberto. Não que estes impeçam você de utilizar o sistema. No entanto, se você for utilizar ele com dados vindos do mercado de forex, irá notar que algumas coisas, não estão sendo corretamente apresentadas. Precisamos corrigi-las minimamente, mas isto será visto no próximo artigo.

Ainda ficou faltando resolver o problema do arquivo de configuração, além de outros problemas relacionados a ele. Como o sistema esta funcionando de forma adequada, a ponto de que você possa fazer um replay de dados obtidos no mercado de forex. A solução destes problemas pendentes ficarão para o próximo artigo.


Arquivos anexados |
Market_Replay_yvg20.zip (14386.04 KB)
Encontrando padrões de velas usando MQL5 Encontrando padrões de velas usando MQL5
Neste artigo, falaremos sobre como detectar automaticamente padrões de velas usando MQL5.
Teoria das Categorias em MQL5 (Parte 5): Equalizadores Teoria das Categorias em MQL5 (Parte 5): Equalizadores
A teoria das categorias é um ramo diversificado e em expansão da matemática que só recentemente começou a ser abordado na comunidade MQL5. Esta série de artigos tem como objetivo analisar alguns de seus conceitos para criar uma biblioteca aberta e utilizar ainda mais essa maravilhosa seção na criação de estratégias de negociação.
Implementando o fator Janus em MQL5 Implementando o fator Janus em MQL5
Gary Anderson desenvolveu um método de análise de mercado baseado em uma teoria que chamou de fator Janus. Essa teoria descreve um conjunto de indicadores que podem ser usados ​​para identificar tendências e avaliar o risco de mercado. Neste artigo, vamos implementar essas ferramentas no MQL5.
Uso de modelos ONNX em MQL5 Uso de modelos ONNX em MQL5
O ONNX (Open Neural Network Exchange) é um padrão aberto para a representação de modelos de redes neurais. Neste artigo, consideraremos o processo de criação do modelo SNN-LSTM para previsão de séries temporais financeiras e o uso do modelo ONNX criado em um Expert Advisor MQL5.