English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 15): Nascimento do SIMULADOR (V) - RANDOM WALK

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 15): Nascimento do SIMULADOR (V) - RANDOM WALK

MetaTrader 5Testador | 13 junho 2023, 15:40
492 0
Daniel Jose
Daniel Jose

Introdução

Nos últimos artigos desta serie, sobre o desenvolvimento de um sistema de replay. Tenho mostrado como simular, ou melhor dizendo, como gerar algum tipo de modelagem virtual de um determinado ativo. A fim de conseguir ter um movimento bastante próximo, do que seria um movimento real. Apesar de termos tido grandes avanços, saindo de um simples sistema, muito similar ao utilizado pelo testador de estratégias, e chegando a um modelo bastante interessante. O fato é, que ainda não conseguimos fazer uma modelagem, que se adequasse, a qualquer base de dados. Que fosse, ao mesmo tempo dinâmico e estável o suficiente, para nos permitir gerar, as mais diversas situações possíveis. Isto com o mínimo de esforço, referente a questão da programação.

O que iremos fazer aqui. Será corrigir uma falha, que existe, no artigo Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 14): Nascimento do SIMULADOR (IV), onde apesar de termos gerado, um primeiro principio de RANDOM WALK. Ele não é de todo adequado, quando estamos trabalhando com valores, que estão previamente definidos em um arquivo, ou banco de dados. No nosso caso especifico, o nosso banco de dados, indicará sempre quais são as métricas, que teremos que usar e respeitar. Apesar do sistema RANDOM WALK, visto e desenvolvido, ser capaz de gerar movimentos muito similares, ao que é visto em um mercado real. Ele não é adequado, para ser de fato utilizado, em um simulador de movimentação. O motivo disto, é por conta que ele não consegue, cobrir totalmente o range, que precisa ser coberto. Isto em todos os casos. É verdade que em casos bastante raros, teremos de fato a cobertura de todo o range. Saindo de um ponto que é o preço de abertura, indo em direção a máxima ou mínima. Mudando completamente o sentido, assim que alcança um dos limites e indo ao outro extremo. Para finalmente, de forma quase que mágica. Encontrar e encerrar exatamente no preço, definido como sendo o fechamento da barra.

Pode parecer impossível disto acontecer. Mas muito e muito eventualmente, isto de fato irá acontecer. Mas não podemos ficar a mercê da casualidade. Precisamos que o mesmo, continue sendo o mais aleatório quanto for possível e permitido. Ao mesmo tempo, precisamos que ele também cumpra o seu papel. Que é justamente cobrir, de forma total e integra, os pontos predefinidos na barra. Pensando desta maneira, e analisando alguns conceitos matemáticos abstratos. Conseguiremos gerar uma forma relativamente atraente um RANDOM WALK controlado. Pelo menos no que tange, o fato de que todos os pontos, de interesse, e definidos, serão alcançados e respeitados.

A ideia em si não é complicada. Apesar de bastante inusitada. Não irei entrar, em detalhes matemáticos da coisa, para não complicar inutilmente a explicação. No entanto, irei mostrar o conceito que será utilizado. Juntamente com o código é claro, de forma que você, também possa compreender, e quem sabe, até mesmo desenvolver pequenas variações do que irei apresentar.


Entendendo a ideia e o conceito

Se você tem acompanhado esta serie, sobre o Replay / Simulador. Deve ter notado, que começamos experimentando e tentando fazer, primeiramente um sistema, que conseguisse cobrir todos os pontos de preço. Isto usando uma técnica, muito similar a usada no testador de estratégias. Esta técnica se baseia, no típico movimento de zig zag. Muito comum e bastante conhecido, por pessoas que estudam o mercado. Para quem não conhece, ou não sabe do que se trata, pode ver uma representação esquemática na figura 01.

Figura 01

Figura 01 - Típico movimento em zig zag.

O fato é que apesar desta modelagem, ser muito boa para o testador de estratégia. Ela esta longe de ser adequada, ao um sistema de replay / simulação. Pelo simples fato, de que a quantidade de tickets produzidos, ao utilizar esta modelagem, fica sempre muito abaixo, do que realmente seria encontrado, em um momento típico do mercado. Mas isto não significa, que este modelo não é valido. Apenas que ele não é adequado, para ser utilizado em um sistema, como o que queremos desenvolver. Mesmo se construíssemos, uma forma de que o movimento, visto na figura 01, viesse a produzir, uma quantidade de tickets, equivalentes ao de uma negociação real. Ainda assim, o movimento em si, não seria de todo complexo. Seria apenas, uma adaptação, do real movimento da figura. Precisaríamos então, de uma outra forma, de fazer as coisas.

Uma que fosse o mais aleatória possível. Mas que não gera-se, ao mesmo tempo, uma explosão de complexidade no código, a ser programado. Lembre-se do seguinte fato: Se a complexidade do código, cresce muito rápido. Ele ficará rapidamente, inviável de ser mantido, ou ter suas falhas corrigidas. Devemos sempre procurar, manter as coisas o mais simples possível. Desta maneira, você pode logo pensar, que poderíamos simplesmente gerar, valores aleatórios de preços. E assim simular, um nível de complexidade, suficientemente grande, a ponto de que o movimento seria o mais próximo, de um movimento real. Entretanto, não podemos nos dar ao luxo, de aceitar qualquer preço ou qualquer quantidade de tickets sendo gerados. Precisamos respeitar a base de dados. Sempre.

Ao observar uma base de dados, você irá acabar por notar, que existem informações bastante uteis ali. De fato elas estão ali, por serem necessárias. Na figura 02, você pode ver um típico conteúdo de um arquivo de barras. Nele estou destacando alguns valores, que são valores que realmente nos interessa.


Figura 02

Figura 02 : Típico conteúdo de um arquivo de barras de 1 minuto.

Se você, utilizando estes valores, e gerar de forma totalmente aleatória, os valores de preço. Mas, e reforço este mas, mantendo as coisas contidas, dentro de um range informado. Irá produzir um tipo de movimentação, completamente aleatória. No entanto, ao mesmo tempo, esta mesma movimentação, não seria nada confortável. Já que raramente, um mercado real, terá tal tipo de movimentação,

Para que você não fique apenas imaginando, o que esta sendo gerado. Podemos traduzir isto, para um formato de gráfico. O resultado de uma geração, completamente aleatória de preços, dentro de um dado range, ficaria conforme é visto na figura 03.


Figura 03

Figura 03 - Gráfico do resultado de valores totalmente aleatórios.

Este tipo de gráfico, pode ser conseguido, ao se utilizar um método bastante simples. Porém ao mesmo tempo bastante eficaz. Já mostrei no passado como fazer isto no Excel, isto a fim de que você possa gerar este tipo de gráfico. Isto para conseguir analisar com mais exatidão, o movimento que esta sendo construído. Tendo como base, apenas e somente, os valores de um banco de dados. Realmente vale bastante apena, você aprender a fazer isto. Já que é primordial, para que você consiga analisar mais rapidamente, valores totalmente aleatórios. Sem usar este recurso, você ficaria completamente perdido na imensa quantidade de dados, tentando entender toda aquela confusão, aparentemente sem nenhum sentido.

Uma vez que você saiba como gerar os tais gráficos. Você começa a analisar a coisa com mais calma. E acaba notando, que o gráfico contido na figura 03, não é bem adequado, para ser utilizado no nosso sistema. Isto dado o auto nível de aleatoriedade, presente no mesmo. Então você começa a procurar uma forma, de que tal aleatoriedade, seja pelo menos contida. Isto de maneira a podermos gerar algo, mais próximo de um movimento real. Assim, depois de um tempo, você acabará descobrindo uma forma. Uma muito utilizada em jogos eletrônicos antigos, onde um personagem se move aparentemente de forma aleatória, pelo cenário do jogo. Mas lá no fundo, e bem no fundo, o que acontece é que o personagem, segue regras bastante simples. Este método também pode ser visto, em sistema que fazem o embaralhamento de peças, bastante populares, e até mesmo divertidos. Como de cubos mágico ( Figura 04 ), ou mesmo jogos 2D, que utilizam peças deslizantes, para fazer tanto a resolução, quanto o embaralhamento do jogo ( Figura 05 ).




Figura 05

Figura 04 - Cubo Magico é um exemplo de movimentação RANDOM WALK


Figura 05

Figura 05 - Quebra cabeças deslizante - O mais simples sistema de uso do RANDOM WALK

Apesar de a primeira vista, tais "brinquedos" não parecem utilizar o RANDOM WALK. Eles de fato utilizam este método. Não para serem resolvidos. Se bem que até daria para resolver os mesmos, utilizando o método RANDOM WALK. Mas o tempo necessário, seria consideravelmente grande, frente ao necessário para resolve-los, usando métodos mais adequados. Mas para que as peças, fiquem em posições, aparentemente aleatórias. É necessário de fato, utilizar uma formulação, que envolve o RANDOM WALK. Uma vez feito isto, você os tenta resolver, mas utilizando uma  outra metodologia. Assim sendo uma criança, iria fazer, ao conseguir recolocar as peças no seu devido lugar. A mesma coisa acontece, com o sistema de geração de tickets, utilizando o RANDOM WALK. Na verdade, a matemática por trás do sistema de movimentação RANDOM WALK, é praticamente a mesma em todos os casos, ela não muda.

O que de fato muda, é apenas o sistema de orientação, que iremos de fato fazer uso. Você pode imaginar, que um movimento aparentemente sem nenhum sentido, mas que esteja em um espaço 3D, não se encaixaria em tal matemática. Entretanto, para ser sincero, são muito poucos os casos, em que isto de fato acontece. Em muitos deles, a matemática envolvida na descrição do RANDOM WALK, irá conseguir explicar as variações, que acontecem ao longo do tempo. Então ao utilizarmos tal matemática, em um sistema de Preço x Tempo, iremos obter um gráfico parecido com o da figura 06.


Figura 06

Figura 06 - RANDOM WALK do preço x tempo

Este tipo de coisa, que segue os mesmos princípios, de movimentos estocásticos, se parece em muito com o real movimento, que o mercado normalmente apresenta. Mas não se iluda. O gráfico acima, não é o de um ativo real. Ele é obtido, ao se utilizar um gerador de números aleatórios. A partir do preço anterior, você irá somar ou subtrair, em uma unidade o preço. Gerando assim um novo preço. A forma de se saber, se iremos subtrair ou adicionar, depende da regra que será utilizada. Você pode simplesmente dizer, que todo número PAR gerado aleatoriamente, indicará soma. E em todo numero IMPAR deveremos subtrair. Apesar de simples, esta regra irá conseguir efetuar o seu trabalho. Mas também você pode mudar a regra, de forma que todo número gerado aleatoriamente, e que seja por exemplo, divisível por 5, indicará soma, caso contrário teremos uma subtração. E o simples fato de fazer esta mudança na regra, irá gerar um gráfico totalmente distinto. Até mesmo o valor que inicializa o sistema de geração aleatória, faz com que as coisas mudem ligeiramente.

Mas apesar do gráfico da figura 06, ser adequado para um sistema totalmente livre. Não podemos de fato utilizá-lo em um sistema de simulação. Isto por que precisamos respeitar os valores que estão sendo indicados, pelo banco de dados. Estes nos dão uma indicação explicita de limites. Tanto superior, quanto inferior, para a geração do RANDOM WALK. Desta forma, ao fazermos as correções, de maneira que estes limites sejam obedecidos. Iremos sair do gráfico presente na figura 06, e vamos para um que pode ser visto na figura 07.


Figura 07

Figura 07 - RANDOM WALK dentro de limites

O sistema que gera um gráfico, como mostrado na figura 07, é bem mais adequado, de ser utilizado em um simulador. Onde temos uma base de dados, que nos fornece os limites, nos quais podemos nos mover. De fato, apesar de continuarmos utilizando uma matemática, igualmente simples, conseguimos gerar um sistema, que contem um bom nível de complexidade. E que ao mesmo tempo, não é de todo previsível, ou seja, estamos no caminho certo. Notem que do gráfico gerado lá na figura 01, para este visto na figura 07. A única coisa que de fato mudou, foi o nível de complexidade. Ou melhor dizendo, aleatoriedade presente neste ultimo é melhor. Mas apesar do sistema da figura 01, ser o suficiente para um testador de estratégia. O gráfico gerado e visto na figura 07, contém muito mais coisas, do que realmente é necessário, para um testador. Entretanto, tais coisas são de suma importância para um simulador.

Mas mesmo com este aumento no nível de complexidade, vistos no gráfico 07. Ainda assim, ele não é de todo suficiente, para de fato ser utilizado em um simulador. Visto o fato, de que a única coisa que teremos total certeza de usar, é o ponto inicial. No caso o PREÇO DE ABERTURA. Nenhum dos outros pontos indicados na base de dados ( FECHAMENTO, MÁXIMA, MÍNIMA ), podem, ou irão, receber a garantia, de serem visitados. Isto é um problema, visto que para o simulador realmente ser viável, todos os pontos indicados na base de dados, devem ser, com toda a certeza, visitados.

Com isto, precisamos fazer algo, que torne o gráfico da figura 07, em um gráfico da figura 08. Onde temos absoluta certeza, de que os pontos, foram de fato acessados, em algum momento.


Figura 08

Figura 08 - RANDOM WALK forçado


Este tipo de coisa envolve, não mais do que algumas mudanças. Mas se você observar e com atenção o gráfico. Irá notar que nitidamente, o movimento esta sendo conduzido, de alguma forma. Mas ainda assim, não irá conseguir deixar de notar, que temos um RANDOM WALK. Não tão natural, mas ainda randômico. Como seria esperado em um movimento estocástico. Agora quero que notem o seguinte: Todos os gráficos vistos acima, utilizam a mesma base de dados. Ela é vista na figura 02. No entanto, a melhor solução, é a vista na figura 08. Onde de fato temos, um movimento que não é tão confuso, e fora de base de dados, como o visto na figura 03. No entanto, este ainda assim, é muito similar, ao que poderia ter de fato ocorrido durante a construção da barra.

Com base nestes conceitos mostrados até aqui, acredito ter de fato convencido você, que a forma como o código ficou no artigo anterior, não é exatamente o que precisamos em um simulador. Isto devido o fato de que alguns dos pontos, podem não ser devidamente visitados, durante o período que o movimento randómico ocorreu. Entretanto, ao invés de forçar tais pontos a vim aparecer no gráfico. Que tal deixar as coisas um pouco mais naturais ?!?!

E é este ponto, que irei mostrar e abordar, no próximo tópico.


RANDOM WALK forçado

O fato de chamar o movimento, de Random Walk forçado. Não indica que iremos impor uma condição para ele. No entanto iremos limitar o movimento, de uma forma bastante especifica. De maneira que ele pareça ao mesmo tempo, o mais natural possível, mas mantendo a sua natureza aleatória. No entanto, com um detalhe: Vamos forçar o movimento, a um ponto de convergência. E este ponto, é justamente o que deverá ser visitado. Desta maneira, teremos de fato, um misto entre a figura 01 e a figura 07. Ficou curioso não é mesmo ?!?! Mas não vamos de fato usar o caminho que normalmente seria criado pela figura 01. Vamos utilizar uma forma um pouco diferente.

Para de fato entender, vamos ver o código do Random Walk, que esta presente no código do artigo anterior. Este pode ser visto logo abaixo:

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;
                                double  v0, v1;
                                bool    bLowOk, bHighOk;
                                
                                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 = 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;                                     
                                bLowOk = bHighOk = false;
                                for (int c0 = 1; c0 < max; c0++)
                                {                               
                                        v0 = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? 1 : -1));
                                        if (v0 <= rate.high)
                                                v0 = tick[c0].last = (v0 >= rate.low ? v0 : tick[c0 - 1].last + m_PointsPerTick);
                                        else
                                                v0 = tick[c0].last = tick[c0 - 1].last - m_PointsPerTick;
                                        bLowOk = (v0 == rate.low ? true : bLowOk);
                                        bHighOk = (v0 == rate.high ? true : bHighOk);
                                }                                       
                                il0 = (long)(max * (0.3));
                                if (!bLowOk) tick[macroRandomLimits(il0, il0 * 2)].last = rate.low;
                                if (!bHighOk) tick[macroRandomLimits(max - il0, max)].last = rate.high;                         
                                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
                        }                       

O que de fato precisamos, é modificar de alguma forma, este código que esta destacado acima. Se isto for feito de forma adequada. Poderemos manter o sistema seguindo as premissas do RANDOM WALK. Ao mesmo tempo que controlamos a forma e a maneira, como o caminho será gerado. Assim conseguiremos visitar todos os pontos, que estão sendo informados. Lembrando que o ponto de abertura, sempre será visitado. Temos que de fato, nos preocupar, é com os outros 3 pontos. O de máxima. O de mínima. E o de fechamento. Então vamos fazer o seguinte: Primeiramente, vamos criar uma rotina, bem básica, a fim de substituir, o código destacado acima. Mas faremos isto, de uma forma muito mais interessante.

Para facilitar a nossa vida. Vamos primeiro criar uma nova rotina, que será a responsável por gerar o RANDOM WALK. Esta pode ser vista abaixo, na sua primeira versão.

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : price));
                                        switch (iMode)
                                        {
                                                case 0:
                                                        if (price == rate.close)
                                                                return c0;
                                                case 1:
                                                case 2:
                                                        break;
                                        }
                                        if ((int)floor(vNext) < c1)
                                        {
                                                if ((++c2) <= 3) continue;
                                                vNext += vStep;
                                                if (rate.close > vLow) vLow += m_PointsPerTick); else vHigh -= m_PointsPerTick;
                                        }
                                }
                                
                                return pOut;
                        }

Não entendeu o que estou fazendo ?!?! Bem, não se preocupem. Irei explicar, para que você consiga acompanhar o meu raciocínio. Pois como foi dito, esta é a primeira versão. Primeiro, fazemos um cálculo, para descobrir qual será o passo que iremos executar para conduzir o random walk, de forma que ele chegue a um ponto especifico. Armazenamos os valores máximos e mínimos do limite, que o random walk poderá de fato estar. Agora começa a nossa saga. Vamos caminhar, de forma randômica, uma certa quantidade de pontos. A cada passo que estivermos dando, vamos ajustar as coisas. Não de forma a travar o random walk. Mas sim de forma a conduzi-lo, em uma espécie de canal, de onde ele não poderá sair. Ele pode sacolejar de um lado para outro, mas em momento algum irá sair de dentro do canal. Quando estamos no modo zero, assim que o ponto de destino for alcançado, a rotina irá retornar. Isto é importante, para gerar uma randomização maior. Mas não se preocupem, depois isto irá ficar mais claro.

Agora temos que fazer uma coisa: Lembra que fizemos um calculo, para saber de quanto, em quanto tempo, o canal iria ser reduzido ?!?! Pois bem, chegou a hora de começar a fechar o canal.  Este fechamento, vai acontecendo aos pouco. Mas com um detalhe bastante curioso. Nos de fato, não iremos fechá-lo completamente. Iremos deixar uma pequena faixa, bem estreita, onde o preço irá poder continuar o seu caminho randômico, seria algo parecido com as bandas de Bollinger. Mas no final, ele estará praticamente no ponto indicador como sendo o ponto final, ou seja, o fechamento. Este se dá realmente, ao mudar os limites do canal. Primeiro vamos fechando a parte de baixo, e quando tivermos encostado no ponto de saída, começamos a fechar a partir da parte de cima.

De uma forma ou de outra, o último ponto, estará praticamente sendo acessado. Mas se ele não estiver sendo acessado. Ele estará muito, mas muito próximo do ideal. Mas onde esta rotina acima se encaixa ?! Bem. Ela irá substituir, o antigo método de random walk. Ao fazer isto, estaremos unindo, tanto o que acontece na figura 01, quanto o que acontece na figura 07. Tendo como resultado, uma imagem gráfica muito próxima da figura 08.

Então veja como ficou a nova rotina de simulação.

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

O trecho em destaque, é que esta substituindo ao antigo método. Agora vem uma questão, e esta é bastante importante, que você consiga de fato entender. O que o trecho em destaque esta fazendo ? Você consegue responder, apenas olhando o código dele ?!?!

Se você consegue, ótimo. Meus sinceros parabéns. Se não consegue, então vamos ver este trecho.

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

O que esta sendo feito, é um GRANDE e imenso ZIG ZAG. Mas como 🤔 ?!?! Não estou conseguindo ver isto 🥺 ?!?! Vamos por partes. Inicialmente, calculamos um ponto limite, para que a primeira perna do zig zag, possa terminar.  Feito isto, já iremos logo de cara, definir o tamanho da terceira perna. Um detalhe: A primeira perna, diferente da terceira, não tem um tamanho fixo. Ela pode terminar, a qualquer momento. Este momento, é definido pela rotina do random walk. Mas já que ao terminar a primeira perna, deveremos iniciar a segunda. Este valor retornado pela rotina RandomWalk, é utilizado como ponto de inicio. Onde teremos uma nova chamada, para montagem de um segundo random walk. Ficou confuso ?!?!

Calma vamos chegar lá. Agora, definimos os limites, e os pontos de entrada, e saída da primeira perna do random walk. A rotina de criação do random walk, é chamada e retorna assim que o ponto de finalização for alcançado. Ou o mais próximo, quanto for possível chegar neste ponto. Agora pegamos, e ajustamos novamente os pontos de entrada, e saída da próxima pernada.  Chamamos a rotina de criação do random walk, de forma que o movimento, irá ir de um extremo ao outro. Mas mesmo que estes extremos, sejam alcançados, a rotina somente irá retornar, quando o numero de posições definidas, for de fato alcançado.  Assim que isto acontecer. Definimos pela terceira e ultima vez, os pontos de entrada e saída do random walk. Efetuamos a chamada, que irá construir a última pernada. Ou seja, temos um grande zig zag. Onde ao invés do preço ir, de um ponto a outro. Ele ficará sacolejando, dentro dos limites que definimos. 😁

Ao executar este sistema que foi explicado acima. Você irá conseguir um gráfico muito próximo, do visto na figura 08. O que não é nada mal, considerando a simplicidade do método visto acima. Mas podemos melhorar a coisa toda. Se você observar com atenção, irá notar que no gráfico da figura 08, existem pontos em que o preço fica ali, meio como se estivesse batendo em uma parede. Isto apesar de parecer estranho, em alguns momentos, de fato acontece em um mercado real. Mais especificamente, quando um lado, seja compradores ou vendedores, não permite que o preço se desloque, além de um determinado ponto, a famosa briga de escoras no book.

Mas, e apenas mas. Se você desejar um movimento um pouco mais natural do sistema, terá que mudar não mais a rotina de simulação. Mas somente e apenas, a rotina responsável, por criar o random walk. No entanto, temos um detalhe nesta história toda. Você não deve, e vou repetir, NÃO DEVE, tentar criar um novo método de random walk. Você deve apenas, e somente, criar uma forma de ligar, e desligar, os limites, de forma que o movimento, flua por si só. Se isto for feito de forma adequada, o resultado final, será um gráfico, com uma aparência bem mais natural. Como se em nenhum momento, estivéssemos guiando o movimento.

Uma primeira tentativa de se fazer isto, é adicionado um checkup de toques nos limites. Assim que isto acontecer, algo mágico passará a ser executado. Então veja como a nova rotina de random walk, isto não é um outro método, é apenas uma adaptação da rotina original. Irá ficar, quando adicionamos tão checkup. Lembrando que irei explicar apenas, e somente as partes novas.

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                
    char  i0 = 0;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : 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 (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + m_PointsPerTick); else vHigh = (i0 == 3 ? vHigh : vHigh - m_PointsPerTick);
                                        }
                                }
                                
                                return pOut;
                        }

Agora temos uma situação, completamente nova aqui. Então temos que evitar, de ultrapassarmos, antigos limites, dos quais antes, não haveria problemas em ultrapassar. Mas vejam um detalhe bem sutil. Temos uma nova variável, que se inicia zerada. Esta na verdade, seria como se fossem um conjunto booleano. O que esta singela variável faz, é algo incrível. Então prestem bastante atenção, para conseguirem entender. Pois é algo muito bem definido e bastante curioso. Quando estamos na segunda pernada do zig zag. Temos em algum momento, um toque do preço na máxima, e um toque do preço na mínima. Então em cada um destes toques, anotamos um valor especifico na nossa variável. Agora o que acontece, se observarmos a variável, não como sendo unidades de bits, e sim de forma completa ?!?! Teremos um valor bem especifico. Este valor pode ser Zero, Um, Dois ou Três.

Mas espera um pouco, como assim Três 🤔 ?!?! O detalhe, é que quando temos um toque na máxima, iremos fazer uma operação lógica OR, onde iremos fazer com que o bit menos significativo fique com um valor verdadeiro. Quando temos um toque na mínima, iremos fazer a mesma operação, mas agora no segundo bit menos significativo. Ou seja caso o primeiro bit já esteja com o valor verdadeiro, ao colocarmos este segundo bit com um valor verdadeiro, iremos imediatamente, sair de um valor igual a 1 para um valor igual a 3. Por conta disto que a contagem vai até o valor 3.

É importante compreender este fato, pois será justamente este o valor que iremos testar. Ao invés de testar 2 booleanas diferentes, iremos testar um valor que representa ambas. Compreendeu a lógica do sistema 😁 ?! Desta forma, caso o valor seja igual a 3, teremos uma explosão dos limites, que poderiam ser utilizado. Ou para que você entenda melhor. Sem fazermos este teste, os limites iriam se fechar, até o ponto que o random walk, seria comprimido, a apenas 3 posições possíveis de movimento. Mas ao fazermos isto, deixaremos que o movimento flua naturalmente novamente. Já que os limites foram alcançados, não faz sentido continuar restringido o movimento.

Esta explosão, acontece nestes pontos. Assim quando a rotina for tentar reduzir os limites. Ela não fará mais isto. Agora o random walk, irá ocorrer dentro de todo o limite ficando assim muito mais natural. Se você executar, agora o sistema, com a mesma base de dados que estamos utilizando, desde o inicio do artigo. Terá um gráfico muito próximo, ao que é mostrado na figura 09.

Figura 09

Figura 09 - Random Walk com explosão.


Observem que este gráfico, parece ser bem mais natural do que o gráfico visto na figura 08. No entanto, mesmo este último gráfico parecer ideal, ele ainda contar com um ponto não tão natural assim. Este ponto pode ser percebido sobre a última pernada. Prestem bastante atenção, ao final do gráfico. Você irá notar, que ele parece um pouco forçado. Isto frente aos limites de movimento possível. Este tipo de coisa, merece ser corrigido. E é justamente isto que faremos agora.

Para resolver esta questão, deveremos adicionar algumas poucas linhas na rotina responsável pelo random walk. Então a versão final no sistema, é visto logo abaixo:

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                char i0 = 0;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : 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 += m_PointsPerTick; else vHigh -= m_PointsPerTick;
                                                        }else
                                                        {
                                                                if (rate.close < vHigh) vHigh -= m_PointsPerTick; else vLow += m_PointsPerTick;
                                                        }
                                                } else
                                                {
                                                        if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + m_PointsPerTick); else vHigh = (i0 == 3 ? vHigh : vHigh - m_PointsPerTick);
                                                }
                                        }
                                }
                                
                                return pOut;
                        }

Parece doideira, mas vejam o seguinte: Quando estamos executando a primeira e a segunda pernadas, o código que de fato será executado, é este daqui. Quando estamos na terceira pernada, teremos uma figura um pouco diferente.

Para de fato criar a figura, que em analise gráfica é conhecida como TRIANGULO SIMÉTRICO, temos que fazer uma redução gradual de ambos os lados. Mas, e é imprescindível que você entenda, que poderemos ter, um TRIANGULO SIMÉTRICO ou um ASSIMETRICO. Isto caso o ponto de saída, esteja em uma posição deslocada, igualmente entre os extremos, teremos a criação de um triangulo simétrico. Se o ponto de fechamento, ou saída, estiver muito próximo a um dos extremos, o triangulo será assimétrico. Para conseguir isto, vamos utilizar uma soma e subtração alternadas. Isto é feito nestes pontos especificamente. A forma de alternar estes pontos, é testando o valor de redução atual.

Com isto, teremos o gráfico final muito parecido com o da figura 10. Visto logo abaixo:

Figura 10

Figura 10 - Gráfico do Random Walk de uma barra de 1 minuto


Notem que ele aparenta ser muito mais natural do que qualquer outro gráfico visto anteriormente. Com isto, fechamos básico de random walk. E poderemos partir para as próximas etapas do nosso sistema de replay / simulador.

Para que você possa entender como o sistema atualmente está.

No anexo, você tem acesso ao código completo e no seu atual estágio de desenvolvimento.


Conclusão

Você de fato não precisa usar uma matemática extravagante para explicar, ou criar as coisas. Quando a roda foi inventada, não foi usada uma matemática a fim de saber qual deveria ser o raio da mesma com base no seu valor correspondente ao PI. Tudo que de fato foi feito, foi fazer com que o movimento desejado acontecesse. Neste artigo, mostrei que uma matemática simples, e usada em brinquedos de criança, consegue explicar e desenhar movimentos que muitos acreditam ser impossíveis de serem desenhados. E ficam fazendo uso de uma matemática extravagantes, sendo que a solução é simples. Então sempre procure fazer o simples.


Arquivos anexados |
Ciência de Dados e Aprendizado de Máquina (Parte 12): É possível ter sucesso no mercado com redes neurais de autoaprendizagem? Ciência de Dados e Aprendizado de Máquina (Parte 12): É possível ter sucesso no mercado com redes neurais de autoaprendizagem?
Certamente muitas pessoas estão cansadas ​​​​de tentar constantemente prever o mercado de ações. Você gostaria de ter uma bola de cristal que o ajudasse a tomar melhores decisões de investimento? As redes neurais autoaprendentes podem ser a solução para isso. Neste artigo, vamos ver se esses algoritmos poderosos podem ajudar a surfar na onda e ser mais espertos que o mercado de ações. Ao analisar grandes volumes de dados e identificar padrões, as redes neurais autoaprendentes podem fazer previsões que geralmente são mais precisas do que as previsões dos traders. Vamos descobrir se essas tecnologias avançadas podem ser utilizadas para tomar decisões de investimento mais inteligentes e obter mais lucros.
Algoritmos de otimização populacionais: Algoritmo do macaco (MA) Algoritmos de otimização populacionais: Algoritmo do macaco (MA)
Neste artigo, estaremos analisando o algoritmo do macaco (Monkey Algorithm, MA). A habilidade destes animais ágeis para superar obstáculos complexos e atingir as partes mais inacessíveis das árvores foi a inspiração para a concepção do MA.
Experimentos com redes neurais (Parte 4): Padrões Experimentos com redes neurais (Parte 4): Padrões
As redes neurais são tudo para nós. E vamos verificar na prática se é assim, indagando se MetaTrader 5 é uma ferramenta autossuficiente para implementar redes neurais na negociação. A explicação vai ser simples.
Desenvolvendo um fator de qualidade para os EAs Desenvolvendo um fator de qualidade para os EAs
Nesse artigo vamos explicar como desenvolver um fator de qualidade para ser retornado pelo seu EA no testador de estratégia. Iremos mostrar duas formas de cálculo conhecidas (Van Tharp e Sunny Harris).