English Русский Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay (Parte 35): Ajeitando as coisas (I)

Desenvolvendo um sistema de Replay (Parte 35): Ajeitando as coisas (I)

MetaTrader 5Exemplos | 14 novembro 2023, 14:22
329 0
Daniel Jose
Daniel Jose

Introdução

No final do artigo anterior, Desenvolvendo um sistema de Replay (Parte 34): Sistema de Ordens (III), informei que o sistema apresentou algumas falhas bastante estranhas e até mesmo desconcertantes. O motivo disto é que tais falhas estavam ocorrendo por conta de algum tipo de interação dentro do sistema. Apesar das tentativas de tentar compreender o motivo de algumas das falhas, para assim sana-las. Todas foram frustradas, já que não fazia o mínimo sentido de algumas delas estarem ocorrendo. Quando fazemos uso de ponteiros ou recursão em C / C++, e o programa começa a apresentar falhas. Uma das primeiras providencias é verificar tais mecanismos. Mas aqui no MQL5, isto não ocorre da mesma forma que no C / C++. Mas depois de fazer algumas pequenas edições, uma das falhas foi sanada. Apesar de que a solução não parece, ao meu ver ser uma solução tão elegante. Ela de fato fez com que uma das falhas desaparece-se por completo.

Mas mesmo assim iremos precisar fazer uma edição no código um pouco mais radical. A fim de sanar por completo as falhas que estão acometendo o sistema. Estas já deviam estar presentes a um bom tempo. Mas por conta que não estava acontecendo alguns tipos de interação dentro do sistema. Isto de alguma forma bem especifica. Mas assim que, tais interações começaram a se dar, tais falhas que estavam ali, mas passavam desapercebido, começaram a sem ser notadas, durante o uso do programa.

As falhas presentes não afetam negativamente o sistema. Mas não o deixa trabalhar de maneira que seja realmente adequado. Além de confundir o usuário. Tornando a experiência no programa algo bastante desagradável e nada aceitável. A primeira das falhas, é relativamente simples de ser corrigida. Então vamos ao tópico onde explicarei como resolver a questão da mesma.


Resolvendo a indicação de serviço ocupado.

A primeira das falhas e esta é a mais simples de ser corrigida. É a responsável por fazer com que ao pressionarmos CTRL ou SHIFT, para posicionamento de ordens. Apareça a indicação de que o serviço esteja ocupado. Isto faz com que durante o processo de play, mesmo o sistema funcionando normalmente, seja indicado que o serviço, está efetuando alguma outra tarefa. Esta tarefa é o deslocamento de tempo. Onde as barras são criadas antes de você poder fazer algum tipo de analise ou estudo no gráfico de replay / simulador. Apesar desta falha, não ser algo extremamente danoso. Ela torna a experiência, no uso do replay / simulador, algo um tanto quanto desagradável. Já que ela mais nos confundi do que nos informa.

Algumas pessoas poderiam imaginar, que o melhor seria remover a indicação de serviço ocupado. Já que faz algum tempo que não usamos a visualização de criação de barras, durante o deslocamento temporal. Mas fazer isto não resolve de fato o problema. Isto apenas iria jogar o problema para debaixo do tapete. Mas a solução é algo bastante simples e com um custo bastante baixo. Isto tudo frente ao fato de que podemos manter as coisas, de maneira que em algum momento no futuro, possamos voltar com o sistema de visualização, onde a criação das barras pode ser vista durante a passagem de tempo. Assim como era feito antes de desabilitarmos tal recurso do serviço de replay / simulação. Assim sendo, para resolver o problema, precisamos fazer algumas modificações. Começaremos pelo seguinte código:

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(m_id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        ulong ul = 1;

        m_id = ChartID();
        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != m_id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

Esta primeira modificação deverá ser feita no arquivo do indicador. Você pode ver acima onde exatamente a primeira das modificações está se dando. A parte riscada foi removida e no lugar dela temos este novo código. Observem com bastante atenção, pois a única mudança que realmente precisou de fato acontecer aqui, foi com relação ao valor do parâmetro lparam, a ser passado para a função EventChartCustom. Bizarramente, esta simples troca já começa a fazer toda diferença.

No mesmo arquivo, mas em uma outra função. Precisaremos fazer algo bastante parecido. Isto pode ser visto logo abaixo:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
  static bool bWait = false;
  u_Interprocess Info;
        
  Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
  if (!bWait)
  {
     if (Info.s_Infos.isWait)
     {
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 0, 0, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 1, 0, "");
        bWait = true;
     }
  }else if (!Info.s_Infos.isWait)
  {
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
     bWait = false;
  }
        
  return rates_total;
}

Da mesma forma como aconteceu no código de OnInit. Aqui temos as partes riscadas sendo removidas, e no lugar delas temos um novo código. Mas a única diferença entre o código original e o novo, é o valor do parâmetro lparam. Novamente a mesma coisa se deu justamente na chamada da função EventChartCustom.

Você pode estar pensando: Mas por que disto ?!?! Isto irá de fato funcionar ?!?! Na verdade, apenas fazer estas mudanças aqui, no código do indicador, não fará o problema ser solucionado. Para de fato resolver o problema, temos que ir a classe C_Control e adicionar um pequeno teste na função de tratamento de mensagens. Então o novo código pode ser visto logo a seguir:

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)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
//... Restante do código ...
  }
}

Aqui estou capturando apenas o fragmento que de fato precisamos para compreender a solução do problema. Reparem que foi adicionado um pequeno teste, em ambos eventos customizados. E o fato de que quando uma interação acontece com o usuário, fazendo com que o programa dispare um evento customizado, faz com que estes testes, somente permitam que o evento customizado seja executado. Tais eventos são disparados justamente no código do programa do indicador de controle. Por qualquer outro motivo o valor que irá chegar aqui no tratador, será diferente do esperado. Isto considerando o valor do parâmetro lparam. Este tipo de coisa ainda irá voltar a acontecer no futuro. Quando eu for explicar como fazer uso de algo bastante curioso na plataforma MetaTrader 5. Lá irei me aprofundar bastante na explicação do motivo desta falha estar acontecendo. Mas de qualquer forma neste momento o motivo é bizarro.

Mas espera um pouco. Isto não faz o mínimo sentido ?!?! De fato, também não entendi  no começo o por que. Mas de alguma forma, ao pressionamos a tecla SHIFT ou CTRL, a fim de usar o sistema de ordens, que está sendo desenvolvido. A plataforma está gerando um evento que dispara ambos eventos acima. Mas principalmente o  CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn, que faz com que a imagem de serviço ocupado apareça no gráfico. E assim que você libera as teclas SHIFT ou CTRL, um evento  CHARTEVENT_CUSTOM + C_Controls::ev_WaitOffé disparado fazendo com que tudo volte ao normal.

Com este problema corrigido, podemos passar para o próximo. No entanto este segundo problema, é consideravelmente muito mais complexo de ser corrigido. Desta forma será preciso ver isto em um novo tópico, isto para que você não fique confundindo as coisas. Então vamos ver o próximo tópico para entender a real situação a ser resolvida.


Fazendo uma reutilização mais profunda da classe C_Mouse.

A forma de sanar a próxima falha, que acontece quanto estamos de fato executando algum estudo, onde o sistema pode acabar movendo o indicador de posição, sem que o usuário de fato perceba. Passa por uma edição de código bem mais profunda. Muitos poderiam pensar, que apenas fazendo um teste, a fim de verificar se o usuário, está de fato tentando mover o pino de posição, já poderia ser o suficiente. Este pino é parte integrante do indicador de controle, sendo usado para que o usuário possa saber em que ponto o replay ou simulação se encontra. Mas, um simples teste não é nem de longe o suficiente para resolver esta situação. Precisamos ir um pouco além deste simples teste. Na verdade, iremos fazer algo muito mais complicado neste primeiro momento. Mas que irá resolver o problema de uma forma muito mais definitiva, além de nos permitir fazer outras coisas depois.

Vamos passar a usar a classe C_Mouse, que está sendo usada inicialmente no Expert Advisor, dentro do indicador de controle também. Mas fazer isto, não é algo assim tão simples como você pode estar imaginando neste momento. Será preciso fazer algumas mudanças drásticas em diversos pontos do código. Isto para que a classe C_Mouse, possa de fato trabalhar em perfeita harmonia, tanto no indicador de controle, quanto também no Expert Advisor. Isto sem gerar nenhum tipo de conflito ou problema. Mas ao fazer isto, iremos abrir a porta que nos permitirá fazer outra coisa.

Vamos começar vendo a classe C_Mouse e o que mudou nesta mesma classe. Para explicar as modificações, precisamos ver de fato o que foi adicionado ao código desta classe. A primeira coisa que foi feita, está relacionado as variáveis globais privativas. Como pode ser visto no fragmento abaixo:

struct st_Mem
{
   bool     CrossHair,
            IsFull;
   datetime dt;
}m_Mem;

Esta variável, que foi adicionada, e se encontra em destaque, servirá para que façamos um teste dentro da classe. Este tem como prioridade definir como a a classe irá de fato ser executada. Isto na questão relacionada aos objetos que a classe deverá trabalhar durante o seu período de vida. Uma vez que esta variável tenha sido declarada. Precisamos de fato, inicia-la de uma forma adequada. Para isto utilizaremos o constructor da classe. Mas agora vem a primeira das complicações. Como você pode saber se deverá utilizar o sistema de modo FULL ou não ?!?! Detalhe: ao usar o modo FULL, estaremos de fato fazendo uso dos objetos de gráfico. Tais objetos serão utilizado pelo Expert Advisor. Quando não estivermos fazendo uso da classe dentro do Expert Advisor, tais objetos NÃO deverão ser criados. Mas como podemos dizer a classe se estaremos usando o Expert Advisor ou outra coisa qualquer ?!?! Existem algumas maneiras de se fazer isto. Mas iremos, e precisaremos utilizar uma que seja feita por meio do constructor da classe. Isto para evitar um grande numero de testes extras depois e riscos envolvidos em uso indevido da classe. Assim sendo, o constructor, será modificado e ficar conforme mostrado abaixo:

C_Mouse(C_Terminal *arg, color corH = clrNONE, color corP = clrNONE, color corN = clrNONE)
{
   Terminal = arg;
   if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   if (_LastError != ERR_SUCCESS) return;
   m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
   ZeroMemory(m_Info);
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
}

Agora muita atenção ao que esta sendo feito aqui. O fato de estamos definindo estas cores com um valor default, aqui na declaração do constructor, nos permite sobrecarreguemos o constructor. Esta sobrecarga irá permitir que não precisemos de fato criar um outro código de constructor, a fim de poder usar esta mesma classe C_Mouse. Isto de forma a diferenciar o seu uso no indicador de controle ou no Expert Advisor. Se esta sobrecarga não fosse feita, ou possível. Precisaríamos criar um outro constructor, apenas para dizer a classe, se esta seria utilizada no modo FULL ou NÃO. Neste ponto do código do constructor, definimos o valor da variável. Ao mesmo tempo, já fazemos o primeiro uso do valor da mesma. Se estivermos no modo FULL, iremos criar a linha de preço, a ser usada no gráfico. Caso contrário esta linha não será criada.

O código do destructor, também sofreu uma pequena mudança. Esta pode ser vista abaixo:

~C_Mouse()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName);
}

Caso a construção da classe falhe, pelo fato de que o ponteiro para a classe C_Terminal, não tenha sido corretamente inicializado. Não poderemos usar o ponteiro no destructor. Para evitar que um ponteiro invalido seja utilizado, fazemos este teste no destructor da classe. Esta foi a parte fácil da codificação, inicial. Agora vamos dar uma rápida passada pelos código antigos da classe, apenas para verificar o que foi preciso modificar nos mesmos.

void CreateStudy(void)
{
   if (m_Mem.IsFull)
   {
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
      CreateObjectInfo(0, 0, def_NameObjectStudy);
   }
   m_Info.Study = eStudyCreate;
}
//+------------------------------------------------------------------+
void ExecuteStudy(const double memPrice)
{
   double v1 = GetInfoMouse().Position.Price - memPrice;
   int w, h;
                                
   if (!CheckClick(eClickLeft))
   {
      m_Info.Study = eStudyNull;
      ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
      if (m_Mem.IsFull) ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName + "T");
   }else if (m_Mem.IsFull)
   {
      string sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ",
      MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1/ memPrice) * 100.0)));
      GetDimensionText(sz1, w, h);
      ObjectSetString(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                           
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h));                            
      ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
   }
   m_Info.Data.ButtonStatus = eKeyNull;
}

Estes dois procedimentos acima, tiveram uma modificação bem fácil e simples de se entender. Vejam que foi adicionado um pequeno teste em ambos procedimentos, a fim de que os objetos sejam criados e manipulados. Apenas se estivemos utilizando a classe em seu modo FULL, tais manipulações irão de fato acontecer. Mas também temos um outro procedimento dentro da classe C_Mouse. Este é de real importância para nos, já que é o procedimento no qual iremos interagir com a classe. Seu código pode ser visto abaixo na integra:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   int w = 0;
   static double memPrice = 0;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         ChartXYToTimePrice(def_InfoTerminal.ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
         if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = def_AcessTerminal.AdjustPrice(m_Info.Data.Position.Price));
         m_Info.Data.Position.dt = def_AcessTerminal.AdjustTime(m_Info.Data.Position.dt);
         ChartTimePriceToXY(def_InfoTerminal.ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
         if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
         m_Info.Data.ButtonStatus = (uint) sparam;
         if (CheckClick(eClickMiddle))
            if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
                  if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
                  m_Info.Study = eStudyExecute;
               }
         if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
         m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
         break;
   }
}

Assim como os procedimentos anteriores, este daqui também passou pelo mesmo tipo de modificação. Tudo que foi de fato feito, foram adicionar testes a fim de saber se iremos ou não manipular objetos no gráfico. Você pode pensar que tais testes seriam desnecessários. Já que em alguns casos os objetos não estariam no gráfico. E caso eles estivessem, a manipulação dos mesmos iria se dar da mesma forma. Isto por conta que a plataforma MetaTrader 5, não iria criar os objetos de forma duplicada. Mas quero que pensem no fato de que ao fazer tais manipulações nos objetos, você não as estará fazendo apenas uma única vez. O fato do Expert Advisor e do indicador de controle, estarem utilizando a mesma classe, mas sem saber que ambos a estão usando ao mesmo tempo. Força a plataforma MetaTrader 5 a fazer uma dupla chamada aos manipuladores de objeto. Isto não é um problema de fato. Mas você deve se lembrar de que estamos usando o sistema a fim de efetuar uma simulação ou replay de mercado. E desde o inicio deste projeto, tenho reiterado diversas vezes que o nosso maior problema, é fazer com que a simulação ou replay sejam o mais parecidos com o que acontece em um mercado real. Este fato, não se refere a simples criação das barras. Mas sim o tempo que precisa ser utilizado para criar tais barras. Ao fazer com que o MetaTrader 5, faça chamadas duplas a objetos que poderiam ter sua apresentação efetuada em apenas uma chamada, toma um tempo que para nos é precioso. Não importa qual seja o seu processador, ou equipamento utilizado. Se você começar a perder milissegundos ou mesmo nano segundos, por conta de códigos duplicados em termos de chamadas. Logo, logo irá acabar perdendo segundos ou atrasando em demasia a criação das barras. Então devemos sim, testar as coisa a ponto de evitar que o MetaTrader 5, precise fazer um sobre trabalho desnecessário, para poder apresentar os objetos no gráfico.

Pois bem, com as modificações no código da classe C_Mouse feitos, não precisamos nos preocupar com o código do Expert Advisor. Se bem que a estrutura de diretórios mudou. Mas isto só afeta o código da include. Então não vejo necessidade de entrar em detalhes disto, aqui no artigo. O fato é que o código do Expert Advisor, não vim a sofrer nenhum tipo de desvio na sua implementação, já é um pequeno alivio. Mas este é momentâneo, já que com relação ao código do indicador de controle, que é usado para controlar o replay / simulador, teremos algumas mudanças, bem mais profundas e que merecem ser explicadas em detalhes e com calma. Então vamos a um novo tópico.


Modificando o indicador de controle

Para fazer esta modificação no indicador de controle. Iremos de fato começar, trabalhando na classe usada por ele. Estou falando da classe C_Control. As modificações no código começam, de fato a serem feitas no seguinte ponto, mostrado no fragmento logo abaixo:

#include "..\Auxiliar\C_Terminal.mqh"
#include "..\Auxiliar\C_Mouse.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Controls : protected C_Mouse
{
        protected:
                enum EventCustom {ev_WaitOn, ev_WaitOff};
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider,
                                szBarSliderBlock;
                        int     posPinSlider,
                                posY,
                                Minimal;
                }m_Slider;
                C_Terminal *Terminal;

Você pode observar logo de cara que o código agora conta com duas chamadas para incluir os arquivos. Tanto da classe C_Terminal, quanto da classe C_Mouse. Mas por que não utilizar a classe C_Study, que herda a classe C_Mouse ?!?! O motivo para isto, é que não iremos fazer com que o indicador de controle faça, ou gere estudos. Isto é trabalho para o Expert Advisor, pelo menos neste momento da implementação. E não para o indicador de controle. Por conta disto iremos usar a classe C_Mouse. Observem que a classe C_Control estará herdando a classe C_Mouse. Apesar desta herança esta acontecendo, o indicador de controle, de fato não irá tirar nenhum tipo de proveito desta herança. Não diretamente. Por isto, tal herança poderia até mesmo ser feita de forma privada. Mas normalmente faço este tipo de herança como sendo protegida. E como último ponto a chamar atenção nestas declarações, temos o ponteiro para ser utilizado pela classe C_Terminal.

NOTA: No código original da classe C_Control, havia uma variável global privativa que dava acesso ao index da janela do gráfico. Este index já não irá mais existir nesta versão. O motivo é que iremos utilizar a classe C_Terminal para fazer este trabalho, que antes era feito por meio de um index interno da classe C_Control.

Por conta deste detalhe sobre o index. Todos os pontos onde ele era referenciado, foram retirados. Já os pontos onde de fato ele era necessário, o mesmo foi substituído pelo definição que nos permite acessar o index via classe C_Terminal. Ok. Então vamos ver agora o código do constructor da classe. Este pode ser visto logo abaixo:

C_Controls(C_Terminal *arg)
          :C_Mouse(arg),
           m_bWait(false)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      m_szBtnPlay             = NULL;
      m_Slider.szBarSlider    = NULL;
      m_Slider.szBtnPin       = NULL;
      m_Slider.szBtnLeft      = NULL;
      m_Slider.szBtnRight     = NULL;
   }

Agora o constructor irá receber um parâmetro, que é um ponteiro para a classe C_Terminal. Este é passado para a classe C_Mouse. Mas observem que agora a classe C_Mouse, NÃO será usada no modo FULL. Então os objetos da classe C_Mouse não serão de fato criados. Isto por que ela irá server apenas para nos dar suporte no modo de reutilização de código. Mesmo assim, testamos aqui, se o ponteiro é de fato valido para ser utilizado. Isto é importante para que não façamos uso de algo que esta apontando para uma região inválida, ou desconhecida da memória. Também temos o destructor da classe que recebeu uma pequena modificação, como pode ser visto logo a seguir:

~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

Este teste evita que tentemos usar um ponteiro inválido. Já que o constructor pode ter falhado, mas o destructor não sabe disto. Então ao fazer este teste, garantimos que o destructor, também saberá o motivo pelo qual o constructor falhou. Isto quando ele falhou, pois pode ser que o destructor esteja sendo chamado pelo simples encerramento do uso da classe C_Controls. Já que agora estamos usando um sistema, onde algumas coisas são inicializadas fora desta classe. Podemos fazer uma mudança extra no código original.

void Init(const bool state)
{
   if (m_szBtnPlay != NULL) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true);
   CreateBtnPlayPause(state);
   GlobalVariableTemp(def_GlobalVariableReplay);
   if (!state) CreteCtrlSlider();
   ChartRedraw();
}

As linhas riscadas acima, foram removidas por conta de que as coisas que elas estão configurando, já foram feitas em outros pontos. Isto por que a classe C_Mouse, que irá ligar o movimento do mouse e na classe C_Terminal, que irá dizer a plataforma MetaTrader 5, que desejamos ser alertados caso um objeto seja removido do gráfico. Por conta destes motivos, tais linhas foram removidas, já que se elas ficassem presentes no código, poderíamos ter um comportamento estranho do código em determinadas situações. Lembre-se: Não devemos supor, ou duplicar código em hipótese alguma. O fato de fazer isto, não é configurando um erro. Mas é um erro, já que dificulta a correta execução do código em alguns cenários, e atrapalha imensamente a manutenção do mesmo ao longo do tempo.

O próximo código a ser modificado é o tratador de mensagens, da classe. Este pode ser 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;
                                
   C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
         {
            if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
            {
               RemoveCtrlSlider();
               CreteCtrlSlider();
            }else
            {
               Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
               CreateBtnPlayPause(Info.s_Infos.isPlay);
            }
            ChartRedraw();
         }
         break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_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;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + 86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + 114;
            if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > 0) PositionPinSlider(sps + x - six);
         }else if (six > 0)
         {
            six = -1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

Este código aqui sofreu algumas mudanças, frente ao que era antes. O que você está vendo é a versão já finalizada do mesmo. Desta forma ficará mais simples explicar o que está acontecendo aqui. Como o indicador de controle, precisa saber, como o mouse está se comportando. Usar apenas o evento CHARTEVENT_MOUSE_MOVE para isto não será de fato suficiente. Precisamos fazer com que a classe C_Mouse execute o seu trabalho. Lembre-se, apesar do Expert Advisor usar a classe C_Mouse, assim como o indicador, ambos não estão compartilhando informações sobre o que o mouse está fazendo.

Pensei em fazer este tipo de compartilhamento, o que de fato é possível ser feito. Mas temos alguns problemas ao fazer isto.

  1. Se você compartilhar os dados do mouse, entre o Expert Advisor e o Indicador de controle, usando algum tipo de artificio da plataforma MetaTrader 5. Poderá ter problemas quando for preciso usar o Expert Advisor fora do sistema de replay / simulador. Ou poderá ter dificuldades em fazer com que o indicador de controle de fato compreenda o que o mouse está fazendo.
  2. Uma outra possibilidade seria usar um compartilhamento de memória, via DLL. Mas fazer isto, iria gerar um dependência da plataforma ou do serviço de replay / simulador. E de fato não tenho interesse, neste momento em gerar tal dependência. Desta forma então, o uso de memória compartilhada a fim de fazer com que o MetaTrader 5, não precise gerenciar a classe C_Mouse, da forma como esta sendo feita, deixa de ser algo realmente válido.

Estes motivos podem parecer pouco válidos, mas quero explorar ao máximo as capacidades da linguagem MQL5, assim como também da plataforma MetaTrader 5 para conseguir construir um sistema de replay / simulador. Mostrando que podemos fazer muito mais do que muitos consideram ser possível ser feito na plataforma ou na linguagem. Por conta disto, precisamos fazer com que a classe C_Mouse, atualize, aqui também, os dados referentes a qualquer coisa relacionada ao mouse. Isto é feito usando esta chamada daqui. Uma vez que estaremos de fato tratando eventos do mouse, via CHARTEVENT_MOUSE_MOVE, podemos então prosseguir para este evento especifico.

A primeira coisa que fazemos, é testar se o usuário está efetuando algum tipo de estudo no gráfico. Se este for o caso, qualquer coisa relacionada ao tratamento de eventos do mouse na classe C_Control, deverá ser ignorado. A partir de agora, não iremos de fato tratar os eventos do mouse, localmente. Iremos sim perguntar a classe C_Mouse, o que aconteceu e a partir daí tomar as decisões e medidas corretas, a fim de responder ao desejo do usuário. Então verificamos se o usuário está dando um clique com o botão esquerdo e se o objeto pino está presente no gráfico ( modo pausado ). Se isto for verdadeiro, verificamos a posição que o clique ocorreu. Se foi no objeto pino, o mesmo irá responder de maneira adequada, enquanto o botão esquerdo se mantiver pressionado. Desta forma o usuário poderá arrastas o objeto pino, de um lado para outro. Da mesma forma como era feito antes.

Então tudo que foi preciso realmente fazer foi adicionar uma forma de que a classe C_Control, conseguisse saber se o usuário esta ou não executando algum tipo de estudo. Este tipo de coisa, poderia até mesmo ser feita de outra maneira. Mas como foi dito no inicio do artigo: Fazer desta forma como esta sendo feito, nos traz algumas vantagens. Mesmo que estas não sejam tão evidentes neste momento.


Conclusão

Neste artigo, demonstrei como você pode agir, a fim de utilizar classes, ou métodos que estão sendo desenvolvidos, originalmente para serem usados em um Expert Advisor, em um programa diferente deste, no caso usamos em um indicador. Todo o trabalho feito aqui, irá nos trazer uma melhor experiência, no uso do sistema de replay / simulador. A fim de fazer com que um usuário, que esteja desenvolvendo uma técnica, ou queira experimentar uma determinada forma de operar, possa faze-lo utilizando o sistema que esta sendo desenvolvido nesta sequencia. Ainda existe um último problema, neste momento a ser corrigido. Mas isto será visto no próximo artigo desta serie. Já que para fazer a correção, será preciso mudar algumas coisas e adicionar outras. E já que todo o conteúdo visto neste artigo daqui, já pode ser muito complicado para aqueles que estão começando, a aprender programação. Não irei aumentar ainda mais o nível de complexidade da coisa assim, de forma abruta.

No anexo, você terá acesso tanto ao código completo do sistema. Quanto de três grupos de arquivos para que possa experimentar, e testar o sistema. E assim conseguir aprender um pouco mais sobre como ele tem sido desenvolvido.


Arquivos anexados |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Relembrando a antiga estratégia de tendência: dois osciladores estocásticos, MA e Fibonacci Relembrando a antiga estratégia de tendência: dois osciladores estocásticos, MA e Fibonacci
Estratégias de negociação tradicionais. Neste artigo, vamos explorar uma estratégia de acompanhamento de tendências. Essa abordagem é totalmente baseada em análise técnica e faz uso de vários indicadores e ferramentas para gerar sinais e identificar metas de negociação. Os elementos-chave dessa estratégia incluem um oscilador estocástico de 14 períodos, um oscilador estocástico de cinco períodos, uma média móvel de 200 períodos e uma projeção de Fibonacci (para determinar as metas de negociação).
Redes neurais de maneira fácil (Parte 49): Soft Actor-Critic (SAC) Redes neurais de maneira fácil (Parte 49): Soft Actor-Critic (SAC)
Continuamos nossa exploração dos algoritmos de aprendizado por reforço na resolução de problemas em espaços de ação contínua. Neste artigo, apresento o algoritmo Soft Actor-Critic (SAC). A principal vantagem do SAC está em sua capacidade de encontrar políticas ótimas que não apenas maximizam a recompensa esperada, mas também têm a máxima entropia (diversidade) de ações.
Teoria das Categorias em MQL5 (Parte 13): Eventos de calendário com esquemas de banco de dados Teoria das Categorias em MQL5 (Parte 13): Eventos de calendário com esquemas de banco de dados
Neste artigo, discutimos como os esquemas de banco de dados podem ser incorporados para categorização em MQL5. Analisaremos brevemente como os conceitos de esquema de banco de dados podem ser combinados com a teoria da categoria na identificação de informações de texto (string) relevantes para a negociação. O foco será em eventos de calendário.
Agora é mais fácil criar painéis gráficos no MQL5 Agora é mais fácil criar painéis gráficos no MQL5
Neste artigo, apresentaremos um guia simples e claro para quem deseja criar uma das ferramentas mais valiosas e úteis na negociação, nomeadamente um painel gráfico que simplifica as tarefas de negociação. Os painéis gráficos permitem que você economize tempo e se concentre mais na negociação em si.