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

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 04): Ajustando as coisas (II)

MetaTrader 5Exemplos | 28 março 2023, 13:03
780 3
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 03): Ajustando as coisas ( I ), criamos um Expert Advisor, que seria capaz de controlar de forma bastante simples o serviço de replay de mercado. No entanto, lá fizemos apenas e somente uma única coisa: Pausar ou dar Play no sistema, não foi criado nenhum tipo de controle, de forma a podermos manipular, qual posição desejávamos começar o replay. Ou seja, ainda não é possível começar o replay, no meio do período ou em algum outro ponto, sendo sempre necessário começar a partir do inicio dos dados. O que é pouco viável, para quem deseja fazer algum tipo de treinamento.

Pois bem, aqui vamos implementar uma forma, que seja a mais simples quanto for possível, de fazer a seleção de qual seria o ponto inicial do replay, a ser apresentado ao operador. Ao mesmo tempo, iremos fazer uma pequena mudança de estratégia, por conta do pedido de alguns amigos, que ao ver o sistema sendo montado, gostaram tanto, mas que gostariam de usar seus próprios Expert Advisor. Então nos vamos mudar uma coisa no sistema de forma a permitir isto.

Pelo menos por enquanto, esta será a abordagem que iremos adotar, já que quero mostrar como de fato uma nova aplicação é criada. Já que muitos imaginam que ela surge simplesmente do nada, e não tem a devida noção de como todo o processo se dá, deste o nascimento da ideia, até a total estabilização do sistema e do código. Permitindo assim que a aplicação execute exatamente o que era esperado.


Trocando o Expert Advisor pelo Indicador

Esta troca é bastante simples de ser feita. E uma vez que ela tenha sido executada, você poderá usar o seu próprio Expert Advisor. Tanto para efetuar estudos no replay de mercado, quanto para operar no mercado real. Como por exemplo, o Expert Advisor que demonstrei em artigos passados. Para mais detalhes veja a serie Desenvolvendo um EA de negociação do ZERO, apesar de ele não ser voltado para ser 100% automatizado, poderá ser adaptado para ser usado no replay, mas isto é assunto para o futuro. No entanto, você também poderá usar algum Expert Advisor que veio da serie Aprendendo a construindo um EA que opera de forma automática (Parte 01): Conceitos e estruturas, onde expliquei como você cria um Expert Advisor que funciona de forma 100% automática.

Mas fazer uso de um Expert Advisor, não é de fato o nosso foco principal neste momento. Iremos explorar muito isto no futuro. Mas agora o foco é outro.

O código completo do indicador, pode ser visto logo abaixo. Isto pegando exatamente a funcionalidade que existia no Expert Advisor, e colocando no indicador:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <Market Replay\C_Controls.mqh>
//+------------------------------------------------------------------+
C_Controls      Control;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        Control.Init();
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Control.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+

A única parte diferente, é a adição do nome curto, que devemos sempre, preferencialmente, colocar nos indicadores. E isto é justamente a parte em destaque no código acima. Mas ao fazer isto, ganhamos um bônus, poderemos usar qualquer Expert Advisor, para praticar e fazer estudos no replay. Mas antes que alguns questione, o replay de mercado NÃO É um testador de estratégias. Ele é voltado para a pessoa que deseja praticar leitura de mercado, e assim ganhar alguma consistência, ao melhorar a sua percepção, sobre os movimentos dos ativo. Mas ele não é um testador de estratégia, já que o MetaTrader 5 conta com um testador muito bom para isto. No entanto, o testador de estratégias, não é adequado para praticar um replay de mercado.

Apesar da conversão não ter tido a primeira vista, nenhum tipo de efeito colateral. Isto não é de todo verdade. Ao executar o sistema de replay atualizado, para que o controle seja feito por um indicador, ao invés de usar um Expert Advisor, você irá perceber uma falha. Quando ocorrer a mudança de tempo gráfico, o indicador é retirado do gráfico, para logo depois ser reposto. Esta operação de retirar ele e depois repor, faz com que o botão que indica se estamos no modo pause ou modo play, ficar inconsistente com o real status do serviço de replay. Para corrigir isto, teremos que fazer um pequeno teste. Com isto o código de inicialização do indicador, ficará conforme indicado abaixo:

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}

As adições, que estão em destaque no código, garantem que o status do serviço de replay, corresponda de fato ao botão que estaremos vendo no gráfico. As mudanças no código de controle, são bastante simples e não merecem destaque especial.

Agora o arquivo template, que é usado no replay, que antes usava um Expert Advisor, passará a usar um indicador. Isto nos deixando completamente livres para fazer outras coisas no futuro.


Implementando o controle de posição

Aqui vamos implementar um controle, para dizer onde queremos ir dentro arquivo de replay, para iniciar a execução de algum estudo. Mas antes que alguns pensem que teremos total precisão do ponto que iremos indicar. Não iremos de fato fazer isto, e o motivo não é a impossibilidade de fazer tal coisa. Muito pelo contrário, seria bem mais simples indicar um ponto exato. No entanto, ao conversar e trocar experiência com pessoas com mais tempo de mercado, uma coisa foi consenso entre eles. O ideal, é não ir para um ponto exato, onde temos um movimento especifico, já esperado. Mas sim começarmos o replay de mercado, em um ponto próximo do movimento desejado. Ou seja, você terá que entender o que estava acontecendo antes de entrar de fato no movimento.

Esta ideia me pareceu tão boa, que de fato decidi que o replay de mercado, não deveria de fato ir para um ponto exato. Mesmo sendo mais fácil implementar isto, este deveria sim ir para um pronto próximo. O qual próximo que seria, iria depender da quantidade de tickets negociados no dia. Quanto mais tickets negociados, mais difícil será acertar o ponto exato.

Com isto será muito mais comum, você acessar um ponto próximo, e terá que entender o que estava acontecendo, para de fato criar uma simulação de operação. Novamente, NÃO estamos criando um testador de estratégia. E ao fazer isto, você com o tempo, acabará entendendo quando um movimento é mais seguro, ou o risco é alto demais, não valendo apena entrar na operação. Mesmo que depois de um tempo, teremos um movimento forte e direcional.

Todo o trabalho nesta etapa, será feito dentro da classe C_Control. Então mãos a obra.

A primeira coisa que faremos, será definir algumas definições

#define def_ButtonLeft  "Images\\Market Replay\\Left.bmp"
#define def_ButtonRight "Images\\Market Replay\\Right.bmp"
#define def_ButtonPin   "Images\\Market Replay\\Pin.bmp"

Agora iremos precisar criar um conjunto de variáveis, para armazenar os dados do sistema de posição. Estas são implementadas da forma como é visto abaixo:

struct st_00
{
        string  szBtnLeft,
                szBtnRight,
                szBtnPin,
                szBarSlider;
        int     posPinSlider,
                posY;
}m_Slider;

Isto mesmo que você acabou de notar. Nos iremos usar um controle deslizante, para conseguir apontar uma posição aproximada de onde o replay deve ser iniciado. Por conta disto, temos agora uma função genérica. Que tanto será usada para criar os botões de play/pause, como os botões do nosso controle deslizante. Esta função é vista logo abaixo, e acredito que você não terão dificuldades em entende-la, já que é algo bastante comum.

inline void CreateObjectBitMap(int x, int y, string szName, string Resource1, string Resource2 = NULL)
                        {
                                ObjectCreate(m_id, szName, OBJ_BITMAP_LABEL, 0, 0, 0);
                                ObjectSetInteger(m_id, szName, OBJPROP_XDISTANCE, x);
                                ObjectSetInteger(m_id, szName, OBJPROP_YDISTANCE, y);
                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 0, "::" + Resource1);
                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 1, "::" + (Resource2 == NULL ? Resource1 : Resource2));
                        }

Bom, agora todo e qualquer botão, irá utilizar esta função para ser criado. Isto facilita bastante as coisas, e aumenta o reuso de código, tornando assim as coisas mais estáveis e de forma mais rápida. A próxima coisa a ser criada, será uma função que irá representar o canal a ser usado no controle deslizante. Esta barra é criada pela função logo a a seguir:

inline void CreteBarSlider(int x, int size)
                        {
                                ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
                        }

Aqui a parte mais curiosa, é a parte que representa as bordas do canal do controle. Você pode ajustar isto ao seu desejo, assim como a largura do canal, que é ajustado na propriedade OBJPROP_YSIZE. Mas ao modificar o valor nesta propriedade, não se esqueça de também ajustar o valor que subtrai m_Slider.posY, para que o canal fique no meio dos botões.

A função que cria os botões play/pause. Agora ficou da seguinte maneira:

void CreateBtnPlayPause(long id, bool state)
{
        m_szBtnPlay = def_PrefixObjectName + "Play";
        CreateObjectBitMap(5, 25, m_szBtnPlay, def_ButtonPause, def_ButtonPlay);
        ObjectSetInteger(id, m_szBtnPlay, OBJPROP_STATE, state);
}

Bem mais simples não é verdade ?!?! Mas agora vamos ver a rotina que irá criar os controles deslizantes. Esta pode ser vista abaixo:

void CreteCtrlSlider(void)
{
        u_Interprocess Info;
                                
        m_Slider.szBarSlider = def_PrefixObjectName + "Slider Bar";
        m_Slider.szBtnLeft   = def_PrefixObjectName + "Slider BtnL";
        m_Slider.szBtnRight  = def_PrefixObjectName + "Slider BtnR";
        m_Slider.szBtnPin    = def_PrefixObjectName + "Slider BtnP";
        m_Slider.posY = 40;
        CreteBarSlider(82, 436);
        CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft);
        CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight);
        CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        PositionPinSlider(Info.s_Infos.iPosShift);
}

Observem com atenção os nomes dos controles, isto é importante. Já que estes controles, não ficarão disponíveis durante o tempo que o serviço de replay estiver no modo play. Então toda a vez que estivermos no modo pausado, esta rotina será chamada, criando assim os controles deslizantes para nos. Vejam que por conta disto, iremos capturar o valor da variável global de terminal, para poder identificar e assim posicionar o controle deslizante na posição correta.

Então não é bom que você mexa na variável global de terminal manualmente. Um detalhe que você deve ficar atento, é com relação ao pino. Diferente dos botões, ele é criado com o ponto de ancoragem exatamente no centro do mesmo, facilitando assim o seu posicionamento. Aqui já vemos uma chamada para um outro procedimento, que é visto logo a seguir:

inline void PositionPinSlider(int p)
{
        m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
        ChartRedraw();
}

Este irá posicionar o botão deslizante, em uma determinada região. Mas garantimos que ele fique dentro dos limites pré estabelecidos com antecedência.

Como você pode imaginar, temos que fazer alguns pequenos ajustes no sistema ainda. Já que a cada mudança no tempo gráfico, o indicador será reinicializado, fazendo com que venhamos a perder, o ponto atual que estamos de fato no replay. Uma das formas de evitar que isto aconteça, é fazendo algumas adições a rotina de inicialização. Aproveitando estas mudanças, iremos colocar algumas coisas extras também. Então vamos ver como a rotina de inicialização ficou:

void Init(const bool state = false)
{
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        CreateBtnPlayPause(m_id, state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
}

Agora adicionamos também um código, para que os eventos de movimento de mouse, sejam repassados para dentro do indicador. Sem a adição desta linha, os eventos de mouse serão perdidos, e não serão repassados pelo MetaTrader 5 para o indicador. Agora para evitar que o controle deslizante, fique aparecendo sem que seja de fato necessário, adicionamos um pequeno teste. Caso este confirme a necessidade da apresentação do controle deslizante, ele será apresentado na tela.

Com tudo que foi visto até aqui, você já deve estar pensando: Como acontecerá o tratamento de eventos ?!?! Será que teremos um código extra super complicado ?!?! Bem, de certa forma, a maneira de tratar os eventos do mouse, não muda muito. O fato de você desejar colocar um evento de arrasto, não é algo muito complicado. Tudo que você tem que de fato fazer, é gerenciar alguns limites para que a coisa não saia do controle. Fora isto, a coisa em si é bastante simples de ser feita e implementada.

Então vamos dar uma olhada no código da função, que faz todo este tratamento: a DispatchMessage. Mas para facilitar, vamos ver o código em fragmentos. Assim ficará mais simples de explicar o que esta acontecendo. O primeiro fragmento a ser visto, é o responsável pelo tratamento dos eventos de clicar nos objetos. Veja o código logo abaixo:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;

//... outras variáveis locais ....
                                
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                ChartRedraw();
                        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                break;

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

Quando clicamos no botão play/pause, temos que fazer algumas coisas. Uma delas, é caso estejamos em modo pausado, criar o controle deslizante. Mas também se sairmos do modo pausado, e entramos no modo de execução, estes controles devem ser retirados do gráfico, de forma que não tenhamos mais acesso a eles. Mas ao mesmo tempo que fazemos isto, também repassamos o valor atual do controle deslizante, para a variável global de terminal. Fazendo isto desta forma o serviço de replay, passa a ter acesso a posição percentual, que estamos ou desejamos colocar o sistema de replay.

Além destas questões envolvidas no botão play/pause, temos também que tratar, os eventos que acontecem ao clicarmos nos botões de deslocamento pontual do controle deslizante. Caso cliquemos no botão de deslocamento para a esquerda, o valor atual presente no controle deslizante, deverá ser decrementado em uma unidade. De forma similar, em caso tenhamos um clique no botão de deslocamento para a direita, este irá adicionar uma unidade ao controle, até o limite máximo estabelecido.

Vejam que é algo bastante simples. Nada muito complicado pelo menos nesta parte, a fim de efetuar o tratamento das mensagens de clique nos objetos. Mas agora vem uma questão, um pouco mais complicada, que é arrastar o controle deslizante. Para fazer isto, vamos ver o código que faz o tratamento dos eventos de movimento do mouse. Este é visto logo abaixo:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
        static int six = -1, sps;
        int x, y, px1, px2;
                                
        switch (id)
        {

// ... EVENTO de clicar no objeto ...

                case CHARTEVENT_MOUSE_MOVE:
                        x = (int)lparam;
                        y = (int)dparam;
                        px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                        px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                        if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                        {
                                if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
                                {
                                        six = x;
                                        sps = m_Slider.posPinSlider;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                }
                                if (six > 0) PositionPinSlider(sps + x - six);
                        }else if (six > 0)
                        {
                                six = -1;
                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                        }
                        break;
        }
}

Este parece ser um pouco mais complicado, mas é tão simples, quanto o de tratar dos cliques no objetos. A única grande diferença, é que agora teremos que fazer uso de mais variáveis. E algumas delas devem ser estáticas, para que o valor não seja perdido entre as chamadas. Quando um evento de movimento do mouse acontece, o MetaTrader 5, envia uma mensagem para dentro do nosso sistema. Devemos e iremos usar esta mensagem, para descobrir o que aconteceu. Seja para o fato de saber, onde o ponteiro do mouse esta, ou quais botões estão pressionados. Seja para verificar alguma outra coisa. Mas tudo isto vem na mensagem enviada pelo MetaTrader 5 para a nossa aplicação.

Quando o botão esquerdo estiver pressionado, teremos algo que de fato desejamos utilizar. Mas para garantir que o controle deslizante esta na tela, e não tenhamos um falso positivo. Fazemos uso um teste extra, isto para garantir a integridade do que estamos de fato fazendo.

Uma vez que o teste indica que este evento é válido. Executamos um outro teste, que irá verificar se estamos clicando no botão deslizante. Na verdade, na região pertencente ao controle deslizante. Mas ao mesmo tempo, iremos verificar se esta posição ainda é válida, pois pode acontecer de o clique já ter acontecido, mas a posição não ser valida. Neste caso deveremos ignorá-lo. Caso este teste passe, iremos armazenar tanto a posição do clique, quanto o valor do controle. Mas também precisamos travar o arrasto do gráfico. Isto é necessário para a próxima etapa, onde iremos calcular a posição do controle deslizante, com base nos valores anteriores presentes no controle. Sem armazenar estes dados, antes de efetuar qualquer tipo de cálculo, seria extremamente complicado, ajustar as coisas, e saber como agir aqui. Mas da forma como esta sendo feito, será algo super simples e fácil de ser implementado, pois o cálculo será apenas um cálculo de desvio. 

Quando o botão esquerdo for liberado, a situação será restabelecida ao seu modo original. Ou seja, o gráfico poderá ser arrastado, e a variável estática usada para armazenar a posição do mouse, terá um valor indicando que nenhuma posição, estará em analise. Este mesmo método, pode ser usado, para arrastar qualquer coisa no gráfico, o que é algo muito bom. E isto tudo, usando o estilo clicar e arrastar. Então tudo que você precisará fazer, será analisar onde está a região, que poderá receber o clique. Ajustar as coisas, e o resto será feito pelo código, que será bem parecido com este mostrado acima.

Com tudo isto funcionado, já temos um comportamento desejado na parte dos controles. Mas ainda não terminamos. Temos que fazer com que o serviço, utilize o valor que estamos indicando no controle deslizante. E isto será feito no próximo tópico.


Ajustando a classe C_Replay

As coisas nunca são exatamente como alguns imaginam. O simples fato de termos criado um controle deslizante, e ajustado as coisas na classe de controle ( C_Control ), não indicam que tudo já esta funcionando. Precisamos fazer alguns poucos ajustes na classe, que de fato monta o replay.

Estes ajustes, não são muito complicados de serem feitos. Na verdade, são bem poucos, e em pontos bastante específicos. Mas é preciso que você esteja bastante ciente de que qualquer mudança feita em uma classe, irá afetar a outra. Mas não necessariamente teremos que fazer alguma mudança em outros pontos. A preferência, é nunca ter que mexer em pontos desnecessários, e sempre levar o encapsulamento ao seu nível máximo, sempre que possível, ocultando assim a complexidade de todo o sistema.

Mas vamos direto ao que interessa, e a primeira coisa a ser feita, antes mesmo de qualquer outra, é ajustar a rotina Event_OnTime. Esta é a responsável por colocar os ticks negociados no ativo de replay. Basicamente iremos adicionar uma pequena coisa nesta rotina. Acompanhe abaixo o código da mesma.

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
inline int Event_OnTime(void)
{
        bool isNew;
        int mili, test;
        static datetime _dt = 0;
        u_Interprocess Info;
                                
        if (m_ReplayCount >= m_ArrayCount) return -1;
        if (m_dt == 0)
        {
                m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                m_Rate[0].tick_volume = 0;
                m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                _dt = TimeLocal();
        }
        isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
        m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
        mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
        do
        {
                while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                {
                        m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                        m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                        m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                        m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                        m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                        isNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec);
        m_Rate[0].time = m_dt;
        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
        mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
        test = (int)((m_ReplayCount * def_MaxPosSlider) / m_ArrayCount);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != test)
        {
                Info.s_Infos.iPosShift = test;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
                        
        return (mili < 0 ? 0 : mili);
};
#undef macroGetMin

Esta rotina, irá montar as barras de 1 minuto. Mas observem que adicionamos uma variável na mesma. Comparando como o código anterior, esta variável não existia. Agora iremos ter a representação, da posição percentual relativa, armazenada na variável global de terminal. Por conta disto, precisamos desta variável, para conseguir decodificar o conteúdo interno armazenado, na variável de terminal. Então, uma vez que o tick negociado, tenha sido adicionado na barra de 1 minuto, precisamos saber, qual é o percentual da atual posição em que o replay se encontra. Isto é executado neste calculo. Onde descobrimos a posição relativa, em relação a quantidade total de ticks armazenados.

Este valor, é então comparado com o valor que esta na variável global de terminal. Caso eles sejam diferentes, iremos atualizar o mesmo, de forma que quando o sistema for pausado, o valor já irá esta indicando a posição relativa. Desta forma, não precisaremos ficar a todo momento, efetuando algum tipo de cálculo extra, ou tendo algum tipo de aborrecimento com isto.

Assim, já teremos a primeira etapa concluída. Porém, toda via e entretanto, temos um outro problema a ser resolvido. Como posicionar o sistema de replay, na posição relativa desejada. Isto depois de ajustar o valor durante uma pausa ?!?!

Este problema é um pouco mais complicado de ser solucionado. Isto pelo fato de podermos ter tanto adição, que é mais simples de resolver, quando a subtração, que é um pouco mais complicada de ser resolvida. Esta subtração, não é o grande problema, pelo menos nesta fase de desenvolvimento. Mas será na próxima fase, que será vista no próximo artigo desta serie. Mas a primeira coisa de fato a ser feita, será adicionar uma rotina extra na classe C_Replay, para fazer este trabalho de adicionar, ou subtrair barras do sistema de replay. Vamos ver a preparação desta rotina, e esta é vista logo abaixo:

int AdjustPositionReplay()
{
        u_Interprocess Info;
        int test = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_ArrayCount);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == test) return 0;
        test = (int)(m_ArrayCount * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (test < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
                CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
                m_ReplayCount = 0;
                m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                m_Rate[0].tick_volume = 0;
                m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
        };
        for (test = (test > 0 ? test - 1 : 0); m_ReplayCount < test; m_ReplayCount++)
                Event_OnTime();

        return Event_OnTime();
}

No código acima, podemos ver o fragmento, que realmente é a base deste sistema de ajuste, mas vamos entender de fato, o que esta acontecendo neste sistema base. A primeira coisa, é gerar o valor percentual da posição atual. Depois disto, comparamos o valor, com o encontrado na variável global de terminal. Este valor encontrado na variável global, é gravado pelo sistema de controle. Caso os valores sejam iguais ( não é o valor absoluto, e sim o valor percentual ), a rotina irá finalizar, já que percentualmente, estamos no ponto correto, ou o usuário não modificou a posição, durante a fase de que o sistema estava pausado.

Mas caso os valores sejam diferentes, é gerando um valor. E este é absoluto, baseando-se no valor percentual, indicado na variável global de terminal. Ou seja, agora teremos um ponto absoluto, de onde o sistema de fato deverá iniciar o replay. Este valor, dificilmente será o mesmo que o do contador de tick de negociação. Isto por conta de uma serie de fatores. Mas caso ele seja menor, que o valor atual que esta no contador de replay, iremos remover todo o conteúdo presente no ativo atual.

Esta é a parte complicada. Não nesta fase de desenvolvimento, mas será na próxima fase. Mas por enquanto, não existe motivos para uma preocupação maior neste momento. Então agora, poderemos fazer algo comum a ambas situações, adicionar novos valores, até que a posição do contador de replay, venha a corresponder a posição absoluta menos 1. Este menos 1, tem como motivo, permitir que esta função retorne, um valor que será usado como delay depois. E isto, é conseguido usando a própria Event_OnTime. 

Já que este tipo de mudança, nunca vem sem dor. Vamos ver o que teve que ser modificado no sistema, e podemos ver isto, ao observar o código abaixo. Que foi o único lugar, que de fato sofreu mudanças:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //Arquivo com ticks
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        id = Replay.ViewReplay();
        Print("Aguardando permissão para iniciar replay ...");
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Serviço de replay iniciado ...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = (Replay.Event_OnTime() > 0);
                }else
                {
                        if (bTest)
                        {
                                delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay);
                                bTest = false;
                                t1 = GetTickCount64();
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) break;
                                t1 = GetTickCount64();
                        }
                }
        }
        Replay.CloseReplay();
        Print("Serviço de replay finalizado ...");
}
//+------------------------------------------------------------------+

Enquanto estivermos no modo pausa, iremos fazer este teste a fim de verificar se estamos mudando o status do serviço. Quando isto ocorrer, iremos pedir para a classe C_Replay, fazer um novo reposicionamento, de forma que ele ( reposicionamento ), poderá ou não ser executado.

Caso venha a ser executado, teremos o valor do próximo delay a ser usado, uma vez feito este ajuste, e o sistema tenha sido reposicionado. Caso isto tenha sido necessário, iremos prosseguir de forma natural, por todo o restante do tempo, até o momento em que sairemos da fase de play, e entremos na fase se pause. Quando todo o procedimento será novamente repetido.


Conclusão

No vídeo, é demonstrado o sistema completo funcionando, onde você poderá notar como a coisa de fato acontece. O único detalhe, e que você irá notar, é que será preciso esperar o sistema estabilizar, antes de usar o replay, já que o movimento, parece ser impossível de ser operado, durante a fase que o movimento, estiver fazendo o deslocamento da posição, até o ponto desejado.

Este tipo de situação será corrigida futuramente. Mas por hora, dá para ir levando, já que ainda temos muito a resolver.



No arquivo em anexo, estou dando a vocês 2 arquivos de ticks reais de mercado, para que você possam experimentar o sistema de movimento e posicionamento, em dias cujo a quantidade de tickets negociados foram diferentes. Assim você poderá notar, como o sistema de posicionamento percentual funciona. Deixando a coisa muito mais complicada, para aqueles que procuram estudar um momento especifico de mercado. Mas isto é intencional, como foi explicado no inicio do artigo.

Neste sistema de replay, que estou mostrando, como montar no MetaTrader 5, você de fato, irá aprender a analisar o mercado. Pois não existirá um ponto exato, onde você irá parar e dizer: AQUI ... é aqui que se deve entrar ... pois pode ser que o movimento que você de fato tenha visto, esteja a algumas barras de distancia. Então, ou você aprende a analisar o mercado, ou vai odiar este sistema de replay que será exposto nesta serie de artigos.


Arquivos anexados |
Market_Replay.zip (10795.89 KB)
Últimos Comentários | Ir para discussão (3)
fernandomsoares
fernandomsoares | 11 ago 2023 em 11:49

Ola Daniel !

Primeiramente gostaria de te agradecer muito por tanta disposição, empolgação e competência em ensinar o que você sabe.

Tenho acompanhado seus artigos e gostaria de colocar tambem que os projetos propostos neles são muito úteis para o estudo e uso do metatrader 5.

Você está unindo conhecimento e mostrando como aplicar em coisas realmente úteis, isto é extraordinário. Obrigado!

Agora, voltando a discursão sobre o artigo, você fala que dá para utilizar o Replay com o nosso proprio EA, e isto é genial.

Eu estou desenvolvendo um EA para operar no mercado de mini-indice B3, e o que ele faz é fazer analise de fluxo dos dados dos ticks.

Estou tendo muitos problemas em rodar o EA nos dias passados via replay do Metatrader 5, pois preciso do movimento tick a tick e ele não executa todos os ticks do movimento do dia, pula muitos segundos de dados.

Vi que o Replay que você esta fazendo e publicando pode me ajudar com este problema, uma vez que eu baixo os dados dos ticks dos dias das corretoras e armazeno.

Dá para executar o serviço de Replay, e rodar o meu EA lendo os ticks do serviço (arquivo baixado), podendo fazer o debug do meu EA ? Pergunto isto, porque fiquei em dúvida se quando eu parar o codigo em debug do meu EA se o serviço tambem iria parar de plotar os ticks no grafico. Você teria um exemplo de como poderia fazer a chamada do EA no projeto do Replay ?

Daniel Jose
Daniel Jose | 11 ago 2023 em 14:23
fernandomsoares #:

Ola Daniel !

Primeiramente gostaria de te agradecer muito por tanta disposição, empolgação e competência em ensinar o que você sabe.

Tenho acompanhado seus artigos e gostaria de colocar tambem que os projetos propostos neles são muito úteis para o estudo e uso do metatrader 5.

Você está unindo conhecimento e mostrando como aplicar em coisas realmente úteis, isto é extraordinário. Obrigado!

Agora, voltando a discursão sobre o artigo, você fala que dá para utilizar o Replay com o nosso proprio EA, e isto é genial.

Eu estou desenvolvendo um EA para operar no mercado de mini-indice B3, e o que ele faz é fazer analise de fluxo dos dados dos ticks.

Estou tendo muitos problemas em rodar o EA nos dias passados via replay do Metatrader 5, pois preciso do movimento tick a tick e ele não executa todos os ticks do movimento do dia, pula muitos segundos de dados.

Vi que o Replay que você esta fazendo e publicando pode me ajudar com este problema, uma vez que eu baixo os dados dos ticks dos dias das corretoras e armazeno.

Dá para executar o serviço de Replay, e rodar o meu EA lendo os ticks do serviço (arquivo baixado), podendo fazer o debug do meu EA ? Pergunto isto, porque fiquei em dúvida se quando eu parar o codigo em debug do meu EA se o serviço tambem iria parar de plotar os ticks no grafico. Você teria um exemplo de como poderia fazer a chamada do EA no projeto do Replay ?

Hum.... Esta sua questão é curiosa. Gostei da sua dúvida 😁👍. Já que também pode ser dúvida de outras pessoas. Obrigado por expor a mesma publicamente.

E a resposta é SIM 😁👍. Existe sim, uma maneira de você usar seu Expert Advisor, e dizer ao serviço de replay / simulador que pare de plotar ticks no gráfico. Para fazer isto, será necessário fazer uma coisa, bem específica. E como você está perguntando, acredito, que você não tenha ideia de como a coisa realmente funciona. Então prefiro, não dizer aqui, neste momento. Mas não se preocupe. Já escrevi os artigos em que explico em detalhes como fazer o que você deseja. Eles vão demorar um pouco para sair, já que antes de explicar a forma de fazer isto. Foi preciso explicar justamente uma outra coisa, que permite entender justamente o que você deseja fazer.

Tenha um pouco de pacíencia, e continue seguindo e estudando os artigos, sobre o replay / simulador. E continue desenvolvendo o seu Expert Advisor, além de continuar baixando e armazenando os ticks. Pois para fazer com que o seu Expert Advisor, possa dizer ao serviço o que você está desejando. Será preciso adicionar, apenas uma linha de código. Isto mesmo, que você acabou de ler. Será preciso adicionar somente uma única linha de código e tudo funcionará como você deseja. Mas antes de fazer isto, você precisará entender, o que esta única linha de código irá fazer no MetaTrader 5. E explicar isto, em um comentário, não é tão simples. 😁👍

fernandomsoares
fernandomsoares | 17 ago 2023 em 19:58
Obrigado pela atenção e resposta. Vou continuar acompanhando e ficar atento e empolgado para quando você fizer esta publicação. Vai ser de muita, muita ajudar na depuração e aprimoramento do meu EA. Muito Obrigado ! Que Deus continue te abençoando muito !
Desenvolvendo um sistema de Replay — Simulação de mercado (Parte 05): Adicionando Previas Desenvolvendo um sistema de Replay — Simulação de mercado (Parte 05): Adicionando Previas
Conseguimos desenvolver, uma forma de fazer com que o replay de mercado, fosse executado dentro de um tempo bastante realista e aceitável. Vamos continuar nosso projeto. Agora iremos adicionar dados de forma a ter um comportamento melhor do replay.
Teoria das Categorias em MQL5 (Parte 2) Teoria das Categorias em MQL5 (Parte 2)
A Teoria das Categorias é um ramo diverso da Matemática e em expansão, sendo uma área relativamente recente na comunidade MQL5. Esta série de artigos visa introduzir e examinar alguns de seus conceitos com o objetivo geral de estabelecer uma biblioteca aberta que atraia comentários e discussões enquanto esperamos promover o uso deste campo notável no desenvolvimento da estratégia dos traders.
Algoritmos de otimização populacionais: Busca por cardume de peixes (FSS - Fish School Search) Algoritmos de otimização populacionais: Busca por cardume de peixes (FSS - Fish School Search)
O FSS (Fish School Search) é um algoritmo avançado de otimização inspirado no comportamento dos peixes que nadam em cardumes. Aproximadamente 80% desses peixes nadam em comunidades organizadas de parentes, o que tem sido comprovado como uma estratégia importante para melhorar a eficiência de procura por alimento e proteção contra predadores.
Como desenvolver um sistema de negociação baseado no indicador Gator Oscillator Como desenvolver um sistema de negociação baseado no indicador Gator Oscillator
Um novo artigo em nossa série sobre como aprender a desenvolver um sistema de negociação baseado nos indicadores técnicos mais populares será sobre o indicador técnico Gator Oscillator e como criar um sistema de negociação por meio de estratégias simples.