English Русский 中文 Español Deutsch 日本語
preview
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

MetaTrader 5Testador | 10 julho 2023, 17:31
295 0
Daniel Jose
Daniel Jose

Introdução

Nos artigos anteriores nesta mesma serie, acredito ter ficado claro, que precisamos implementar algumas coisas extras. Mas que porém são de extrema necessidade, para nos ajudar a organizar um pouco melhor as coisas, isto pensando em algumas melhorias que terão que ser ainda feitas. Se bem, que se você for utilizar o sistema, de replay / simulação, para trabalhar em apenas e somente um único ativo, muitas das coisas que iremos implementar, não precisam ser de fato efetivadas. Podendo ser deixadas de lado.  Ou melhor dizendo, não irão precisar necessariamente aparecer no que será o arquivo de configuração.

Mas, e isto é muito provável que você não venha a usar apenas um ativo, e sim diversos ativos diferentes. Ou mesmo uma base de dados bastante ampla. Desta forma se torna necessário organizar um pouco as coisas. E assim, será necessário implementar algum código extra apenas para isto. Se bem que em alguns casos, bem específicos poderemos simplesmente utilizar o que já está disponível. Ou seja, já foi implementado e se encontra no código fonte, porém está em forma implícita. Precisando apenas ser removido da obscuridade para a luz.

Mas como gosto sempre de deixar as coisas o mais organizadas, quanto for possível. Acredito que muitos também pensem e tentam fazer a mesma coisa. Será bom, e bastante adequado, saber e entender, como fazer para implementar tal funcionalidade. De quebra, você irá acabar aprendendo, como adicionar novos parâmetros no sistema. Caso eu não venha a cobrir um parâmetro especifico, que você precise de fato ter em um ativo particular, em que você deseje usa a fim de fazer algum estudo, ou mesmo algum tipo de analise.

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. Uma destas coisas será o tema do próximo artigo. Mas felizmente graças aos dois últimos artigos, onde mostrei como adicionar os tickets a janela de observação de mercado, a coisa toda já está bem encaminhada. Quem por um acaso não tenha visto estes artigos, pode acessá-los por meio dos seguintes links:  Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tickets e Tickets (I) e o outro é Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 18): Tickets e Tickets (II). Estes dois artigos tem informações preciosas para o que iremos fazer nos próximos artigos.

No entanto, ainda falta alguns detalhes bem específicos e estes serão implementados neste artigo. Além de outros bem complexos, que exigirão outros artigos apenas para explicar como iremos trabalhar neles, a ponto de resolver as devidas pendencias. Então vamos iniciar a implementação do sistema que será visto neste artigo. Começaremos com a parte, onde iremos melhorar a organização dos dados utilizados.


Implementando um sistema de diretórios.

A questão aqui, não é de fato, se precisamos ou não, implementar este sistema. Mas sim por que implementar este sistema. Você pode, no atual estágio de desenvolvimento, usar um sistema de diretórios. Mas, porém, todavia e entretanto, você terá muito mais trabalho ao implementar as coisas, da forma como o serviço de replay / simulador esta no atual estágio. Não que isto seja inviável, mas o trabalho é consideravelmente muito maior, do que simplesmente adicionar uma nova variável no arquivo de configuração. Para que você entenda de fato, do que estou falando. Veja as figuras abaixo:

Figura 01

Figura 01 - Forma de acessar diretórios no sistema atual.


Figura 02

Figura 02 - Forma alternativa de acessar diretórios


Apesar da figura 01, ter o mesmo comportamento da figura 02. Isto pelo ponto de vista do sistema de replay / simulação. Você logo nota que é bem mais prático configurar as coisas, ao utilizar um arquivo de configuração, que tem como base a figura 02. Isto por que bastará você informa apenas e somente, uma única vez qual é o diretório onde os dados estarão, e todo o resto é feito pelo sistema de replay / simulação. O fato de podermos utilizar a figura 02, evita que em caso estejamos usando uma base de dados muito extensa, para poder cobrir o uso de uma média móvel, por exemplo. venhamos a esquecer ou cometer algum erro, no momento que estivermos indicando, onde os dados devem ser buscados. Se você fizer isto, ( cometer um erro de digitação ), pode acontecer duas situações:

  • A primeira é o sistema simplesmente emitir um aviso, informando que os dados não poderão ser acessados. 
  • E a outra situação, e esta mais grave, você utilizar dados incorretos.

Mas tendo a possibilidade de ajustar o diretório em um único local, este tipo de falha se torna mais difícil de acontecer. Não que isto não irá de fato acontecer, mas será mais difícil. Lembrando que você pode separar as coisas em diretórios, ainda mais específicos, fazendo assim uma combinação entre a figura 01 e a figura 02. Mas aqui irei manter a coisa em um nível mais básico. No entanto, sinta-se a vontade em implementar a coisa toda, de uma maneira a cobrir os dados de uma forma que lhe seja mais adequada, e voltada ao seu estilo de organizar as coisas.

Tudo bem, mensagem dada hora de ver como fazer isto na prática. A forma de fazer isto é relativamente simples, e fácil de se entender. Isto perto do que ainda teremos que fazer de fato. Primeiramente, vamos criar uma nova variável privativa da classe. Esta é vista no fragmento abaixo:

private :
    enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
    string m_szPath;

Ao adicionar esta variável nesta posição, ela ficará visível para todos os procedimentos internos da classe. Mas no entanto, não poderá ser acessada fora dela. Isto garante que ela não será modificada de forma indevida ( pelo menos em principio ). Isto por que você pode em algum procedimento interno da classe, modificar o valor da variável, sem de fato notar isto. E ao fazer tal coisa, pode acabar tendo dificuldades em entender por que o código não está funcionando como esperado.

Feito isto, temos que dizer a nossa classe, para começar a reconhecer um novo comando dentro do arquivo de configuração. Este procedimento é feito em um ponto bem especifico, mas pode variar dependendo do que você esteja adicionando, mas no nosso caso, será feito no procedimento visto logo a seguir:

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "POINTSPERTICK",
                                "PATH"
                                };
        string  szRet[];
        char    cWho;
                
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_PointsPerTick = StringToDouble(szRet[1]);
                    return true;
                case 1:
                    m_szPath = szRet[1];
                    return true;                                    
            }
            Print("Variável >>", szRet[0], "<< não definida.");
        }else
            Print("Definição de configuração >>", szInfo, "<< invalida.");
                                
        return false;
    }

Vejam como a coisa é muito mais simples de ser feita, quando todo o código está sendo estruturado para receber melhorias. Mesmo assim, você tem que tomar alguns cuidados. Se tais cuidados forem tomados, você não terá problemas para adicionar qualquer tipo de coisa que vier a precisar ser feita pelo código.

A primeira coisa que fazemos, é adicionar o nome, ou o rotulo do comando, a ser usado no arquivo de configuração, dentro da matriz de dados sequenciais. Mas notem o detalhe de que este deve estar todo em maiúsculo. Até daria para fazer as coisas serem case-sensitive, mas isto iria dificultar para o usuário, no momento que ele fosse digitar e colocar o comando no arquivo de configuração. Se você for o único a usar o sistema, e queira usar o mesmo rotulo, mas com significados diferentes, talvez seja uma boa ideia usar o sistema case-sensitive. Caso contrário este tipo de ideia, pode apenas complicar todo o trabalho de maneira completamente desnecessária. Eu mesmo acho que usar o mesmo rotulo, para significados diferentes só serve para complicar a nossa vida. Por conta disto não faço tal uso.

Uma vez que o rotulo tenha sido adicionado a matriz de comandos. Temos que implementar a sua funcionalidade. Isto é feito exatamente neste ponto. Simples assim. Já que ele é o segundo da cadeia, e ela inicia no zero. Usamos o valor 1 para indicar que estaremos implementando aquela funcionalidade especifica. Já que a ideia é apenas e somente, ter o nome do diretório, o comando é bem simples. E para encerrar, retornaremos o valor verdadeiro para o chamador. Informando assim que o comando foi reconhecido e implementado com sucesso.

A sequencia para fazer qualquer adição no sistema, é justamente esta que foi mostrada. Uma vez que isto tenha sido feito, podemos utilizar os dados que foram informados dentro do arquivo de configuração. Porém existe um ponto que esqueci de informa, é algo tão simples, mas que você deve sempre ficar atento. Pois pode em alguns casos, dar a impressão que um novo recurso adicionado, é que esta gerando problemas. Quando na verdade, pode simplesmente ser o fato de que ele não tenha sido corretamente inicializado. No caso, sempre que você adicionar uma variável global privativa, cuide para que ela seja inicializada corretamente no constructor da classe. Você pode ver isto sendo feito no código abaixo, onde inicializamos a nova variável.

C_ConfigService()
	:m_szPath(NULL)
	{
	}

Ao fazer isto, garantimos que teremos um valor conhecido em uma variável que ainda não teve nenhum valor atribuído a ela. Este tipo de coisa pode parecer bobagem em alguns cenários. Mas evita grandes problemas e perda de tempo em outros, além é claro ser considerada uma boa pratica de programação. Depois que este trabalho tenha sido feito. A variável inicializada no constructor da classe. E já termos colocado uma forma de atribuir um valor a ela, dentro do que é informado no arquivo de configuração. Chegou a hora de usar este valor. E este será utilizado, em apenas e somente, um único procedimento. Que é justamente o responsável por tratar o carregamento da base de dados.

Então vamos ver como isto foi implementado:

bool SetSymbolReplay(const string szFileConfig)
    {
        #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)
        int        file,
                iLine;
        char    cError,
                cStage;
        string  szInfo;
        bool    bBarsPrev;
        C_FileBars *pFileBars;
        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
            Print("Falha na abertura do arquivo de configuração [", szFileConfig, "]. Serviço sendo encerrado...");
            return false;
        }
        Print("Carregando dados para replay. Aguarde....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        iLine = 1;
        cError = cStage = 0;
        bBarsPrev = false;
        while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
        {
            switch (GetDefinition(FileReadString(file), szInfo))
            {
                case Transcription_DEFINE:
                    cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
                    break;
                case Transcription_INFO:
                    if (szInfo != "") switch (cStage)
                    {
                        case 0:
                            cError = 2;
                            break;
                        case 1:
                            pFileBars = new C_FileBars(macroFileName);
                            if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true;
                            delete pFileBars;
                            break;
                        case 2:
                            if (LoadTicks(macroFileName) == 0) cError = 4;
                            break;
                        case 3:
                            if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true;
                            break;
                        case 4:
                            if (!BarsToTicks(macroFileName)) cError = 6;
                            break;
                        case 5:
                            if (!Configs(szInfo)) cError = 7;
                            break;
                    }
                break;
            };
            iLine += (cError > 0 ? 0 : 1);
        }
        FileClose(file);
        switch(cError)
        {
            case 0:
                if (m_Ticks.nTicks <= 0)
                {
                    Print("Não existem ticks para serem usados. Serviço esta sendo encerrado...");
                    cError = -1;
                }else if (!bBarsPrev) FirstBarNULL();
                break;
            case 1  : Print("O comando na linha ", iLine, " não é reconhecido pelo sistema...");    break;
            case 2  : Print("O sistema não esperava o conteudo da linha ", iLine);                  break;
            default : Print("Existe um erro na linha ", iLine);
        }
                                                        
        return (cError == 0 ? !_StopFlag : false);
#undef macroFileName
    }

Por conta que estaremos utilizando a coisa, de uma forma única, e ao mesmo tempo, em mais de um ponto diferente. Preferi utilizar uma macro para ajudar na codificação. Todos os pontos em amarelo, irão receber exatamente o código que esta contido na macro. Isto facilita muito as coisas, a fim de não precisar ficar digitando a mesma coisa diversas vezes, e correr o risco de gerar algum tipo de bug, no caso de manutenção, ou modificação de um código que é usado em diversos pontos diferentes. Mas vamos olhar um pouco melhor o que a macro esta fazendo.

#define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)

Você se lembra que inicializamos a variável com um determinado valor ?!?! Pois bem, no momento em que formos tentar utilizar a variável, iremos justamente testar isto, qual o valor que ela contém. Caso ele seja aquele com que inicializamos no constructor, teremos um determinado caminho. Caso seja o que foi encontrado no arquivo de configuração, teremos outro caminho. Mas de uma forma ou de outra, no final teremos o nome do qual poderemos usar para acessar o arquivo.

Este sistema é tão versátil, que você pode mudar o diretório a qualquer momento. Isto sem precisar mexer em mais nada dentro do sistema, já pronto e compilado. Motivo pelo qual não irá necessitar recompilar todo o código, apenas para ajustar algo que poderá ser feito ajustando o arquivo de configuração. A única coisa que você deverá fazer, para mudar o diretório que deseja trabalhar, é usar o seguinte fragmento dentro do arquivo de configuração:

[Config]
Path = < NEW PATH >

Onde < NEW PATH > irá conter o novo diretório, a ser utilizado a partir daquele ponto dentro do arquivo de configuração. Algo realmente bastante agradável. Isto por que, o trabalho será imensamente reduzido, ao se trabalhar com banco de dados que podem conter uma arvore de diretórios. Lembre-se que você deve criar as coisas de maneira a facilitar organizar e catalogar os dados. Assim será consideravelmente mais simples encontrar o arquivo que você procura.

Feito isto podemos passar para o próximo passo, onde iremos ajustar mais alguns detalhes que precisam ser implementados. Este é visto no próximo tópico.


Ajustando os dados do ativo customizado

Para implementar o nosso sistema de ordens, iremos precisar incialmente de 3 valores básicos, são eles : Volume Mínimo, Valor do ticket e Tamanho do ticket. Porém destas informações, apenas uma se encontra atualmente implementada. No entanto, sua implementação não está exatamente como deveria ser. Pois pode acontecer de que o valor não tenha sido configurado no arquivo de configuração. O que dificulta muito o trabalho de você criar um ativo sintético, apenas voltado para podermos simular prováveis movimentos de mercado.

Se você não fizer esta configuração, o sistema pode ficar com dados inconsistentes no momento que, viermos a começar a trabalhar no sistema de ordens. Precisamos garantir que de fato tais dados estejam devidamente configurados. Isto para que não venhamos a nos enrolar tentando implementar algo em um sistema já densamente codificado. Então iremos começar corrigindo o que está mal configurado, para que na próxima etapa do nosso trabalho não tenhamos problemas. Se viermos a ter problemas, que estes sejam de uma outra natureza. Pois o sistema de ordens, não irá de fato entrar em contato com o serviço, que esta sendo utilizado para gerar o replay / simulador. A única informação com que ele entrará em contato, é com o gráfico e o nome do ativo, e somente isto. Pelo menos esta é a minha intenção neste momento. Não sei se isto de fato será possível de ser feito.

Pensando desta forma, a primeira coisa que precisamos fazer, será inicializar os 3 valores, que precisamos obrigatoriamente. Porém estes estarão com dados zerados. Mas vamos com calma. Primeiro vamos corrigir o problema que estamos tendo. Para fazer isto teremos o 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.0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Aqui colocamos com um valor inicial zero, mas como bônus. Damos também uma descrição para o nosso símbolo customizado. Isto não é obrigatório, mas pode ser interessante caso você abra a janela, que contém a lista de ativos, e veja lá, um com um nome peculiar. Bem, como você deve ter notado, e pode estar pensando, nos não iremos mais utilizar aquela variável, que existia anteriormente. Uma tal que era declarada em um local especifico visto no código abaixo:

class C_FileTicks
{
    protected:
        struct st00
        {
            MqlTick  Info[];
            MqlRates Rate[];
            int      nTicks,
                     nRate;
            bool     bTickReal;
        }m_Ticks;
        double       m_PointsPerTick;
    private :
        int          m_File;

Todos os pontos na qual esta variável aparecia, agora deverão referenciar o valor contido e definido no ativo. Assim agora teremos um novo código. Mas basicamente este valor era de fato referenciado em dois locais em todo o sistema de replay / simulação. O primeiro é visto logo abaixo:

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
    {
        double vStep, vNext, price, vHigh, vLow, PpT;
        char i0 = 0;

        PpT = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
        vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / PpT);
        vHigh = rate.high;
        vLow = rate.low;
        for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
        {
            price = tick[c0 - 1].last + (PpT * ((rand() & 1) == 1 ? -1 : 1));
            price = tick[c0].last = (price > vHigh ? price - PpT : (price < vLow ? price + PpT : price));
            switch (iMode)
            {
                case 0:
                    if (price == rate.close)
                        return c0;
                        break;
                case 1:
                    i0 |= (price == rate.high ? 0x01 : 0);
                        i0 |= (price == rate.low ? 0x02 : 0);
                        vHigh = (i0 == 3 ? rate.high : vHigh);
                        vLow = (i0 ==3 ? rate.low : vLow);
                        break;
                case 2:
                    break;
            }
            if ((int)floor(vNext) < c1)
            {
                if ((++c2) <= 3) continue;
                vNext += vStep;
                if (iMode == 2)
                {
                    if ((c2 & 1) == 1)
                    {
                        if (rate.close > vLow) vLow += PpT; else vHigh -= PpT;
                    }else
                    {
                        if (rate.close < vHigh) vHigh -= PpT; else vLow += PpT;
                    }
                } else
                {
                    if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + PpT); else vHigh = (i0 == 3 ? vHigh : vHigh - PpT);
                }
            }
        }

        return pOut;
    }

Já que não quero ficar repetido o mesmo código em vários pontos, usamos uma variável local para nos ajudar. Mas o principio é o mesmo, referencia um valor que se encontra definido dentro do ativo. Agora o segundo ponto onde este valor é referenciado, é justamente na classe C_Replay. Mas lá por motivos práticos faremos algo um pouco diferente do que foi visto acima. Diferente do momento em que estamos criando um random walk. A apresentação e uso das informações no gráfico, tende a gerar um certo nível de degradação na performance. Isto por conta de um excesso de chamadas desnecessárias. Isto por conta que no momento que o random walk, esta sendo criando, cada barra irá gerar 3 chamadas para ele.

Mas uma vez que ele tenha sido criado, pode haver milhares de tickets presentes. Isto tendo sido criados apenas 3 chamadas. No momento de apresentação e montagem do gráfico, isto tende a degradar a performance levemente. Mas vamos entender como isto realmente se daria na prática. Quando estamos usando um arquivo de tickets reais, ou seja fazendo um replay, tal degradação não ocorreria. Dado o fato de que ao utilizar dados reais, nenhuma informação extra seria necessária, para que o sistema viesse a montar as barras de 1 minuto. Assim como repassar as informações para o gráfico de tickets, na janela de observação de mercado. Algo que foi visto nos dois artigos anteriores.

Mas quando vamos utilizar barras de 1 minuto, para gerar os tickets, ou seja fazer uma simulação. Precisaremos saber qual o tamanho do ticket. Para que esta informação possa ajudar ao serviço, a criar um modelo de movimento, que seja de fato adequado. Esta movimentação será vista na janela de observação de mercado. Mas a criação das barras, não necessita de tal informação. Isto por que a conversão, foi efetuada na classe C_FileTicks.

Sabendo deste detalhe, devemos então considerar a função que criar tal gráfico. E assim, verificar qual é o número de acessos, que ele irá sofrer durante a execução. No caso de estarmos fazendo a simulação, esta função é vista logo abaixo:

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 (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;
        }
        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)
                        {
                                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
        }

Aqui declaramos uma variável estática local. Esta irá servir para evitar um excesso de chamadas a função, que captura o tamanho dos tickets. Esta captura irá acontecer apenas e somente um única vez, durante todo o tempo de vida, e execução do serviço, Mas esta variável, será utilizada apenas e somente nestes locais. Então de certa forma, não faz sentido estender para fora desta função. Mas observem que este local, onde a variável de fato é utilizada, somente será acessado se estivermos utilizando o modo de simulação. No modo replay, esta variável não tem de fato nenhuma serventia prática.

Com isto resolvemos o problema do tamanho do ticket. Ainda faltam dois outros problemas para serem resolvidos. Mas o problema do ticket ainda não foi totalmente solucionado. Existe uma pendencia ainda aberta. A inicialização. Mas vamos resolver isto, ao mesmo tempo que resolveremos, os outros dois problemas, já que o caminho será o mesmo.


Últimos detalhes a serem criados.

A questão agora, é de fato saber, o que devemos de fato ajustar. É verdade, que você pode ajustar diversas coisas, em um ativo customizado. Mas para nosso proposito, várias destas coisas, não são de todo necessárias. Precisamos apenas focar no que realmente precisamos de fato. E ajustar isto, para que no momento que for preciso de tais informações, tenhamos elas de forma simples e o mais versátil quanto for possível fazer. Digo isto, pois a intensão de fato, é começar a criar um sistema de ordens em breve. Não sei se isto realmente irá se dar assim, tão rapidamente. Mas de qualquer modo, quero que o Expert Advisor que será criado, seja compatível com o replay / simulador, ao mesmo tempo que também possa ser utilizado no mercado normal. Seja em conta demo, seja em conta real. Para isto precisamos fazer com que as coisas, tenham um mesmo nível de informações necessárias que seria possível encontrar no mercado real.

Dito isto, precisamos então inicializar as coisas com valores zerados. Isto para garantir que o ativo customizado, a ser utilizado, terá estes valores compatíveis com os que realmente serão encontrados no ativo real. Mas também, o fato de inicializar os valores com zero, significa que poderemos testar os mesmos depois. Isto facilita muito o trabalho de implementação a fim de verificar possíveis falhas na configuração do ativo.

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");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Aqui iniciamos os valores, que de fato precisaremos futuramente. Isto de forma mais imediata. Tais valores são o tamanho do ticket, o valor do ticket e o volume ( no caso o passo ). Mas já que o passo, muitas das vezes, isto para não dizer sempre, corresponde ao volume mínimo, que se deve usar. Não vejo problemas, em configurar ele, no lugar do volume mínimo. Mesmo por que, o passo é mais importante para nos na próxima etapa. Mas também existe o motivo de que tentei ajustar o volume mínimo, e por algum motivo, não tive sucesso ao fazer isto. O MetaTrader 5, simplesmente estava ignorando o fato de que era para ajustar o volume mínimo.

Feito isto, precisamos fazer uma outra coisa. Testar se de fato, estes valores foram inicializados. Isto será feito no código a seguir:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
   u_Interprocess info;
   
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o tamanho do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo.");
   if (m_IdReplay == -1) return false;
   if ((m_IdReplay = ChartFirst()) > 0) do
   {
        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
        {
            ChartClose(m_IdReplay);
            ChartRedraw();
        }
   }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
   Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
   info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
   ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
   ChartRedraw(m_IdReplay);
   GlobalVariableDel(def_GlobalVariableIdGraphics);
   GlobalVariableTemp(def_GlobalVariableIdGraphics);
   GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
   while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
   
   return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
  }

Para evitar ficar repetindo as mesmas coisas. Utilizaremos uma macro para nos ajudar. Ela irá mostrar uma mensagem de erro e finalizar informando que o sistema falhou. O que fazemos aqui, é testar, um por um, os valores que deverão, obrigatoriamente ser declarados e inicializados no arquivo de configuração. Caso um deste não tenha sido inicializado, isto será informado ao usuário. Para que ele providencie a correta configuração do ativo customizado. Sem isto o serviço de replay / simulador, não irá prosseguir. Por conta que a partir deste momento, ele já será considerado funcional e capaz de informar ao sistema de ordens, no caso o EA que será construído. Os dados necessários para que a simulação ou replay de mercado aconteça de forma adequada. A fim que você seja capaz de conseguir simular o envio de ordens.

Bem, mas para que estes valores sejam inicializados. Precisamos fazer mais algumas adições ao sistema. Estas são vista logo abaixo:

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "PATH",
                                "POINTSPERTICK",
                                "VALUEPERPOINTS",
                                "VOLUMEMINIMAL"
                                };
        string  szRet[];
        char    cWho;
        
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_szPath = szRet[1];
                    return true;
                case 1:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
                    return true;
                case 2:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
                    return true;
                case 3:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
                    return true;                                    
            }
            Print("Variável >>", szRet[0], "<< não definida.");
        }else
            Print("Definição de configuração >>", szInfo, "<< invalida.");

        return false;
    }

Vejam que para adicionar as coisas ao sistema, é algo bastante simples. Aqui fizemos a adição de dois novos valores, que podem ser configurados com a simples edição, do arquivo que configura o replay ou simulação de um ativo qualquer. Estes são: O valor do ticket, que irá gerar a chamada a uma função, que respectivamente aguarda um valor adequado. E o valor do tamanho do passo, que também irá chamar uma função interna, que ajusta este valor. Todas as adições que você deseja fazer devem seguir estes passos.


Considerações finais.

No momento ainda, não estou efetuando testes a fim de verificar se os valores são, ou não adequados. Por conta disto, tome cuidado ao editar o arquivo de configuração, para evitar erros durante o uso do sistema de ordens.

De uma forma ou de outra, veja como as coisas estão nos ativos customizados presentes no anexo.

Um detalhe importante: Apesar de ter dito que o sistema esta praticamente funcional. Isto não é de todo verdade. Já que no momento, não é possível fazer nem replay ou simulação, utilizando dados do FOREX. O motivo é por conta do forex utilizar algumas coisas, nas quais o sistema ainda não consegue de fato trabalhar. Se você tentar fazer isto, será gerado erros de range nas matrizes do sistema. Seja no replay, seja na simulação. Mas estou providenciando as correções para que ele seja capaz de trabalhar com dados, oriundos do mercado de forex.

No próximo artigo iremos começar a tratar desta questão: O FOREX.


Arquivos anexados |
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 18):  Tiquete e mais tiquetes  (II) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 18): Tiquete e mais tiquetes (II)
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.
Algoritmos de otimização populacionais: Algoritmo semelhante ao eletromagnetismo (EM) Algoritmos de otimização populacionais: Algoritmo semelhante ao eletromagnetismo (EM)
O artigo descreve os princípios, os métodos e as possibilidades de aplicação do EM a diferentes problemas de otimização. Ele uma ferramenta de otimização eficiente, capaz de lidar com grandes quantidades de dados e funções multidimensionais.
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.