Discussão do artigo "Expert Advisor multiplataforma: Sinais"

 

Novo artigo Expert Advisor multiplataforma: Sinais foi publicado:

No artigo, são discutidas as classes CSignal e CSignals, que serão usadas em Expert Advisors multiplataforma. Além disso, serão examinadas as diferenças entre MQL4 e MQL5 quanto à organização de dados necessários para avaliar os sinais de negociação obtidos. O resultado será um código compatível com os compiladores das duas versões.

Exemplo #4: Expert Advisor com base no HA e MA

Nosso último exemplo é a combinação dos indicadores MA e HA para serem incluídos no EA. Não há muitas diferenças neste exemplo. Simplesmente adicionamos as definições de classe encontradas nos 2º e 3º exemplos, e depois adicionamos os ponteiros para instâncias CSignalMA e CSignalHA, na instância CSignals. O seguinte mostra um resultado de teste usando este Expert Advisor.

(MT4)

signal_ha_ma (MT4)

Autor: Enrico Lambino

 
MetaQuotes Software Corp.:

Novo artigo Expert Advisor multiplataforma: Sinais foi publicado:

Autor: Enrico Lambino


Olá, Enrico. Acabei de analisar seu trabalho em busca de uma solução para meu problema. Ao compilar a classe StopBase, recebo uma lista de erros, veja abaixo

implicit conversion from 'number' to 'string'   OrderStopBase.mqh       395     25
implicit conversion from 'number' to 'string'   OrderStopBase.mqh       467     26
implicit enum conversion        OrderStopBase.mqh       468     33
implicit enum conversion        OrderStop.mqh   37      33
implicit conversion from 'number' to 'string'   OrderStop.mqh   44      43
'COrderBase' - import not defined       OrderStop.mqh   54      7
'else' - semicolon expected     OrderStop.mqh   81      4
')' - unexpected token  OrderStop.mqh   81      46
implicit conversion from 'number' to 'string'   StopBase.mqh    767     22
implicit conversion from 'number' to 'string'   StopBase.mqh    774     28
implicit conversion from 'number' to 'string'   StopBase.mqh    796     22
implicit conversion from 'number' to 'string'   StopBase.mqh    803     28
implicit conversion from 'number' to 'string'   StopBase.mqh    698     36
'OrderType' - cannot convert enum       Stop.mqh        227     52
'StopLossCustom' - no one of the overloads can be applied to the function call  Stop.mqh        228     61
could be one of 2 function(s)   Stop.mqh        228     61
   double CStopBase::StopLossCustom(const string,const ENUM_ORDER_TYPE,const double)    StopBase.mqh    136     22
   bool CStopBase::StopLossCustom()     StopBase.mqh    99      22
'OrderType' - cannot convert enum       Stop.mqh        215     71
'TakeProfitCustom' - no one of the overloads can be applied to the function call        Stop.mqh        215     98
could be one of 2 function(s)   Stop.mqh        215     98
   double CStopBase::TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double)  StopBase.mqh    140     22
   bool CStopBase::TakeProfitCustom()   StopBase.mqh    111     22
implicit conversion from 'number' to 'string'   Stop.mqh        250     39
implicit enum conversion        Stop.mqh        291     31
implicit conversion from 'number' to 'string'   Stop.mqh        292     36

Ignorando os avisos, os erros parecem vir de problemas de tipo de ordem. Além disso, a compilação de seu exemplo signal_ma produz os seguintes erros

'GetPointer' - parameter conversion not allowed OrderStopVirtualBase.mqh        51      39
'GetPointer' - parameter conversion not allowed OrderStopVirtualBase.mqh        58      41
'=' - type mismatch     OrderStopsBase.mqh      106     23
'=' - type mismatch     OrderStopsBase.mqh      108     23
'=' - type mismatch     OrderStopsBase.mqh      110     23
'=' - type mismatch     OrderStopsBase.mqh      180     20
'=' - type mismatch     OrderStopsBase.mqh      182     20
'=' - type mismatch     OrderStopsBase.mqh      184     20

De onde vêm esses erros e como posso resolvê-los? Em uma questão relacionada, pelo que sei, a Substituição de Liskov estipula que devemos programar para uma interface, pois a superclasse pode ser substituída por sua classe base. Por que você optou por passar objetos para as funções (que são interfaces em si), em vez de usar suas classes de base? Posso estar errado, mas acredito que essa seja a melhor prática, não é?

Agradeço antecipadamente por sua ajuda e pelo excelente trabalho que fez.
 

Olá, Shephard, obrigado por seu comentário. Com relação às suas perguntas:

  1. Infelizmente, neste momento não há como compilar o StopBase.mqh por conta própria. Embora os compiladores MQL4 e MQL5 aceitem declarações progressivas, eles dão um erro se você tentar acessar um método ou membro da classe que foi declarado progressivamente. Você precisará compilar StopBase.mqh com uma classe muito maior, como COrderManager ou CExpertAdvisor (CStop e CStops são componentes dessas duas classes).
  2. Com relação aos erros de incompatibilidade de tipos, não os encontrei nos arquivos originais (o código-fonte foi modificado por você?).
  3. Acho que, na maioria dos casos, as classes de base podem ser passadas para os métodos de classe sem problemas. Ao codificar especialistas usando a biblioteca, seria mais fácil presumir que a classe base não existe. Mas alguns objetos têm novos métodos virtuais e também métodos não virtuais. Esses dois conjuntos de métodos não podem ser acessados somente pela classe base. Se você usar as classes derivadas em vez da base, o compilador poderá selecionar a versão correta das classes derivadas, portanto, usar as classes derivadas é mais abrangente do que usar somente as classes base.
 
Enrico Lambino:

Olá, Shephard, obrigado por seu comentário. Com relação às suas perguntas:

  1. Infelizmente, neste momento não há como compilar o StopBase.mqh por conta própria. Embora os compiladores MQL4 e MQL5 aceitem declarações progressivas, eles dão um erro se você tentar acessar um método ou membro da classe que foi declarado progressivamente. Você precisará compilar StopBase.mqh com uma classe muito maior, como COrderManager ou CExpertAdvisor (CStop e CStops são componentes dessas duas classes).
  2. Com relação aos erros de incompatibilidade de tipos, não os encontrei nos arquivos originais (o código-fonte foi modificado por você?).
  3. Acho que, na maioria dos casos, as classes de base podem ser passadas para os métodos de classe sem problemas. Ao codificar especialistas usando a biblioteca, seria mais fácil presumir que a classe base não existe. Mas alguns objetos têm novos métodos virtuais e também métodos não virtuais. Esses dois conjuntos de métodos não podem ser acessados somente pela classe base. Se você usar as classes derivadas em vez da base, o compilador poderá selecionar a versão correta das classes derivadas, portanto, usar as classes derivadas é mais abrangente do que usar somente as classes base.

Olá, Enrico,

Muito obrigado por sua resposta rápida. Entendo seu raciocínio sobre passar classes concretas em vez de suas classes de base. Algumas implementações que são simples em C++ simplesmente não são possíveis em MQL, mas é assim que as coisas são.

Com relação às incompatibilidades de tipos, não, eu não alterei nada. Simplesmente baixei e compilei. Eu estava observando seu trabalho em busca de um problema incômodo que eu tinha em meu próprio sistema. Sua abordagem é realmente excelente.

Desenvolvi meu sistema assim. Considerei um sistema de saída de sinal como uma estratégia. Digamos que eu tenha um sistema de crossover MA; se eu criar um objeto desse sistema com, digamos, EURUSD, isso será uma estratégia. Adiciono essa estratégia a uma lista de estratégias. Posso criar outra estratégia e adicioná-la à lista, mas as estratégias não são chamadas pelo OnTick, elas fazem parte de um padrão Observer, são atualizadas ou chamadas quando ocorrem eventos específicos, como uma nova barra de 5 minutos, uma nova barra Renko de 10 pip etc. Tive problemas com os Stops, o que desencadeou minha pesquisa.

Mais uma vez, obrigado por dedicar seu tempo para compartilhar seu conhecimento e habilidade, e por sua pronta resposta.

 

Olá, Shephard,

Obrigado por compartilhar suas sugestões e percepções.

Acho que o que você está fazendo é uma ótima ideia. No momento, estou trabalhando nos últimos artigos desta série (paradas de ordens e paradas incluídas). Não tenho certeza se as classes de paradas são adequadas (isso depende da sua implementação). Se você puder fazê-las funcionar independentemente do gerenciador de ordens, isso seria bom, pois ajudaria a simplificar seus objetos de classe. Mas espero que você as considere úteis em seu trabalho.

Com relação à compilação:

  1. Somente os arquivos de código-fonte/cabeçalho principais e os arquivos de classe base podem ser compilados sem erros (com exceção daqueles que usam declarações forward). Esse é o motivo pelo qual, nos exemplos, usei a diretiva #include nos arquivos de cabeçalho para os arquivos de base (não nos arquivos específicos da linguagem).
  2. Você deve usar o compilador correto (compilar um arquivo mq5 com um compilador MQL4 resultará em erros de compilação).
 

Olá, Enrico,

Estou testando seu módulo de sinais. Tenho um sinal SHORT e um sinal NEUTRAL. Por que finalmente obtive o CMD_VOID? Quando CMD_VOID aparece?

Na verdade, isso aparece em CSignalsBase::Check

if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }

Há apenas 2 sinais. O sinal anterior era CMD_SHORT. O sinal atual é CMD_NEUTRAL. Você pode confirmar que CMD_SHORT e CMD_NEUTRAL dão como resultado CMD_VOID?

Se meu primeiro sinal fosse CMD_NEUTRAL e o segundo CMD_SHORT, o sinal total seria CMD_SHORT. Mas se o primeiro sinal for CMD_SHORT e o segundo CMD_NEUTRAL, o resultado será CMD_VOID.

Acho que deve ser assim:

if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open && signal_open!=CMD_NEUTRAL)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }
 
if(m_new_signal)
     {
      if(m_signal_open==m_signal_open_last)
         m_signal_open = CMD_NEUTRAL;
      if(m_signal_close==m_signal_close_last)
         m_signal_close= CMD_NEUTRAL;
     }

Isso não é totalmente correto em termos de sinais de fechamento. Por que não pode haver dois sinais de fechamento consequentes na mesma direção?

Por exemplo, tenho um sinal de entrada curto, o próximo sinal de saída longo (fechamento curto), o próximo sinal de entrada longo, o próximo sinal de entrada curto, o próximo sinal de saída longo. Portanto, o último sinal para sair da posição curta não funcionará, pois é na mesma direção do sinal de saída anterior.

 
mbjen:

Isso não é totalmente correto em termos de sinais de fechamento. Por que não pode haver dois sinais de fechamento consequentes na mesma direção?

Por exemplo, tenho um sinal de entrada curto, o próximo sinal de saída longo (fechamento curto), o próximo sinal de entrada longo, o próximo sinal de entrada curto, o próximo sinal de saída longo. Portanto, o último sinal para sair da posição vendida não funcionará, pois é na mesma direção do sinal de saída anterior.

Depende do que você deseja alcançar e de como os sinais devem ser avaliados.

O último código que você postou só é executado quando o membro da classe m_new_signal é definido como true. Isso serve para negociar somente com novos sinais. Você pode definir esse membro de classe protegido usando o método NewSignal disponível na classe.

 
Enrico Lambino:

Depende do que você deseja alcançar em relação a como os sinais devem ser avaliados.

O último código que você postou só é executado quando o membro da classe m_new_signal é definido como true. Isso serve para negociar somente com novos sinais. Você pode definir esse membro de classe protegido usando o método NewSignal disponível na classe.


Eu sei disso. Mas se eu o definir como falso, isso também afetará meus sinais de entrada. Tudo bem para o sinal de entrada, mas não para o de saída, pois as regras de saída podem ser diferentes. Pode ser um sinal de reversão ou de saída, portanto, dois sinais de saída na mesma direção é algo normal.

 

Parece que em m_signal_close é salvo o valor do sinal anterior em SignalBase.mqh. Por exemplo, tenho um sinal de saída. Se ele o verificar e o método Calculate() retornar falso, ele fornecerá o último valor de sinal que estava na saída anterior.

 

Oi mbjen,

mbjen:

Olá, Enrico,

Estou testando seu módulo de sinais. Tenho um sinal SHORT e um sinal NEUTRAL. Por que finalmente obtive o CMD_VOID? Quando CMD_VOID aparece?

Na verdade, isso aparece em CSignalsBase::Check

Há apenas 2 sinais. O sinal anterior era CMD_SHORT. O sinal atual é CMD_NEUTRAL. Você pode confirmar que CMD_SHORT e CMD_NEUTRAL dão como resultado CMD_VOID?

Se meu primeiro sinal fosse CMD_NEUTRAL e o segundo CMD_SHORT, o sinal total seria CMD_SHORT. Mas se o primeiro sinal for CMD_SHORT e o segundo CMD_NEUTRAL, o resultado será CMD_VOID.

Acho que deve ser assim:

Desculpe-me por ter ignorado sua primeira pergunta. Eu não havia notado isso até agora. Foi um feedback muito bom.

Obrigado por apontar isso. Sim, você está certo. Atualizarei o código e revisarei o artigo.

mbjen:

Eu sei disso. Mas se eu definir como falso, isso também afetará meus sinais de entrada. Tudo bem para o sinal de entrada, mas não para a saída, pois as regras de saída podem ser diferentes. Pode ser reversão ou sinal de saída, portanto, dois sinais de saída na mesma direção é algo normal.

Você tem razão nesse ponto. Mas, por exemplo, se eu quiser dar um sinal de saída somente em um cruzamento de MA, isso seria necessário. Acho que separá-los seria uma opção melhor do que o código atual:

if(m_new_signal)
 {
  if(m_signal_open==m_signal_open_last)
  m_signal_open = CMD_NEUTRAL;
 }
if(m_new_signal_close)
 {
  if(m_signal_close==m_signal_close_last)
  m_signal_close= CMD_NEUTRAL;
 }
mbjen:

Parece que em m_signal_close é salvo o valor do sinal anterior em SignalBase.mqh. Por exemplo, tenho um sinal de saída. Se ele o verificar e o método Calculate() retornar falso, ele fornecerá o último valor de sinal que estava na saída anterior.

O método Calculate é um método virtual. Se o método retornar falso, você deverá redefinir esses membros da classe para neutro dentro do próprio método.