English Русский Español
preview
Desenvolvendo um sistema de Replay (Parte 36): Ajeitando as coisas (II)

Desenvolvendo um sistema de Replay (Parte 36): Ajeitando as coisas (II)

MetaTrader 5Exemplos | 21 novembro 2023, 14:58
348 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior, Desenvolvendo um sistema de Replay (Parte 35): Acertando as coisas, resolvemos e corrigimos algumas coisas que estavam deixando o sistema funcionando de uma forma um tanto quanto fora do normal. Apesar das correções feitas naquele artigo, terem promovido uma melhoria com relação a experiência que o usuário irá ter. Não tivemos um completo ajuste, de todas as coisas pertinentes ao sistema de replay / simulador.

O fato é que apesar da experiência do usuário ter melhorado, temos um pequeno problema ainda pendente. E neste artigo resolveremos este problema, que apesar de ser relativamente simples, isto em tese, será bastante desafiador para ser resolvido de forma adequada. Aumentando nosso conhecimento sobre o MetaTrader 5 e de como usar a linguagem MQL5 de forma um pouco mais elaborada.


Refletindo o tipo de mercado

Quando o Expert Advisor é jogado no gráfico, ele informará, qual o tipo de conta detectada. Isto é importante para sabermos como o Expert Advisor deverá agir. Mas apesar disto funcionar muito bem, quando o sistema é lançado no gráfico de um ativo, que está em conta REAL ou conta DEMO. O sistema falha no uso do que rege o sistema de replay / simulação. Sendo informado, não o tipo de conta no qual o ativo pertence, mas sim o tipo de conta na qual a plataforma está sendo executada. Isto é um problema, que apesar de pequeno, nos traz alguns incômodos.

Você pode pensar: Resolver isto, está muito longe de ser complicado ou mesmo desafiador. A solução é bastante simples. Vamos fazer com que o sistema de replay / simulador, informe de alguma maneira, qual o tipo correto de conta. Isto dependendo, é claro do ativo que estará sendo usado. De fato a ideia é simples de ser falada e imaginada. Mas colocar ela em prática. Bem, isto já é uma outra história. A verdade, é que fazer o sistema de replay / simulador, nos informe o tipo de conta que deverá ser utilizada não é algo tão direto. Mas felizmente a plataforma MetaTrader 5, nos dá uma forma de implementar uma solução, que seja adequada e plausível de ser de fato usada.

Mas não faremos isto a esmo, ou de maneira imprudente e desleixada. Vamos implementar a solução, de uma maneira a travar de alguma forma as coisas a este tipo de conta. Já que esta informação, será importante para nos, durante o momento que formos criar o sistema de ordens. Para começar, vamos pensar no que realmente temos em mãos: O sistema de replay / simulador, estará e poderá, usar ativos de diversos tipos de mercados diferentes. O fato de isto acontecer, pode fazer com que venhamos a usar ativos, que são usados em contas do tipo NETTING ou do tipo HEDGING.

Nota: Já que o tipo EXCHANGE se parece em muito com o tipo NETTING. Não iremos de fato implementar este tipo EXCHANGE. Este tipo irá ser considerado como sendo do tipo NETTING.

Você como usuário do sistema de replay / simulador, sabe qual o tipo de mercado, ou melhor dizendo, o tipo de conta que é usada pelo ativo. Então podemos adicionar uma maneira, de fazer o usuário informar isto para o sistema de replay / simulador. Esta é a parte fácil, já que tudo que precisaremos é adicionar um novo comando no arquivo de configuração do ativo. Mas o fato de fazer isto, não nos garante que quem realmente irá precisar desta informação, a terá disponível. E quem em nosso sistema de replay / simulação, faz uso desta informação ?!?! O Expert Advisor é que faz o uso desta informação, para ser mais exato, a classe C_Manager junto com a classe C_Orders. Estas que de fato faz uso massivo desta informação em especifico. Mesmo que isto não esteja sendo feito neste momento, você deve pensar em termos mais globais e gerais. Onde poderemos usar algo visto em:  Aprendendo a construindo um Expert Advisor que opera de forma automática.

Justamente por conta deste fato, de que naquela serie de artigos, o Expert Advisor, precisou saber qual o tipo de conta que estava sendo utilizada, é que precisamos fazer com que o sistema de replay / simulador, possa também informar isto para o Expert Advisor. Caso contrário, aquele tipo de funcionalidade vista na serie de artigos, irá se perder, não podendo ser transportada para este sistema daqui. Tudo, bem. Já temos a ideia de como fazer para que o serviço de replay / simulador, saiba qual o tipo de conta utilizada por um determinado ativo. Mas a questão é:  Como levar esta informação para dentro do Expert Advisor ?!?!

Neste ponto é que precisamos de fato parar e pensar. Se você observar o código, onde podemos saber o tipo de conta. Você verá o seguinte fragmento:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

Neste trecho destacado, você pode ver onde o Expert Advisor consegue saber o tipo de conta que está sendo utilizada. Mas existe um problema aqui e este é a função AccountInfoInteger. Esta função, não é bem o problema, já que ela reporta exatamente o que pedimos para ela nos dizer. O problema é que, ao estar usando o serviço de replay / simulação, o resultado da função AccountInfoInteger, será a informação referente ao tipo de conta do servidor de negociação. Ou seja se você estiver conectado a um servidor NETTING o resultado, será o de que estamos trabalhando em uma conta NETTING. Mesmo que o ativo do replay / simulador seja do tipo HEDGING.

Esta é a tal falha. Agora vamos as ideias: Você pode dizer que podemos fazer o Expert Advisor, ler o arquivo de configuração do ativo de replay / simulador e isto resolveria o problema. Bem, isto de certa forma seria adequado. Se e somente si, fosse usado apenas um único arquivo para isto, o que não é o caso. Você então pode pensar: Então vamos fazer com que o serviço de replay / simulação passe este informação do tipo de conta. Assim o Expert Advisor poderá saber qual o tipo correto. Sim este é o caminho para fazer isto. Mas aqui mora um pequeno detalhe. Diferente do C / C++, o MQL5 não conta com determinados tipos de construções de código. Não que você definitivamente, seja incapaz de usar alguns tipos de codificação, a fim de transferir a informação entre programas. Já vimos que isto é possível e não apenas uma única vez, mas por diversas vezes. No entanto, o problema é que a nossa informação precisa ficar contida em um bloco de 8 bytes. Assim podermos usar as variáveis globais de terminal, para transportar a informação entre programas. Lembrando que estaremos fazendo isto usando o MQL5. Existem outras formas de fazer isto. Mas aqui quero usar os recursos presentes na plataforma e na linguagem MQL5.

Mas voltando ao nosso problema. Podemos realmente fazer uso do método já implementando a bastante tempo. Isto usado o arquivo InterProcess.mqh, a fim de promover a ligação e tradução que precisamos.No entanto este, este mesmo arquivo contem um problema, que iremos resolver neste artigo. Para compreender o problema, vamos ver como este arquivo se encontra atualmente codificado.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

O problema, são estas booleanas daqui. Diferente do C / C++, onde podemos fazer com que cada booleana fique contida em um bit. Aqui no MQL5, cada booleana irá ocupar oito bits. Talvez você não entenda o que isto implica de fato. Mas um byte inteiro será usado, quando precisamos apenas de um bit para armazenar o que precisamos. O desconhecimento deste fato, faz com que tenhamos um IMENSO problema. Lembrando que o tipo ushort irá ocupar dois bytes para transferir a informação. A estrutura st_0, está na verdade ocupando atualmente quatro bytes e não três como seria o esperado. Se adicionarmos mais quatro, e apenas quatro booleanas nesta estrutura st_0, iremos estar no limite de oito bytes possíveis para o nosso uso.

Este tipo de coisa complica um pouco a parte referente a programação. Então a ideia de fazer as coisas puramente dentro do MQL5, é um pouco mais complicada do que parecer. Se as coisas continuarem se complicarem, ou houver necessidade de passarmos mais dados no modo booleano, iremos precisar modificar radicalmente esta estrutura. E isto é um pesadelo logístico. Mas apesar disto, até o devido momento, podemos sim, manter este mesmo formado de estrutura. Mas seria bom se os desenvolvedores da linguagem MQL5, nos permite-se utilizar o mesmo modo de programação usado em C / C++. Pelo menos no caso dos tipos booleanos. Assim poderíamos definir o numero de bits que cada variável iria utilizar, e o compilador faria todo o trabalho de organizar, separar e agrupar as variáveis. Nos poupando de ter que fazer a programação de baixo nível, que seria necessária ser feita para conseguir este mesmo tipo de coisa. Com uma diferença, se a programação fosse feita pelos desenvolvedores e mantedores do MQL5, este trabalho seria feito de forma bem mais eficiente. Já que seria possível fazer a organização e construção via código assembly, ou algo bem próximo da linguagem de máquina. Se este mesmo trabalho for feito por nós, apenas desenvolvedores, a eficiência do código não seria assim tão grande, além de ser muito mais trabalhoso.

Desta forma, fica então a dica de uma melhoria para o MQL5. Algo aparentemente banal, mas que irá ajudar em muito alguns trabalhos de programação usado a linguagem.

Então vamos voltar ao nosso código, pois ainda podemos trabalhar com o que temos em mãos. Desta forma o novo código do arquivo Interprocess.mqh, fica conforme mostrado abaixo:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                bool    isHedging;      // If true we are in a Hedging account, if false the account is Netting...
                bool    isSync;         // If true indicates that the service is synchronized...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

Notem que a única coisa que fizemos aqui, foi adicionar duas novas variáveis booleanas. Mas isto já será o suficiente para que possamos resolver o problema imediato. Mas com isto ficamos muito perto do limite de oito bytes. Apesar de estar usando somente quatro booleanas, o que seria quatro bits. Mas pelo fato de cada booleana estar ocupando oito bits, faz com que a estrutura st_0, tenha o tamanho de seis bytes, e não de três como seria o esperado.

NOTA IMPORTANTE: Para aqueles que não sabem como este mesmo código seria feito se a linguagem MQL5, utiliza-se de uma modelagem similar ao C / C++ para definir estruturas booleanas. A fim de que o compilador fizesse o trabalho de ajeitar o código para nos. Saibam que a leitura, assim como a fase de codificação, não seria em nada prejudicada. Mas este mesmo código visto acima que ocupa seis bytes, iria ocupar três se fosse possível codificar da seguinte forma:

union u_Interprocess
{
        union u_0
        {
                double  df_Value;      // Value of the terminal global variable...
                ulong   IdGraphic;     // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {           
    		char  isPlay    : 1// Indicates whether we are in Play or Pause mode...
    		char  isWait    : 1// Tells the user to wait...                
    		char  isHedging : 1;   // If true we are in a Hedging account, if false the account is Netting...                
    		char  isSync    : 1// If true indicates that the service is synchronized...
                ushort  iPosShift;     // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};

Este código imediatamente acima, não é compreendido pelo compilador da linguagem MQL5. Pelo menos não no momento que este artigo esta sendo escrito. Mas aqui estamos dizendo ao compilador, que iremos usar um bit apenas em cada uma das declarações. É trabalho do compilador, e não do programador, ajustar de maneira adequada a variável, a fim de que quando formos acessar o bit, poderemos fazer isto de forma bem mais controlada. Usando para isto um rotulo, e não algum tipo de diretiva de definição de compilação.

 Apesar de ser possível ainda sim ter tal construção usando MQL5, nativo. Fazer isto usando diretivas de compilação, é algo muito sujeito a erros e falhas, além de dificultar, e muito a parte da manutenção do código. Apenas gostaria de deixar claro, este tipo de coisa. Para que você não imagine que a linguagem não nos permite fazer algumas coisas.

Mas se o mundo lhe dá limões, vamos fazer uma limonada. E não ficar nos queixando pelo fato do mundo não ser como esperamos, que ele deveria ser. Feito estas mudanças no arquivo Interprocess.mqh. Precisamos ir para um outro ponto. Agora modificaremos o arquivo C_ConfigService.mqh. Dentro deste arquivo faremos algumas mudanças. Começando pelo código mostrado abaixo:

        private :
                enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
                struct st001
                {
                        C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
                        int     Line;
                }m_GlPrivate;
                string  m_szPath;
                bool    m_AccountHedging;

Adicionamos esta variável privativa a classe C_ConfigService. Isto nos fornecer uma memória do que processaremos a seguir. Antes, precisamos inicializar esta variável. Isto é feito no constructor da classe, conforme pode ser visto no seguinte ponto:

C_ConfigService()
        :m_szPath(NULL),
         m_ModelLoading(1),
         m_AccountHedging(false)
   {
   }

Pois bem, o fato de precisarmos de uma variável para servir de memória, é por que precisamos que o Indicador de controle, crie para nos a variável global de terminal, que irá receber os dados já configurados. No entanto esta variável global de terminal, somente irá aparecer, quando o template carregar o indicador de controle no gráfico. E o gráfico somente irá aparecer, depois que o serviço de replay / simulador, estiver terminado de configurar todo o sistema. Se o valor que iremos configura, segundo o que será indicado pelo usuário, não fosse armazenado em algum lugar, não conseguiríamos informar ao Expert Advisor, o tipo de conta. Mas fazendo uma programação cuidadosa, podemos sim, passar o tipo de conta configurada no serviço de replay / simulador para o Expert Advisor. Assim ele poderá saber como trabalhar. Isto caso você esteja utilizando o conhecimento que foi passado na sequencia sobre como criar um Expert Advisor automático. E já esteja pensando em como experimentar a aplicação que você desenvolveu.

Muito bem, já que a variável é uma do tipo privativa e quem irá configurar a variável global de terminal, será a classe C_Replay. Precisamos de um método, para que a classe C_Replay, possa acessar a variável sobre o tipo de conta. Para isto contaremos com o seguinte procedimento:

inline const bool TypeAccountIsHedging(void) const
   {
      return m_AccountHedging;
   }

Este retornará o valor para nos. Agora precisamos fazer com que a configuração feita pelo usuário, possa ser capturada e convertida em algo útil para nosso código. Para isto precisaremos adicionar algum código novo no sistema de captura. Este pode ser visto logo abaixo:

inline bool Configs(const string szInfo)
   {
      const string szList[] = {
                               "PATH",
                               "POINTSPERTICK",
                               "VALUEPERPOINTS",
                               "VOLUMEMINIMAL",
                               "LOADMODEL",
                               "ACCOUNT"
                              };
      string  szRet[];
      char    cWho;
                                
      if (StringSplit(szInfo, '=', szRet) == 2)
      {
         StringTrimRight(szRet[0]);
         StringTrimLeft(szRet[1]);
         for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
         switch (cWho)
         {
            case 0:
               m_szPath = szRet[1];
               return true;
            case 1:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
               return true;
            case 2:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
               return true;
            case 3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
               return true;
            case 4:
               m_ModelLoading = StringInit(szRet[1]);
               m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading);
               return true;
            case 5:
               if (szRet[1] == "HEDGING") m_AccountHedging = true;
               else if (szRet[1] == "NETTING") m_AccountHedging = false;
               else
               {
                  Print("Entered account type is not invalid.");                                                          
                  return false;
               }
               return true;                    
         }
         Print("Variable >>", szRet[0], "<< not defined.");
      }else
         Print("Configuration definition >>", szInfo, "<< invalidates.");
                                        
      return false;
   }

Todo o código que se encontra em destaque, foi adicionado para podermos, no arquivo de configuração, dizer qual será a conta usada. Vejam que fazer isto, foi algo extremamente simples, porém perfeitamente funcional. Agora se você observar no arquivo de configuração irá ver algo do tipo mostrado logo abaixo:

[Config]
Path = Forex\EURUSD
PointsPerTick = 0.00001
ValuePerPoints = 1.0
VolumeMinimal = 0.01
Account = HEDGING

ou algo muito parecido com este outro mostrado abaixo:

[Config]
Path = Petrobras PN
PointsPerTick = 0.01
ValuePerPoints = 1.0
VolumeMinimal = 100.0
Account = NETTING

Notem que temos ativos diferentes que poderão ser utilizados pelo serviço de replay / simulador. Mas como usuário, você poderá definir que tipo de conta deverá ser utilizada. Isto de forma bastante simples e sem complicações. Desta maneira, você poderá estender seus estudos a qualquer tipo de mercado. Permitindo utilizar qualquer metodologia conhecida ou que você desenvolva. Agora vamos ver como fazer para que a classe C_Replay, defina estes dados para nos. Já que o trabalho na classe C_ConfigService, esta terminado. Para que isto aconteça, a configuração dada pelo usuário, seja vista por todo o sistema de replay / simulador, não iremos ter muito trabalho. Tudo que precisaremos fazer é mostrado no código abaixo:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      info.s_Infos.isHedging = TypeAccountIsHedging();
      info.s_Infos.isSync = true;
      GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

A linha riscada foi removida do código e o motivo é que precisamos que a classe C_Replay, carregue o valor da variável que foi postada pelo Indicador de controle, a ponto de podermos saber com o que estamos lidando. Lembre-se: Não faça suposições. Pode ser quem futuramente, precisamos passar valores para o serviço, assim que o indicador de controle começa a trabalhar. Feito isto, lançamos os valores que o usuário definiu, e indicamos que o sistema foi sincronizado. Depois disto lançamos o valor para o MetaTrader 5, disponibilizar os dados via variável global de terminal. A parte referente ao serviço de replay / simulador, termina aqui. Mas a parte da codificação ainda não terminou, precisamos agora modificar o Expert Advisor, para que ele possa saber como o replay / simulação, foi configurado pelo usuário do sistema. Mas para explicar isto com mais calma, vamos para o próximo tópico.


Fazendo o Expert Advisor conhecer o tipo de conta que esta sendo usada.

Antes de entender de fato como fazer para que o Expert Advisor possa reconhecer o tipo de conta, definido pelo usuário. Será preciso você entender como todo o sistema é de fato inicializado. Para isto, veja a imagem 01, logo abaixo que mostra de forma bastante resumida como o processo de inicialização acontece.

Figura 01

Figura 01 - Processo de inicialização do sistema de replay / simulação.

Por que entender esta figura 01 é tão importante ??? O motivo é que a classe C_Terminal, é uma classe compartilhada entre o Indicador de controle e o Expert Advisor. Se você não compreender isto, não conseguirá entender como, e por que o sistema consegue se estabelecer na plataforma MetaTrader 5, a ponto de seguir o que foi configurado pelo usuário. E mais do que isto, se você não compreender esta inicialização, pode acabar fazendo bobagens, ao editar o arquivo que configurará o replay / simulação. Fazendo com que o sistema não funcione da forma como deveria. Assim ao invés de ter uma experiência próxima do que seria o mercado real. Você pode acabar tendo uma falsa ideia, de como deveria agir e se comportar no momento que for colocar o estudo feito no replay / simulação em prática. Então entender a figura 01.

  • Todas as setas em AZUL indicam o primeiro estágio de inicialização. Isto acontece, no exato momento, que você, como usuário faz com que o serviço seja iniciado pelo MetaTrader 5.
  • Uma vez feito isto, passamos para o segundo estágio, este é executado, quando o serviço, cria o gráfico, e utiliza o template, para inicializar o Expert Advisor e o Indicador de controle. Isto é representado pela seta em AMARELO.
  • O próximo estágio, que é representado pelas setas em VERDE, temos a criação da variável global de terminal, pelo indicador de controle, e a configuração desta mesma variável pelo serviço de replay / simulação. Neste ponto você pode pode notar que a classe C_Terminal envia dados para o indicador de controle, mas não para a classe C_Manager, que estará sendo inicializada neste estágio pelo Expert Advisor.
  • O último estágio, que são as setas em VIOLETA, temos a configuração, com base nos dados feitos pelo usuário no arquivo que dita como o replay / simulação irá funcionar, das informações que serão necessárias para o Expert Advisor, venha a de fato saber que tipo de conta estará sendo utilizada.

Apesar de parecer confuso, temos que tomar o máximo de cuidado, de maneira que a classe C_Terminal, permita ao Indicador de controle fazer o seu trabalho. Mas ao mesmo tempo que não permita que o Expert Advisor faça qualquer coisa antes da classe C_Manager estar devidamente configurada. E por que estou dizendo que o gerenciamento, deverá ser feito na classe C_Terminal e não na classe C_Manager ??? Talvez fosse mais simples fazer isto na classe C_Manager, por que não ?!?! O motivo não está em simplesmente escolher qual classe comportará as coisas. O real motivo é a classe C_Orders. Se o sistema de gerenciamento estiver na classe C_Manager, a classe C_Orders não poderia ter acesso aos dados que precisamos. Mas o fato de colocar o gerenciamento na classe C_Terminal, faz com que, mesmo usando uma outra classe depois, para gerir as ordens, a mesma poderá ter acesso aos dados que precisaremos. Mas não podemos, como foi visto na figura 01, fazer o trabalho de qualquer maneira, precisamos fazer com que ele seja feito de forma o mais adequada possível. Caso contrário, todo o sistema poderá simplesmente travar assim que for colocado em funcionamento. Sei que é difícil entender como ele poderia travar. Mas acreditem, se a programação não for feita com bastante rigor o sistema irá travar. Assim podemos ver como o sistema foi implementado. Começando com o código abaixo:

class C_Terminal
{
        protected:
                enum eErrUser {ERR_Unknown, ERR_PointerInvalid};
                enum eEvents {ev_Update};
//+------------------------------------------------------------------+
                struct st_Terminal
                {
                        ENUM_SYMBOL_CHART_MODE   ChartMode;
                        ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
                        long    ID;
                        string  szSymbol;
                        int     Width,
                                Height,
                                nDigits;
                        double  PointPerTick,
                                ValuePerPoint,
                                VolumeMinimal,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
                st_Terminal m_Infos;
                struct mem
                {
                        long    Show_Descr,
                                Show_Date;
                        bool    AccountLock;
                }m_Mem;

Esta variável irá dar acesso, pelos meios já implementados antes deste artigo, ao tipo de conta. Com isto, não precisaremos implementar nenhum novo código na classe, apenas para obter esta informação, o que é muito bom. Mas também temos uma outra variável. Já esta daqui irá servir como TRAVA, mas logo vocês entenderão o por que, e como ela será usada. Continuando, agora temos que ver o constructor da classe. Este pode ser visto logo abaixo:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      m_Mem.AccountLock = false;
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
      ResetLastError();
   }

Aqui inicializamos a variável de travamento, afim de dizer a classe C_Terminal, que o valor de tipo de conta não foi ainda implantado. Mas nesta linha, fazemos algo, e isto é importante. Quando o sistema detectar que o ativo NÃO É O ATIVO DE REPLAY, a classe C_Terminal inicializará os dados da conta. Caso seja o ativo de replay, os dados não serão inicializados agora, mas sim depois. Isto na classe C_Manager. Entendam isto, e de forma definitiva, pois será de suma importância dentro de muito em breve. Quem inicializa os dados quando o ativo é um ativo de replay é a classe C_Manager. Quando o ativo NÃO é de replay, a inicialização se dá neste momento. Mas de qualquer forma, um procedimento será chamado, e este é visto logo abaixo:

inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
   {
      if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
      m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
   }

Este procedimento é extremamente importante. Sem ele o tipo de conta que estará sendo utilizado, seria uma incógnita. Já que você estaria apenas supondo que este ou aquele tipo estaria sendo utilizado. Mas é importante evitar que uma vez feita a inicialização da variável de tipo de conta, que ela seja novamente modificada. Para fazer isto usamos esta trava daqui. Reparem que se, e somente se, a variável de trava estiver como sendo falso, poderemos inicializar o valor com base no argumento passado ao procedimento. Uma vez feito isto, a variável não poderá ser modificada durante todo o resto da execução do código. Um outro detalhe é que iremos nos limitar aos tipos NETTING e HEDGING. O motivo é que o tipo EXCHANGE tem um funcionamento muito parecido como o tipo NETTING. Como estamos usando o sistema para ser operado de maneira bem próxima ao mercado real, não vejo um problema em nos limitarmos aos tipos NETTING e HEDGING. Desta forma terminamos a parte referente ao código presente na classe C_Terminal. Agora vamos ver o que foi preciso mudar na classe C_Manager. Basicamente nesta classe, C_Manager, tudo que foi preciso fazer, foi modificar o código do constructor. Agora ele irá se apresentar como mostrado abaixo:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
      u_Interprocess info;
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                                                                        
      if (def_InfoTerminal.szSymbol == def_SymbolReplay)
      {
         do
         {
            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750);
         }while ((!info.s_Infos.isSync) && (!_StopFlag));
         def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
      };
      switch (def_InfoTerminal.TypeAccount)
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
      }
      Print("Detected Account ", szInfo);
   }

Você pode olhar e que nada mudou. Ou pior, você pode estar sem entender o que esta acontecendo aqui. O que não seria uma boa coisa. Já que aqui temos um laço onde em determinadas condições, pode entrar em loop infinito. Mas grande parte do código continua idêntico ao que era antes. Com uma pequena diferença, que é justamente neste ponto. Antes aqui, testávamos todos os tipos de conta. Agora testaremos apenas dois. E a informação usada para dizer qual o tipo de conta, está na variável que foi criada lá na classe C_Terminal. Mas vamos observar com um pouco mais de cuidado a parte realmente nova no código. Esta se inicia, quando é identificado que o ativo usado é o mesmo usado no sistema de replay / simulação. Se este teste passar, entraremos em um duplo laço. Onde temos um primeiro laço externo, e um outro laço aninhado a este primeiro.

Neste laço aninhado, aguardaremos que o Indicador de controle, crie a variável global de terminal, a fim de que o serviço de replay / simulador, possa configurar a mesma. Caso o programa seja encerrado pelo usuário, ou o indicador de controle tenha criado a variável global de terminal. Iremos sair deste laço aninhado, e entraremos no laço externo. Este laço externo, somente irá ser encerrado em duas situações: A primeira é que o serviço de replay / simulador, tenha configurado a variável global de terminal. A segunda situação, seria o encerramento do programa por parte do usuário. Fora estas duas situações, o laço não será encerrado, voltando a entrar no laço aninhado. Se tudo estiver ocorrido bem e o laço externo tenha sido encerrado. Será envidado o valor a ser usado como tipo de conta, para a classe C_Terminal. 


Conclusão

Neste artigo, resolvemos um problema que apesar de ser pequeno, iria nos causar muitas dores de cabeça no futuro. Espero que você tenha compreendido a importância de tal procedimento. Mas principalmente como foi possível resolver a questão usando o MQL5. Já no anexo você terá acesso a código fonte. Assim como também aos arquivos, já atualizados, a serem utilizados no replay / simulador. Se você já está utilizando o serviço, mesmo sem de fato poder treinar nele, não se esqueça de atualizar também os seus arquivos responsáveis por configurar o serviço de replay / simulador. A fim de que o ativo passe a pertencer ao tipo de conta adequada. Caso isto não seja feito, o sistema irá adotar, por padrão o tipo de conta HEDGING.

Isto pode lhe trazer alguns aborrecimentos futuros. Então não se esqueça de fazer as atualizações a partir deste momento.


Arquivos anexados |
Files_-_FOREX.zip (3744 KB)
Files_-_BOLSA.zip (1358.28 KB)
Files_-_FUTUROS.zip (11397.55 KB)
StringFormat(). Visão geral, exemplos de uso prontos StringFormat(). Visão geral, exemplos de uso prontos
O artigo é uma continuação da revisão da função PrintFormat(). Veremos brevemente a formatação de strings usando StringFormat() e seu uso posterior no programa. Escreveremos modelos para exibir informações sobre um símbolo no log do terminal. Este artigo será útil tanto para iniciantes quanto para desenvolvedores experientes.
Aprendendo PrintFormat() e obtendo exemplos prontos para uso Aprendendo PrintFormat() e obtendo exemplos prontos para uso
Este artigo será útil tanto para iniciantes quanto para desenvolvedores experientes. Nele, analisaremos a função PrintFormat(), veremos exemplos de formatação de strings e escreveremos modelos para a exibição de diferentes informações no log do terminal.
Funções em Aplicativos MQL5 Funções em Aplicativos MQL5
As funções são componentes essenciais em qualquer linguagem de programação. Entre outras coisas, elas ajudam os desenvolvedores a aplicar o princípio DRY (don't repeat youself, não se repita). O artigo fala sobre funções e sua criação no MQL5 com a ajuda de aplicativos simples que enriquecem seu sistema de negociação, sem complicá-lo.
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.