English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
O protótipo do robô de negócio

O protótipo do robô de negócio

MetaTrader 5Exemplos | 26 dezembro 2013, 15:33
2 938 0
---
---

Introdução

O ciclo de vida de qualquer sistema de negociação é reduzido às posições. Isto vai além de qualquer dúvida. Mas quando se trata da realização algorítmica, se tem tantas opiniões quanto programadores. Todo mundo será capaz de resolver o mesmo problema de sua própria maneira, mas com o mesmo resultado final.

Com os anos de prática em programação diversas abordagens para construção de lógica e estrutura dos especialistas foram tentadas. No momento pode se argumentar que foi estabelecido um modelo de padrão claro que é usado em todos os códigos.

Essa abordagem não é 100% universal, mas pode mudar o seu método de planejar a lógica de especialistas. E o caso não é quais capacidades de trabalho com pedidos você quer usar o especialista. O ponto aqui - é o princípio da criação de um modelo de negócio.


1. Princípios de planejamento de sistemas de negociação e tipos de fontes de evento

A abordagem básica para o planejamento do algorítimo, usado pela maioria, é traçar uma posição desde sua abertura até o fechamento. Esta é a abordagem linear. E se você quiser fazer mudança ao código - elas geralmente levam a grandes complicações, uma vez que grande número de condições emergem e o código acumula novos ramos de análise.

A melhor solução para modelar um robô de negociação é "servir condições". E o princípio fundamental - analisar não como essa condição de especialidade e suas posições e ordens surgiram - mas o que devemos fazer com elas agora. Este princípio básico está mudando fundamentalmente a administração de negócio e simplifica o desenvolvimento de código.

Considere isto em mais detalhes.

1.1. O princípio de "Servir Condições"

Como já mencionado, o especialista não precisa saber como o estado atual foi alcançado. Ele deve saber o que fazer com ele, de acordo com o seu ambiente (valores de parâmetro, propriedades dos pedidos arquivadas, etc.).

Este princípio é relacionado diretamente ao fato de que o especialista existe de ciclo a ciclo (particularmente - turno a turno), e não deve ser preocupar com o que aconteceu com os pedidos no turno anterior. Portanto, você deve usar uma abordagem orientada a eventos para administrar pedidos. Ou seja, no turno atual, o especialista salva seu estado, que é o ponto de partida para decisão sobre o próximo turno.

Por exemplo, você deve remover todos os pedidos pendentes do especialista e só então continuar a analisar indicador e situar novos pedidos. A maioria do código exemplifica que nós temos visto o uso do ciclo "while (true) {try to remove}" ou o ciclo ligeiramente mais leve "while (k < 1000) {try to remove; k++;}". Nós pularemos a variante, aonde a nomeação de uma vez do comando de remoção sem erro de análise.

Este método é linear, ele "suspende" o especialista por um período de tempo indeterminado.

Portanto, será mais correto não laçar um especialista, mas armazenar o pedido para remover pedidos, para que a cada novo turno, este pedido seja checado enquanto tenta deletar pedidos pendentes. Neste caso, um especialista, enquanto está lendo os parâmetros de estado, sabe que neste momento ele deve deletar pedidos. E ele tentará removê-los. Se um erro de negócio acontecer, um especialista vai simplesmente bloquear análises futuras e trabalhar antes do próximo ciclo.

1.2. O Segundo Princípio Primordial de Planejamento - é a abstração máxima possível da considerada direção de posição (Compra/Venda), moeda e tabela. Todas as funções especialistas devem ser implementadas de tal modo que, a direção ou símbolo são analisados em casos raros, quando realmente não podem ser evitados (por exemplo, quando você considera o crescimento favorável de preço para a posição de abertura, embora existam diferentes opções de evitar especificidades). Sempre tente evitar tal planejamento de "baixo nível". Isto reduzirá o código e o processo de escrever funções pelo menos duas vezes. E os fará independentes do negócio.

A implementação deste princípio é para repor a análise explícita de tipo de pedidos, parâmetro de símbolos e parâmetros calculados dependentes com as funções de macro. No artigo a seguir, nós cobrimos esta implementação em detalhes.

1.3. Terceiro princípio - segmentação do algoritmo em lexemas lógicos (módulos independentes)

Na prática, podemos dizer que a melhor abordagem é a separação de operações especialistas em funções individuais. Eu acho que você vai concordar que é difícil escrever todo o algoritmo da escrita do especialista em uma função, e isto complica análises e edições subsequentes. Então nós não devemos fazer isto no MQL5, o qual agora provém controle praticamente total sobre o seu ambiente.

Portanto, as lexemas lógicas (por exemplo, abertura, acompanhamento, fechamento de pedidos) devem ser implementadas separadamente entre si, com análise total dos parâmetros e eventos ambientais. Através dessa abordagem, o especialista se torna flexível no planejamento. Você pode facilmente adicionar novos módulos independentes nele, sem tocar os já existentes, ou incapacitar módulos existentes sem alterar o código principal.

Estes três princípios tornam possível criar um protótipo único para todos os especialistas, que você pode modificar e adaptar facilmente a qualquer dada tarefa.

Fontes de eventos para o sistema especialista são:


1. Indicadores. Um exemplo - é a análise dos valores indicadores lineares, suas interseções, combinações, etc. Indicadores também podem ser: tempo atual, a data obtida da Internet, etc. Na maioria dos casos, os eventos indicadores são usados para sinalizar a abertura e fechamento de pedidos. Menos para seus ajustes (usualmente Trailing Stop Loss ou pedido pendente para o indicador).

Por exemplo, a implementação prática de indicador pode ser chamada um especialista, que analisa interseção da rápida e devagar MA com a abertura futura da posição na direção da interseção.

2. Pedidos existentes, posições e seus estados. Por exemplo, o tamanho atual do prejuízo ou lucro, a presença/ausência de posições ou pedidos pendentes, o lucro da posição de fechamento, etc. Implementação prática destes eventos é mais ampla e mais diversa, conforme existem mais opções de seus relacionamentos do que para os eventos indicadores.

O evento mais simples de um especialista, baseado apenas no evento de negócio, é o reabastecimento para posições médias existentes e a saída dela no lucro desejado. Ou seja, a presença de prejuízo em posição disponível será um evento para colocar um novo pedido comum.

Ou, por exemplo, Trailling Stop Loss. Esta função checa um evento, quando o preço se modifica em lucro para um número específico de pontos a partir do Stop Loss anterior. Como resultado, especialista atrai três Stop Loss atrás do preço.

3. Eventos externos. Embora tal evento não ocorra usualmente em sistemas puramente técnicos, mas geral deve ser considerado para tomar uma decisão. Isto inclui ajustar os pedidos, posições, processamento de erros de troca, processamento de eventos de tabela (mover/criar/deletar objetos, pressionar botões, etc.). Em geral, estes são eventos que não são verificáveis no histórico e ocorrem apenas quando especialistas trabalham.

Um exemplo impressionante de como tais especialistas são sistemas de troca de informação com controle gráfico de negócios.

Toda a variedade de especialistas é baseada na combinação destes três fontes de eventos.

2. A classe base CExpertAdvisor - construtor especialista

Qual será o trabalho de um especialista em negócios? Um esquema geral das interações do programa MQL é mostrado no diagrama abaixo.

Figura 1. Esquema geral das interações dos elementos do programa MQL

Figura 1. Esquema geral das interações dos elementos do programa MQL

Como você pode ver nestes esquemas, primeiro vem a entrada para o trabalho em ciclo (isto pode ser um turno ou um sinal de cronômetro). Neste ponto, no primeiro bloco este turno pode ser filtrado sem processamento. Isto é feito naqueles casos aonde o especialista não é necessário para trabalhar em todo ponto, mas somente na nova barra, ou se o especialista simplesmente não é autorizado a trabalhar.

Então o programa vai para o segundo bloco - módulos de trabalho com pedidos e posições, e somente então o bloco de processamento de eventos é nomeado dos módulos. Cada modo pode inquirir apenas o seu evento de interesse.

Esta sequência pode ser chamada de esquema com lógica direta, uma vez que ela primeiro determina O QUE o especialista fará (qual módulo de processamento é usado) e só então ela implementa COMO e PORQUE, ela fará (obtendo sinais de eventos).

A lógica direta é consistente com a nossa percepção de mundo e lógica universal. Afinal, um homem pensa primeiro conceitos concretos, depois ele os resume, os classifica e identifica suas interrelações.

Especialistas em planejamento não são exceções a esse respeito. Primeiro, é declarado o que um especialista deve fazer (abrir e fechar posições, acionar a parada de segurança), e só depois é especificado em quais eventos e como isto deve ser feito. Mas em nenhum caso vice e versa: receber sinais e pensar aonde e como os processá-los. Esta é a lógica reversa, e é melhor não usá-la, uma vez que como resultado você conseguirá um código pesado com grande número de ramos de condição.

Aqui está um exemplo de lógica reversa e direta. Tome a abertura/fechamento pelo sinal RSI.

  • Na lógica reversa, o especialista começa obtendo o valor indicador, e depois ele checha a direção do sinal e o que você deve fazer com a posição: abrir a Compra, e fechar a Venda, ou vice e versa - abrir a Venda e fechar a Compra. Isto é, o ponto de entrada é obter e analisar o sinal.
  • Na lógica direta, tudo é oposto. Especialistas têm dois módulos de abertura e fechamento de posições, e eles simplesmente checam as condições para executar estes módulos. Ou seja, após dar entrada ao modulo de entrada, o especialista recebe o valor indicador e checa se este é um sinal para iniciar. Então, após dar entrada aos módulos de encerramento, o especialista checa se este é um sinal para fechar a posição. Isto é, não há ponto de entrada - existem módulos operando independentemente do sistema de análise de estado (o primeiro princípio do planejamento).

Agora, se você quer complicar o especialista, será muito mais fácil utilizando a segunda variante ao invés da primeira. Isto será suficiente para criar um novo módulo de processamento de evento.

E, na primeira variante, você terá de revisar a estrutura do processamento de sinal ou colá-la como uma função a parte.

Recomendação: Quando descrevendo o sistema de negociação, não inicie com as palavras como "1. Obtenha o sinal ... abrir pedido", mas para dividir imediatamente em sessões: "a) A condição de abertura de pedidos, b) Condições de manutenção de pedidos, etc." e em cada um analisar os sinais necessários.

Para melhor entender essa abordagem, aqui estão os diferentes esquemas de trabalho no contexto de quatro diferentes especialistas.

Figura 2. Exemplos de implementação de especialista

Figura 2. Exemplos de implementação de especialista

a) Especialista, baseado apenas em sinais de algum indicador. Pode abrir e fechar posições quando sinal está mudando. Exemplo - um especialista MA.
b) Especialista com controle gráfico de negócios.
c) Especialista baseado em indicadores, mas com adição de Trailling Stop Loss e tempo operante. Exemplo - escalpelando nas notícias com a posição de abertura na tendência pelo indicador MA.
d) Especialista sem indicadores, com tendenciamento de posições. Ele verifica parâmetros de posição apenas quando estiver abrindo uma nova barra. Exemplo - especialista de tendenciamento.

Como pode ser visto no esquema, qualquer sistema de negociação é muito fácil de descrever usando a lógica direta.


3. Implementação da classe especialista

Crie uma classe usando todas as regras e requerimentos mencionados acima, a qual será base para todos os futuros especialistas.

A funcionalidade mínima que deverá ter na classe CExpertAdvisor são as seguintes:

1. Inicialização

  • Registrar indicadores;
  • Estabelecer os valores inicias dos parâmetros;
  • Ajustar para o simbolo e prazo requeridos.

2. Funções de Obtenção de Sinais

  • Tempo de trabalho permitido (intervalos trocados);
  • Determinar o sinal para abrir/fechar posições ou pedidos;
  • Determinar o filtro (tendencia, tempo, etc.);
  • Cronômetro Iniciar/Parar.

3. Funções de serviço

  • Calcular o preço de abertura, os níveis SL e TP, o volume do pedido;
  • Enviar requisições de negócio (abrir, fechar, modificar).

4. Módulos de negócio

  • Processar os sinais e filtros;
  • Controlar as posições e pedidos;
  • Trabalhar nas funções especialistas: OnTrade(), OnTimer(), OnTester(), OnChartEvent().

5. Desinicialização

  • Mensagens de produção, relatórios;
  • Limpar mapa, descarregar indicadores.

Todas as funções da classe são divididas em três grupos. O esquema geral de funções agrupadas e suas descrições são apresentadas abaixo.

Figura 3. Esquema de agrupamento de funções de um especialista

Figura 3. Esquema de agrupamento de funções de um especialista

1. Funções de macro

Este pequeno grupo de funções é a base para trabalhar com tipos de pedidos, parâmetros de símbolos e valores de preço para estabelecer pedidos. Estas funções de macro fornecem o segundo principio do planejamento - abstração. Eles trabalham no contexto do símbolo, o qual é usado pelo especialista.

Funções de macro de conversão de tipos funcionam com a direção do mercado - compra ou venda. Portanto, para não criar suas próprias constantes, melhor usar as já existentes ORDER_TYPE_BUY e ORDER_TYPE_SELL. Aqui estão alguns exemplos de uso de macro e os resultados de seu trabalho.

   //--- Type conversion macro
   long       BaseType(long dir);        // returns the base type of order for specified direction
   long       ReversType(long dir);      // returns the reverse type of order for specified direction
   long       StopType(long dir);        // returns the stop-order type for specified direction
   long       LimitType(long dir);       // returns the limit-order type for specified direction

   //--- Normalization macro
   double     BasePrice(long dir);       // returns Bid/Ask price for specified direction
   double     ReversPrice(long dir);     // returns Bid/Ask price for reverse direction

   long dir,newdir;
   dir=ORDER_TYPE_BUY;
   newdir=ReversType(dir);               // newdir=ORDER_TYPE_SELL
   newdir=StopType(dir);                 // newdir=ORDER_TYPE_BUY_STOP
   newdir=LimitType(dir);                // newdir=ORDER_TYPE_BUY_LIMIT
   newdir=BaseType(newdir);              // newdir=ORDER_TYPE_BUY

   double price;
   price=BasePrice(dir);                 // price=Ask
   price=ReversPrice(dir);               // price=Bid

Quando desenvolvendo especialistas, as macro permitem a você não especificar a direção processada e ajuda a criar um código mais compacto.

2. Funções de serviço

Estas funções são designadas para trabalhar com pedidos e posições. Assim como a função de macro, elas também são de baixo nível. Para conveniência, elas podem ser divididas em duas categorias: funções de informação e funções executivas. Elas todas realizam apenas um tipo de ação, sem analisar nenhum evento. Elas realizam pedidos de administradores mais velhos de especialistas.

Exemplos de funções de informação: achar o preço máximo de abertura de pedidos pendentes atuais; achar como a posição foi fechada - com lucro ou prejuízo; obter o número e lista de bilhetes de pedidos de especialistas, etc.

Exemplos de funções executivas: fechamento de pedidos específicos, modificar o Stop Loss na posição específica, etc.

Este grupo é o maior. Este é o tipo de funcionalidade, na qual toda a rotina de trabalho de um especialista é baseada. O grande número de exemplos destas funções podem ser achadas no fórum em https://www.mql5.com/ru/forum/107476. Mas em adição a isto a biblioteca padrão do MQL5 já contém classes que tomam para si mesmas parte do trabalho em situar pedidos e posições, particularmente - a classe CTrade.

Mas qualquer tarefa sua necessitará criar novas implementações ou modificar ligeiramente as existentes.

3. Módulos de processamento de evento

O grupo destas funções é uma superestrutura de alto nível acima dos primeiros dois grupos. Como mencionado acima - estes são blocos prontos para o uso para qual o seu especialista é construído. De maneira geral, eles estão incluídos na função de processamento de evento do programa MQL: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). Este grupo não é numeroso, e os componentes destes módulos podem ser ajustados de tarefa para tarefa. Mas essencialmente nada muda.

Nos módulos tudo deve ser abstrato (o segundo princípio do planejamento) afim do mesmo módulo poder ser invocado tanto para comprar quanto para vender. Isto é alcançado, é claro, com a ajuda da macro.

Então, proceda com implementação

1. Inicialização, desinicialização

class CExpertAdvisor
  {
protected:
   bool              m_bInit;       // flag of correct initialization
   ulong             m_magic;       // magic number of expert
   string              m_smb;       // symbol, on which expert works
   ENUM_TIMEFRAMES      m_tf;       // working timeframe
   CSymbolInfo      m_smbinf;       // symbol parameters
   int               m_timer;       // time for timer

public:
   double              m_pnt;       // consider 5/3 digit quotes for stops
   CTrade            m_trade;       // object to execute trade orders
   string              m_inf;       // comment string for information about expert's work

Este é o grupo de parâmetros mínimos requeridos para as funções especialistas funcionarem.

Os parâmetros m_smb e m_tf são especialmente colocados nas propriedades do especialista, para facilmente dizer a ele em qual moeda e qual período trabalhar. Por exemplo, se você associar m_smb = "USDJPY", o especialista vai trabalhar neste símbolo, independentemente de qual símbolo ele foi executado. Se você estabelecer tf = PERIOD_H1, então todos os sinais e análises de indicadores tomarão lugar na planilha H1.

Posteriormente existem as classes de métodos. Os três primeiros métodos são a inicialização e a desinicialização de um especialista.

public:
   //--- Initialization
   void              CExpertAdvisor();                               // constructor
   void             ~CExpertAdvisor();                               // destructor
   virtual bool      Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization

O construtor e o destruidor na classe base não fazem nada.

O método Init() faz a inicialização de parâmetros especialistas pelo simbolo, prazo e número mágico.

//------------------------------------------------------------------ CExpertAdvisor
void CExpertAdvisor::CExpertAdvisor()
  {
   m_bInit=false;
  }
//------------------------------------------------------------------ ~CExpertAdvisor
void CExpertAdvisor::~CExpertAdvisor()
  {
  }
//------------------------------------------------------------------ Init
bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf)
  {
   m_magic=magic; m_smb=smb; m_tf=tf;         // set initializing parameters
   m_smbinf.Name(m_smb);                      // initialize symbol
   m_pnt=m_smbinf.Point();                    // calculate multiplier for 5/3 digit quote
   if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10;  
   m_trade.SetExpertMagicNumber(m_magic);     // set magic number for expert

   m_bInit=true; return(true);                // trade allowed
  }

2. Funções de obtenção de sinais

Estas funções analisam mercado e indicadores.

   bool              CheckNewBar();                          // check for new bar
   bool              CheckTime(datetime start,datetime end); // check allowed trade time
   virtual long      CheckSignal(bool bEntry);               // check signal
   virtual bool      CheckFilter(long dir);                  // check filter for direction

As duas primeiras funções tem implementação bem específica e pode ser usada em futuras filhas desta classe.

//------------------------------------------------------------------ CheckNewBar
bool CExpertAdvisor::CheckNewBar()          // function of checking new bar
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)      // copy bar
     { Print("CopyRates of ",m_smb," failed, no history"); return(false); }
   if(rt[1].tick_volume>1) return(false);   // check volume 
   return(true);
  }
//---------------------------------------------------------------   CheckTime
bool CExpertAdvisor::CheckTime(datetime start,datetime end)
  {
   datetime dt=TimeCurrent();                          // current time
   if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range
   if(start>=end) if(dt>=start|| dt<end) return(true);
   return(false);
  }

O segundo sempre depende destes indicadores que você esta usando. É simplesmente impossível ver estas funções para todas as classes.

O assunto principal - é importante entender que as funções de sinal CheckSignal() e CheckFilter() podem analisar absolutamente qualquer indicador e suas combinações! Ou seja, módulos de negócio, nos quais estes sinais serão subsequentemente incluídos, são independentes das fontes.

Isto lhe permite usar o especialista uma vez escrito, como modelo para especialistas que funcionem em um princípio similar. Apenas mude os indicadores analisados ou adicione novas condições de filtragem.

3. Funções de serviço

Como já mencionado, este grupo de função é o mais numeroso. Para nossas tarefas práticas descritas no artigo, será suficiente implementar quatro de tais funções.

   double         CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk
   ulong          DealOpen(long dir,double lot,int SL,int TP);     // execute deal with specified parameter
   ulong          GetDealByOrder(ulong order);                     // get deal ticket by order ticket
   double         CountProfitByDeal(ulong ticket);                 // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk
double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk
  {
   if(dist==0 || risk==0) return(lot);
   m_smbinf.Refresh();
   return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue())));
  }
//------------------------------------------------------------------ DealOpen
ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP)
  {
   double op,sl,tp,apr,StopLvl;
   // determine price parameters
   m_smbinf.RefreshRates(); m_smbinf.Refresh();
   StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level
   apr     = ReversPrice(dir); 
   op      = BasePrice(dir);                         // open price
   sl      = NormalSL(dir, op, apr, SL, StopLvl);    // stop loss
   tp      = NormalTP(dir, op, apr, TP, StopLvl);    // take profit

   // open position
   m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp);
   ulong order = m_trade.ResultOrder(); 
   if(order<=0) return(0);                           // order ticket
   return(GetDealByOrder(order));                    // return deal ticket
  }
//------------------------------------------------------------------ GetDealByOrder
ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket
  {
   PositionSelect(m_smb);
   HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER));
   uint total=HistoryDealsTotal();
   for(uint i=0; i<total; i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(order==HistoryDealGetInteger(deal,DEAL_ORDER))
         return(deal);                            // remember deal ticket
     }
   return(0);
  }
//------------------------------------------------------------------ CountProfit
double CExpertAdvisor::CountProfitByDeal(ulong ticket)  // position profit by deal ticket
  {
   CDealInfo deal; deal.Ticket(ticket);                 // deal ticket
   HistorySelect(deal.Time(),TimeCurrent());            // select all deals after this
   uint total  = HistoryDealsTotal();
   long pos_id = deal.PositionId();                     // get position id
   double prof = 0;
   for(uint i=0; i<total; i++)                          // find all deals with this id
     {
      ticket = HistoryDealGetTicket(i);
         if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue;
      prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit
     }
   return(prof);                                        // return profit
  }

4. Módulos de negócio

Finalmente, este grupo de funções vincula todo o processo de negociação, processando os sinais e eventos, usando as funções de macro e as funções de serviço. Lexemas lógicos de operações de negócio são poucos, eles dependem dos seus objetivos específicos. Entretanto, nós podemos diferir os conceitos comuns, que existem em quase todos os especialistas.

   virtual bool      Main();                            // main module controlling trade process
   virtual void      OpenPosition(long dir);            // module of opening position
   virtual void      CheckPosition(long dir);           // check position and open additional ones
   virtual void      ClosePosition(long dir);           // close position
   virtual void      BEPosition(long dir,int BE);       // moving Stop Loss to break-even
   virtual void      TrailingPosition(long dir,int TS); // trailing position of Stop Loss
   virtual void      OpenPending(long dir);             // module of opening pending orders
   virtual void      CheckPending(long dir);            // work with current orders and open additional ones
   virtual void      TrailingPending(long dir);         // move pending orders
   virtual void      DeletePending(long dir);           // delete pending orders

Nós consideraremos implementações específicas destas funções nos exemplos abaixo.

Adicionar novas funções não será difícil, uma vez que nós escolhamos a abordagem correta e compomos a estrutura especialista. Se você utilizar exatamente este esquema, projetos necessitarão esforços e tempo mínimos, o código será legível depois mesmo de um ano.

E claro, seus especialistas não são limitados a eles. Na classe CExpertAdvisor, nós declaramos apenas os métodos mais necessários. Você pode adicionar novos manipuladores na children classes, modificar as já existentes, expandir os seus próprios módulos, deste modo criando uma biblioteca única. Tendo tal biblioteca, o desenvolvimento de especialistas "carcereiros" leva de meia hora a dois dias.


4. Exemplos do Uso da Classe CExpertAdvisor

4.1. Exemplo de trabalho baseado nos sinais indicadores

Como primeiro exemplo, vamos começar com uma tarefa simples - considere o Consultor Especialista de Média Móvel (exemplo básico do MetaTrader 5) usando a classe CExpertAdvisor. Vamos complicar um pouco.

Algoritmo:

a) Condição para abertura de posição

  • Se o preço cruzar a MA de baixo pra cima, então abra a posição para Compra.
  • Se o preço cruzar a MA de cima pra baixo, então abra a posição para Venda.
  • Estabeleça SL (Stop Loss - Parar Perdas), TP (Take Profit - Obter Lucros).
  • O lote da posição é calculado pelo parâmetro de Risco - quantos vão perder pelo depósito quando o Stop Loss for ativado.

b) Condição para fechamento de posição

  • Se o preço cruzar a MA de baixo pra cima, então feche a posição para Venda.
  • Se o preço cruzar a MA de cima pra baixo, então feche a posição para Compra.

c) Limitação

  • Limitar o trabalho de um especialista diariamente por tempo pelo HourStart até o HourEnd.
  • Especialistas realizam operações de negociação apenas na nova barra.

d) Suporte de posição

  • Use um simples acompanhamento de parada a uma distância do TS.

Para nosso especialista, nós necessitaremos de sete funções da classe CExpertAdvisor:

  • Função de sinal - CheckSignal()
  • Filtros de ponto - CheckNewBar()
  • Filtro de tempo - CheckTime()
  • Funções de serviço de abertura de posição - DealOpen()
  • Três módulos operantes - OPenPosition(), ClosePosition(), TraillingPostion()

A função CheckSignal() e os módulos devem ser definidos na classe filha para resolver especificamente suas funções. Nós também precisamos adicionar o indicador de inicialização.

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"

input double Risk      = 0.1; // Risk
input int    SL        = 100; // Stop Loss distance
input int    TP        = 100; // Take Profit distance
input int    TS        =  30; // Trailing Stop distance
input int    pMA       =  12; // Moving Average period
input int    HourStart =   7; // Hour of trade start
input int    HourEnd   =  20; // Hour of trade end
//---
class CMyEA : public CExpertAdvisor
  {
protected:
   double            m_risk;          // size of risk
   int               m_sl;            // Stop Loss
   int               m_tp;            // Take Profit
   int               m_ts;            // Trailing Stop
   int               m_pMA;           // MA period
   int               m_hourStart;     // Hour of trade start
   int               m_hourEnd;       // Hour of trade end
   int               m_hma;           // MA indicator
public:
   void              CMyEA();
   void             ~CMyEA();
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual bool      Main();                              // main function
   virtual void      OpenPosition(long dir);              // open position on signal
   virtual void      ClosePosition(long dir);             // close position on signal
   virtual long      CheckSignal(bool bEntry);            // check signal
  };
//------------------------------------------------------------------ CMyEA
void CMyEA::CMyEA() { }
//----------------------------------------------------------------- ~CMyEA
void CMyEA::~CMyEA()
  {
   IndicatorRelease(m_hma); // delete MA indicator
  }
//------------------------------------------------------------------ Init
bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false);  // initialize parent class

   m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA;  // copy parameters
   m_hourStart=HourStart; m_hourEnd=HourEnd;

   m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator
   if(m_hma==INVALID_HANDLE) return(false);            // if there is an error, then exit
   m_bInit=true; return(true);                         // trade allowed
  }
//------------------------------------------------------------------ Main
bool CMyEA::Main()                            // main function
  {
   if(!CExpertAdvisor::Main()) return(false); // call function of parent class

   if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars
   
   if(!CheckNewBar()) return(true);           // check new bar

   // check each direction
   long dir;
   dir=ORDER_TYPE_BUY;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);
   dir=ORDER_TYPE_SELL;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);

   return(true);
  }
//------------------------------------------------------------------ OpenPos
void CMyEA::OpenPosition(long dir)
  {
   if(PositionSelect(m_smb)) return;     // if there is an order, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00"))) return;
   if(dir!=CheckSignal(true)) return;    // if there is no signal for current direction
   double lot=CountLotByRisk(m_sl,m_risk,0);
   if(lot<=0) return;                    // if lot is not defined then exit
   DealOpen(dir,lot,m_sl,m_tp);          // open position
  }
//------------------------------------------------------------------ ClosePos
void CMyEA::ClosePosition(long dir)
  {
   if(!PositionSelect(m_smb)) return;                 // if there is no position, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00")))
     { m_trade.PositionClose(m_smb); return; }        // if it's not time for trade, then close orders
   if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction
   if(dir!=CheckSignal(false)) return;                // if the close signal didn't match the current position
   m_trade.PositionClose(m_smb,1);                    // close position
  }
//------------------------------------------------------------------ CheckSignal
long CMyEA::CheckSignal(bool bEntry)
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)
     { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); }

   double ma[1];
   if(CopyBuffer(m_hma,0,0,1,ma)!=1)
     { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); }

   if(rt[0].open<ma[0] && rt[0].close>ma[0])
      return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell

   return(WRONG_VALUE);                                // if there is no signal
  }

CMyEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.Main();                  // process incoming tick
  }

Vamos analisar a estrutura da função Main(). Convencionalmente é dividida em duas partes.

Na primeira parte, a função principal é nomeada. Essa função processa os parâmetros possíveis que afetam globalmente o trabalho de um especialista. Isto inclui checar a permissão para negociar para um especialista e a validação de dados históricos.

Na segunda parte, os eventos de mercado são diretamente processados.

O filtro CheckNewBar() é testado - verificando uma nova barra. E módulos para duas direções de negócio são nomeados um após o outro.

Nos módulos tudo é organizado bastante abstratamente (o segundo princípio de planejamento). Não existe endereço direto para as propriedades do símbolo. E três módulos OpenPosition(), ClosePosition() e TraillingPosition() - Conte apenas com os parâmetros que venham do exterior. Isto lhe permite nomear estes módulos para verificação de pedidos tanto para Compra quanto para Venda.


4.2. Exemplo de utilização do CExpertAdvisor - Especialista sem indicadores, analisando o estado da posição e resultado

Para demonstrar, vamos tomar o sistema que negocia apenas na posição reversa com aumento de lote após prejuízo (este tipo de especialista é usualmente chamado de "Martingale").

a) Coloque o pedido inicial

  • Quando o especialista inicia, ele abre a primeira posição para compra com lote inicial.

b) Abra as situações subsequentes

  • Se a posição anterior foi fechada com lucro, então abra posição na mesma direção com lote inicial.
  • se a posição anterior foi fechada com prejuízo, abra posição na direção oposta com um lote maior (usando fator).

Para nosso especialista, nós iremos necessitar de três funções da classe CExpertAdvisor:

  • Abrir posição - DealOpen()
  • Obtenha o valor do lucro da posição fechada pelo bilhete do acordo - CountProfitByDeal()
  • Módulos operantes - OpenPosition(), CheckPosition()

Uma vez que o especialista não analisa nenhum indicador, mas apenas resultados dos acordos, nós usaremos os eventos OnTrade() para a produtividade ótima. Isto é, o especialista, que uma vez colocou o primeiro pedido inicial para Compra, colocará todos os pedidos somente depois do fechamento desta posição. Então nós colocaremos o pedido inicial no OnTick() e faremos todo o trabalho subsequente no OnTrade().

A função Init(), como usual, simplesmente inicializa os parâmetros da classe com parâmetros externos do especialista.

O módulo OpenPosition() abre a posição inicial e é bloqueado pelo sinalizador m_first.

O modulo CheckPosition() controla as futuras reversões de posição.

Estes módulos são nomeados a partir das respectivas funções do especialista: OnTick() e OnTrade().

//+------------------------------------------------------------------+
//|                                                       eMarti.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"
#include 

input double Lots    = 0.1; // Lot
input double LotKoef = 2;   // lot multiplier for loss
input int    Dist    = 60;  // distance to Stop Loss and Take Profit
//---
class CMartiEA : public CExpertAdvisor
  {
protected:
   double            m_lots;       // Lot
   double            m_lotkoef;    // lot multiplier for loss
   int               m_dist;       // distance to Stop Loss and Take Profit
   CDealInfo         m_deal;       // last deal
   bool              m_first;      // flag of opening the first position
public:
   void              CMartiEA() { }
   void             ~CMartiEA() { }
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual void      OpenPosition();
   virtual void      CheckPosition();
  };
//------------------------------------------------------------------ Init
bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class
   m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist;       // copy parameters
   m_deal.Ticket(0); m_first=true;
   m_bInit=true; return(true);                        // trade allowed
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::OpenPosition()
  {
   if(!CExpertAdvisor::Main()) return;                       // call parent function
   if(!m_first) return;                                      // if already opened initial position
   ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position
   if(deal>0) { m_deal.Ticket(deal); m_first=false; }        // if position exists
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::CheckPosition()
  {
   if(!CExpertAdvisor::Main()) return;           // call parent function
   if(m_first) return;                           // if not yet placed initial position 
   if(PositionSelect(m_smb)) return;             // if position exists

   // check profit of previous position
   double lot=m_lots;                            // initial lot
   long dir=m_deal.Type();                       // previous direction
   if(CountProfitByDeal(m_deal.Ticket())<0)      // if there was loss
     {
      lot=NormalLot(m_lotkoef*m_deal.Volume());  // increase lot
      dir=ReversType(m_deal.Type());             // reverse position
     }
   ulong deal=DealOpen(dir,lot,m_dist,m_dist);   // open position
   if(deal>0) m_deal.Ticket(deal);               // remember ticket
  }

CMartiEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.OpenPosition();          // process tick - open first order
  }
//------------------------------------------------------------------ OnTrade
void OnTrade()
  {
   ea.CheckPosition();         // process trade event
  }


5. Trabalhando com eventos

Neste artigo você encontrou exemplos de processamento de dois eventos - NewTick e Trade que foram representados pelas funções OnTick() e OnTrade(), respectivamente. Na maioria dos casos, estes dois eventos são usados constantemente.

Para especialistas, existem quatro funções para processamento de eventos:

  • OnChartEvent processa um grande grupo de eventos; quando trabalhando com objetos gráficos; teclado, mouse e eventos personalizados. Por exemplo, a função é usada para criar especialistas interativos ou especialistas, construídos no princípio de administração gráfica de pedidos. Ou somente para criar controles ativos de parâmetros do programa MQL (usando botões e campos de edição). Em geral, essa função é usada para processar eventos externos de um especialista.
  • OnTimer é chamado quando o evento de cronometro do sistema é processado. É usado em casos quando o programa MQL requer analisar seu ambiente regularmente, para calcular valores de indicadores, quando é necessário para continuamente referir a fonte externas de sinais, etc. A grosso modo, a função OnTimer() - é uma alternativa, ou mesmo a melhor substituição para:
  • while(true) { /* perform analysis */; Sleep(1000); }.
    Ou seja, o especialista não precisa trabalhar num ciclo sem fim no seu início, mas o suficiente para mover a nomeação das suas funções do OnTick() para OnTimer().
  • OnBookEvent processa um evento que é gerado quando o Departamento de Mercado altera o seu estado. Este evento pode ser atribuído ao externo e realizar seu processamento de acordo com a tarefa.
  • OnTester é chamado após testar o especialista em uma certa gama de data, antes que a função OnDeinit() para possíveis blindagens de gerações de testes, quando usando a otimização genética pelo parâmetro máximo personalizado.

Não esqueça que qualquer evento e suas combinações são sempre aconselháveis para uso na solução de suas tarefas específicas.


Epílogo

Como você pode ver, escrever um especialista, tendo o esquema correto, não leva muito tempo. Devido a novas possibilidades de processamento de eventos no MQL5, nós temos estruturas mais flexíveis para a administração de processos de negociação. Mas todas essas coisas se tornam uma ferramenta realmente poderosa apenas se você preparou propriamente o seu algoritmo de negociação.

O artigo descreve três princípios básicos de suas criações - Totalidade de eventos, abstração, modulação. Você fará sua negociação mais fácil, se basear seus especialistas nestes "três pilares".

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/132

Arquivos anexados |
expertadvisor.mqh (17.92 KB)
emarti.mq5 (3.57 KB)
emyea.mq5 (5.9 KB)
Como criar o seu próprio limite móvel Como criar o seu próprio limite móvel
A regra básica do negociante - aumente o lucro, corte as despesas! Este artigo considera uma das técnicas básicas, permitindo seguir esta regra - mover o nível de parada de proteção (nível Stop Loss) após aumentar o lucro da posição, ou seja - nível do Limite móvel. Você encontrará o procedimento passo-a-passo para criar uma classe para o limite móvel nos indicadores SAR e NRTR. Todos poderão inserir este limite móvel em seus experts ou usá-los independentemente para controlar posições em suas contas.
20 sinais de negociação no MQL5 20 sinais de negociação no MQL5
Este artigo o ensinará a como receber os sinais de negócio que são necessários para um sistema de negócio funcionar. O exemplo de formação de 20 sinais de negócio é forncedio aqui como funções de personalização separadas que podem ser usadas ao desenvolver Expert Advisors. Para sua conveniência, todas as funções utilizadas no artigo são combinadas em um único arquivo mqh que pode ser facilmente conectado a um futuro Expert Advisor.
Criando indicadores multicoloridos no MQL5 Criando indicadores multicoloridos no MQL5
Neste artigo, consideraremos como criar indicadores multicoloridos ou converter os existentes para multicor. O MQL5 permite representar as informações de forma conveniente. Agora, não é necessário ver uma dúzia de gráficos com indicadores e realizar análises dos níveis RSI ou estocásticos, é melhor apenas pintar as velas com cores diferentes dependendo dos valores dos indicadores.
Como escrever um indicador na base de um outro indicador Como escrever um indicador na base de um outro indicador
No MQL5 você pode escrever um indicador tanto do zero como baseado em outro indicador já existente, embutido no terminal do cliente ou um personalizado. E aqui você também tem dois modos - melhorar um indicador adicionando novos cálculos e estilos gráficos a ele, ou utilizar um indicador no terminal do cliente embutido ou um personalizado através das funções iCustom() ou IndicatorCreate().