English Русский 中文 Español Deutsch 日本語
MetaEditor : Modelos como um local para se apoiar

MetaEditor : Modelos como um local para se apoiar

MetaTrader 4Exemplos | 4 fevereiro 2016, 14:26
894 0
MetaQuotes
MetaQuotes


Dê-me um ponto de apoio e eu moverei o mundo.
Archimedes

Introdução

Como uma linguagem de programação, MQL4 representa uma grande maioria de usos para um código uma vez escrito e analisado. Esses usos incluem:

  • os arquivos .mqh inclusos. Você pode armazenar nestes arquivos todas as funções necessárias e constantes que podem ser adicionadas em seu código usando a diretiva #include;
  • bibliotecas de função que podem ser compiladas como programas MQL4 normais e podem ser adicionados em seu código no modo de tempo real usando a diretiva #import; e
  • indicadores personalizados para realizar cálculos econômicos em faixas de séries de tempo. Eles são chamados no modo em tempo real usando a função iCustom().

Entretanto, nem todos os desenvolvedores os conhecem, e, portanto, nem todos eles usam esse poderoso mecanismo para escrita fácil e confiável de um Expert Advisor como modelos já prontos para uso, criados utilizando o assistente do Expert Advisor Wizard. Esse artigo descreve algumas vantagens dessa ferramenta.


O que é um modelo?

O que é um modelo com relação ao MetaEditor? Modelos são estes arquivos .mqt que são armazenados na pasta com o mesmo nome no terminal Root_directory_MetaEditor_4/experts/templates/.

Na foto acima, podemos ver 10 destes arquivos. Os básicos são:
  • Expert.mqt - um modelo para criação de Expert Advisors;
  • Script.mqt - um modelo para criação de scripts;
  • Include.mqt - um modelo para criação de scripts;
  • indicator.mqt - um modelo para criação de indicadores;
  • Library.mqt - um modelo para criação de uma biblioteca.

Os outros modelos (Alligator.mqt, etc.) tem como objetivo a criação de indicadores de acordo com o nome do indicador dado no nome do modelo. Por exemplo, vamos abrir o modelo Library.mqt com o MetaEditor. Para isso, precisamos especificar "Todos arquivos (*.*)" no campo "Tipo de arquivo" :

Veremos que o conteúdo do arquivo não é muito grande.

<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

As três primeiras linhas informam sobre a que tipo de arquivos esse modelo pertence:

<expert>
type=LIBRARY_ADVISOR
</expert>

A linha type=LIBRARY_ADVISOR está obviamente informando ao MetaEditor que esse arquivo é um modelo de biblioteca. O MetaEditor usará o modelo necessário de acordo com sua escolha: EA, indicador personalizado, etc.



Então siga o macro de substituição #header# que, de fato, será substituído com o nome que você escolher ao seguir as instruções do Assistente do Expert Advisor.



Por exemplo, se você nomear seu EA como Meu_Melhor_Expert_Advisor, então as linhas a seguir serão formadas ao invés do macro #header#:

//+------------------------------------------------------------------+
//|                                       My_Best_Expert_Advisor.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+

Nesse bloco de comentários podemos ver a informação sobre o nome do EA, seu autor e o link para seu website. Todos esses dados foram inseridos nos campos correspondentes do Assistente do Expert Advisor. As próximas linhas:

#property copyright "#copyright#"
#property link      "#link#"

contém os macros #copyright# e #link# que são obviamente correspondentes aos campos do Assistente do Expert Advisor .


Como usar isso?

Estamos mais interessados na possibilidade de inserir em um modelo nosso próprio código para ser gerado automaticamente em cada criação de um EA. Por exemplo, se você tem alguma experiência na criação de scripts, você sabe que scripts tem a função de serem executados na tabela somente uma vez. É necessário algumas vezes especificar alguns parâmetros externos para o algoritmo do script, para que se torne necessário a alteração desses parâmetros no momento de execução do script. O Assistente do Expert Advisor não fornece essa possibilidade por padrão. Entretanto, isso pode ser alcançado usando uma diretiva no compilador:

#property show_inputs

É suficiente adicionar essa linha em qualquer código de script, e uma janela com parâmetros aparecerá. Vamos exemplificar como fazer isso para que essa linha seja automaticamente adicionada ao Assistente do Expert Advisor em cada novo script criado. Vamos abrir um modelo de EA para isso:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

e adicionar somente uma linha antes do macro que substitua parâmetros externos (#extern_variables#). Abaixo está o código que teremos:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Então salvamos as alterações e começamos a escrever um script usando o Assistente do Expert Advisor. Vamos nomear esse script como TestScript e, depois de pressionar "Fechar"


obter o resultado:
//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

O novo script é diferente destes feitos pelo modelo antigo por somente uma linha, mas nos ajuda muito. Agora não precisamos inserir a linha:

#property show_inputs

manualmente em cada novo script, ele será adicionado automaticamente. De qualquer forma, será muito mais fácil comentá-lo (se não precisarmos dele) do que o inserir todas as vezes. Vamos manualmente adicionar somente uma linha que contenha um parâmetro chamado emptyParam.

//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Vamos compilar e lançar na tabela. O script não executa imediatamente agora, mas permite que nós possamos modificar parâmetros externos:



Esse é o exemplo mais simples de como usar os modelos. A ideia é que somente adicionemos no modelo as linhas de código que precisamos mais em cada criação de um novo programa MQL4. Então essas linhas de código serão automaticamente adicionadas a cada vez que você usar o Assistente do Expert Advisor.

Importante: Todos os modelos são reescritos quando você reinstala o terminal cliente do MetaTrader 4. Então você mesmo deve fazer cópias dos seus templates.

Onde isso nos leva?

A possibilidade de usar modelos já prontos realmente nos permite uma abordagem para escrever um EA com a maior precisão possível. Como os iniciantes escrevem seus EAs? Por exemplo, vamos considerar uma estratégia com base na interseção de duas médias móveis. Abaixo está uma exigência técnica simples para criar um EA com essa estratégia:

  1. Obtenha os valores para as médias móveis do período curto e período longo.
  2. Verifique se sofrem alguma interseção.
  3. Se a menor intercede a maior da parte inferior à superior então compre, StopLoss=N pontos.
  4. Se a menor atravessa a maior da parte superior à inferior então venda, StopLoss=N pontos.
  5. Se uma posição longa aberta estiver disponível, feche-a ao sinal de venda.
  6. Se uma posição curta aberta estiver disponível, feche-a ao sinal de compra.

Bem, o EA está otimizado e preparado. Agora a ideia é adicionar um novo parâmetro, por exemplo, TakeProfit = S pontos. Ou para substituir o bloqueio de alertas de negócios: use os valores estocásticos ao invés das médias móveis da interseção. E assim por diante. Para cada nova alternativa para o EA, teremos que fazer algumas mudanças no código e, de repente parece que não será muito fácil. Em dado momento veremos que temos somente que escrever um novo EA com todas essas novas funções. Claro, escritores experientes de Expert Advisors já trilharam esse caminho e usam suas práticas e abordagens favoritas. As técnicas listadas no início desse artigo os ajuda a, especialmente (vamos repetir):

  • incluir arquivos;
  • bibliotecas;
  • indicadores personalizados.

Entretanto, eles não produzem o efeito máximo sem utilizar um modelo. Bem, o que é um EA modelo e o que ele deve ser para se tornar eficaz? Na minha interpretação, um EA modelo é um protótipo universal de um EA real, mas sem nenhuma característica específica. De fato, é uma certa SuperClass que tem algumas funções puramente virtuais (nos termos da programação orientada a objeto), ou interface (nos termos de Java). O modelo descreverá o que o EA faz, mas não descreve como ele faz isso. Por isso devemos antes analisar a funcionalidade desejada antes de criar um modelo. Devemos criar primeiramente a estrutura de nosso EA.


Estrutura do Expert Advisor

Bem, o que um EA deve fazer? Na primeira aproximação, um EA negocia, significa que ele realiza operações de negociação - faz pedidos para abertura e fechamento de posições de compra. Além do mais, um EA modifica os níveis de StopLoss e TakeProfit das posições existentes (se necessário), coloca ou remove ordens pendentes, modifica os níveis de StopLoss e TakeProfit para pedidos pendentes. Uma estrutura simples começa a surgir:

  1. Bloco para receber sinais de negócios.
  2. Abrir um pedido de mercado ou colocando um pedido pendente.
  3. Escolher pedidos abertos e deletar outros pendentes.

Assim, nós dissecamos a ideia geral de uma negociação automática em três subproblemas. Agora sabemos que devemos fazer uma função (bloco 1) que fará decisões no sinal atual: comprar, vender ou permanecer fora do mercado (sem sinal de negociação). Então, o ( bloco 2), com base do sinal de negociação, podemos abrir uma posição ou colocar um pedido pendente. Porque "pode", e não "deve"? Porque o bloqueio das posições de abertura considera os sinais de negociação, mas não é obrigado a segui-los e fazer negociações. Por exemplo, temos algumas posições já abertas. Então a abertura de novas pode expor a conta de negócios a riscos indevidos. Bem, a última mas não a menos importante funcionalidade de um EA (bloco 3) - fechando posições abertas e removendo pedidos pendentes - é também independente. Podemos fechar posições por um sinal de negociação ou por outras considerações (tempo de espera de posição, fim de uma sessão de negociação, etc.). Desse modo, a divisão da lógica do EA em três partes não parece sem base.


Refinamentos posteriores

Quando paramos para pensar, podemos chegar a uma conclusão que a estrutura acima não está completa. Vamos fazer mais alguns refinamentos em cada bloco.

Primeiro, podemos calcular sinais de negócios para cada tick de entrada (um tick é o fato da alteração de preço) ou fazemos esses cálculos somente ao abrir cada nova barra. No momento, isso pode não parecer importante, mas pode se tornar difícil mudar o código no futuro se não cuidarmos disso anteriormente.

E segundo, podemos também mudar o período de tempo dessas barras: embora tenhamos preparado nosso EA para negociar nas tabelas de uma hora neste momento, poderemos mudar isso no futuro de modo que usará tabelas de 4 horas. Devemos considerar esse ponto também.

Bem, em terceiro, devemos unificar os sinais de negócios. Precisamos de uma sistematização estrita. Já que temos somente três tipos de sinais de negociação, então deixamos nosso bloco exibi-los usando constantes descritas estritamente, e o melhor de tudo, nos termos do MQL4:

  • OP_BUY - sinal de compra;
  • OP_SELL - sinal de venda;
  • OP_BALANCE - sem sinal, fique fora do mercado.

Assim, o primeiro bloco aparecerá da seguinte forma:

/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/

Precisamos então um bloco para calcular níveis importantes para abrir novos pedidos ou modificar os já existentes. Isso é também uma funcionalidade que não depende de outras partes desse código.

/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/

Não mostramos os cálculos de tamanho da posição e lotes aqui, mas são também parâmetros. Entretanto, é possível que você possa querer colocar a função de cálculo de tamanho da posição a ser aberta em um bloco separado que possamos convenientemente chamar de bloco de Gerenciamento de Dinheiro.

O próximo bloco é para modificar os níveis dos pedidos. Ele deve também ser independente, porque podemos modificar ambos níveis de StopLoss e TakeProfit. Além disso, você pode modificá-los a cada tick ou somente na abertura de uma nova barra. Se fornecermos essa flexibilidade no EA, será muito mais fácil para nós otimizarmos ele no futuro.

/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/

Note que podemos especificar o período de tempo de modificação na abertura de uma nova barra do mesmo modo que no bloco 1 (recebendo sinais de negócios).

O próximo bloco é o bloco para fechar os pedidos. Podemos fechar os pedidos por duas razões: tempo e sinal. Nesse caso, falamos tanto do sinal de negociação recebido direcionado de maneira oposta à posição aberta e a alteração das condições que levam à necessidade de manter a posição.

/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/

Eu separei o bloco de exclusão de pedidos pendentes do bloco de fechamento de posição porque a prioridade de fechar uma posição aberta é algumas vezes muito maior (por exemplo, o preço começa a se mover fortemente contra a posição de abertura e precisamos fechá-lo o quanto antes, colocando todas as outras coisas para o futuro) do que a de excluir um pedido pendente (como regra, pedidos pendentes estão em uma distância um tanto longa do preço atual).

/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/

Tudo está absolutamente claro no bloco para excluir pedidos pendentes, então não são necessários comentários adicionais.

O último bloco é o de abertura de posições e a colocação de pedidos pendentes.

/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

Estranho, mas parece que esse é o último. Se pensarmos nisso, essa estrutura de EA é bem lógica: primeiro devemos tomar todas as preparações necessárias (fechar tudo que precisa ser fechado e excluir todos os pedidos pendentes redundantes) sem se ater a nenhum problema para o futuro, e somente então fazer novos pedidos e ativamente intervir no mercado.

/**
      7. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

Entretanto, esse bloco também contém algumas variações. Você pode abrir uma posição no preço de mercado (comprar no preço de Venda ou vender no preço de Compra), você pode colocar um pedido pendente com a data e hora de expiração (o pedido pendente expirado será removido pelo servidor de negócios) ou sem qualquer data de expiração até ser cancelado (até que seja deletado por nós).

Todos acima resultam na seguinte estrutura do modelo do EA:

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/
 
 
/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/
 
/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/
 
 
/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/
 
//----
   return(0);
  }
//+------------------------------------------------------------------+

Podemos já salvar nosso modelo como está. Esse modelo será útil mesmo que não contenha nenhuma função adicional. É uma descrição textual de todos os blocos necessários para escrever um EA. Cada escritor de Expert Advisors pode adicionar a sua visão da estrutura ou mesmo modificá-la totalmente. Entretanto, em qualquer caso, ao criar um novo EA usando o Assistente do EA, você verá uma descrição textual da estrutura que não permitirá que você esqueça o que e em que sequência você deve fazer seu código. Mas podemos ir muito mais além.


Chegando à concepção

Agora podemos começar a preencher a estrutura do modelo do EA com funções generalizadas. Essas funções não descreverão a concepção final das operações necessárias, mas descreverão a interação padrão entre a estrutura do modelo e as funções finais do usuário. Portanto, não descreveremos uma função específica para receber um sinal de negócios, mas iremos descrever onde, em uma função generalizada, o usuário deve inserir a chamada da função específica.


Função da aparência da nova barra para a estrutura de tempo predefinida

Já que decidimos que os sinais de negociações serão exibidos no bloco 1, tanto em cada novo tick ou em uma nova barra em um determinado período de tempo predefinido pelo usuário, nós precisamos, acima de tudo, de uma função que nos informe sobre esse evento: "uma nova barra apareceu na estrutura de tempo". Essa função é simples e parece da seguinte forma:

//+----------------------------------------------------------------------+
//|  It returns the sign of a new bar appearance for the given timeframe |
//+----------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // the array contains open time of the current (zero) bar
   // for 7 (seven) timeframes
   static datetime _sTime[7];  
   int i=6;
 
   switch (timeFrame) 
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);   
   }

Definindo a estrutura de tempo e verificando sua exatidão

A função isNewBar() toma o valor do período de tempo como um parâmetro e como a quantidade de minutos. Vamos introduzir uma nova variável externa, TradeSignalBarPeriod, que indicará o período de tempo necessário:

extern int  TradeSignalBarPeriod       = 0;

Por padrão, ela é igual a zero. Isso significa que o EA sempre rastreará as novas barras ao negociar em "seu" período de tempo, isto é, no período de tempo da tabela onde este EA estiver anexado ao negociar no modo em tempo real, ou igual ao período de tempo no qual for testado. O usuário pode introduzir qualquer constante predefinida para escolher um período de tempo. Entretanto, podem haver erros nisso. Então vamos adicionar uma função para verificar o valor da variável TradeSignalBarPeriod e, se necessário, corrigir erros, se o valor estiver errado.

//+------------------------------------------------------------------+
//|  if the timeframe is specified incorrect, it returns zero        |
//+------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // allowed timeframe, don't do anything
      case PERIOD_H4:  break;  // allowed timeframe, don't do anything
      case PERIOD_H1:  break;  // allowed timeframe, don't do anything
      case PERIOD_M30: break;  // allowed timeframe, don't do anything
      case PERIOD_M15: break;  // allowed timeframe, don't do anything
      case PERIOD_M5:  break;  // allowed timeframe, don't do anything
      case PERIOD_M1:  break;  // allowed timeframe, don't do anything
      default: res=Period();   // incorrect timeframe, set a default one
      }
//----
   return(res);      
   }

Se não ocorrerem erros, a função não faz nada. A usaremos somente uma vez, durante a inicialização do EA. Então colocaremos sua chamada na função init():

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // remind the EA settings
//----
   return(0);
  }

Como você pode ver, além dela, há uma nova função definida por usuário StartMessage() no bloco init(). Ela será descrita abaixo, explicarei somente para o que ela serve aqui. Expert Advisors obtém grandes quantidades de parâmetros de configuração que podem ser esquecidos facilmente após um certo período de tempo. A função StartMessage() foi desenvolvida para fornecer uma descrição mínima dos parâmetros do EA em sua execução para trabalhar em uma conta de negócios nos testes de retaguarda. Você pode ler esses parâmetros nos registros do terminal ou do Testador. Suponho que tal função seja útil em qualquer EA, por isso estou incluindo ela no novo modelo do EA também.


Recebendo um alerta de negócio

Agora podemos nos movermos para o bloco que produz os alertas de negócios. Entretanto, antes de prosseguirmos, vamos considerar mais algumas situações. Uma vez criado um EA, algumas vezes podemos querer verificar como ele funciona em determinados dias da semana. Por exemplo, queremos negociar somente nas quartas-feiras, então o EA deve 'ficar em silêncio'. Podemos fornecer essa possibilidade para escolher os dias de negócios em nossos EA antecipadamente. Vamos criar uma variável integral externa TradeDay. Se seu valor for igual a zero (Domingo=0, Segunda=1, etc.), então negociaremos normalmente. Entretanto, se for outro diferente de zero então nós somente negociamos no dia específico da semana.

Ademais, podemos querer fazer uma operação inversa. Então devemos especificar o dia da semana no qual devemos proibir nosso EA de negociar. Vamos criar mais uma variável externa lógica ReversDay. Se ela for igual a 'falso', então a lógica é normal (pontos de TradeDay no dia de negócios). Se for 'verdadeiro' então as condições são invertidas e TradeDay irá apontar o dia da semana no qual não faremos negócios. Por exemplo, TradeDay=5 e ReversDay=true significa que não faremos negócios nas sextas-feiras (Friday=5).

Bem, o último truque que devemos fornecer é o inverso do sistema de negócios. Por exemplo, podemos querer inverter totalmente nossa estratégia no futuro (mudar comprar por vender e vice-versa) e ver o que acontece. Se fornecermos essa possibilidade do início, então seremos capazes de utilizá-la qualquer dia. Para isso é suficiente somente introduzir mais uma variável externa lógica ReversTradeSignal. Se for igual a 'falso' (padrão) então a estratégia inicial funcionará. Entretanto, se o valor for definido como 'verdadeiro', então o sistema será invertido. Deste modo, as variáveis externas adicionadas são como a seguir:

// constant "off market"
#define OP_BALANCE 6
 
// trade day setting
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// trading system reverse
extern bool ReversTradeSignal          = false;
 
//  trading frequency settings
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

Adicionei a constante OP_BALANCE que significa que não há sinal de negócio disponível. Além do mais, há mais uma variável lógica, TradeSignalEveryTick. Se ela for verdadeira, significa receber os sinais de negociação a cada tick. Como você pode ver, não escrevemos nenhuma linha de nosso sistema de negócios, mas já temos algumas variáveis introduzidas, os propósitos das mesmas podem ser esquecidos após uma certa quantidade de tempo. É por isso que também escrevemos uma função de informação, StartMessage():

//+------------------------------------------------------------------+
//| It shows a message about EA settings                             |
//+------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("Expert Advisor ",WindowExpertName()," has the following settings:");
 
   if (TradeSignalEveryTick)
      array[1]="1) trade signals are considered at every tick; ";
   else       
      array[1]=StringConcatenate("1)trade signals are considered at each bar with the period of ",
                        TradeSignalBarPeriod," minutes;");
 
   if (TradeDay==0)   // trade day is not specified
      array[2]="2)trading is allowed on any day of week; ";
   else 
      {
      if (ReversDay) //  no trading allowed on the specified day
         array[2]=StringConcatenate("2)trading is allowed on all days but day number ",TradeDay);
      else           //  trading is allowed only on the specified day of week
         array[2]=StringConcatenate("2)trading is allowed only on day number ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

Essa função exibe o nome do EA e o mínimo de informação sobre suas configurações. Você pode adicionar outras linhas na linha 'array[]' ao escrever um EA específicos.

Agora estamos prontos para criar uma função que irá calcular um sinal de negócios. As seguintes configurações são passadas para essa função:

  • calcule um sinal de negócio em cada tick ou na abertura de uma nova barra do período de tempo específico;
  • se vai reverter um sinal de negócio ou não;
  • se vai considerar o dia da semana ou não;
  • se vai transformar os dias de negócios em dias sem negócios.
//+------------------------------------------------------------------+
//| receive a trade signal                                           |
//+------------------------------------------------------------------+
// tradeDay - day of week, on which we trade; if it is equal to zero, then we trade all days
//
// useReversTradeDay - if it is equal to 'true', then trading days become non-trading days
//
// everyTick  - if it is equal to zero, the function will calculate a signal for every tick
//
// period - if everyTick==false, then it is calculated as soon as a new bar with this period appears
int getTradeSignal(int tradeDay,          // it is usually equal to 0
                   bool useReversTradeDay,// it is usually equal to 'false'
                   bool everyTick,        // signal is calculated at every tick
                    int period            // working period for indicators and signals
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // we will consider days of week
      {
      // day, on which we don't trade
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // we trade on all days, except the day equal to tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // if we don't take trade signals at every tick
      { // nor we have any new bar on the timeframe of 'period' minutes,
      if (!isNewBar(period)) return(signal); // then exit with an empty signal
      }
 
// Fill function yourFunction() with your code/algorithm
   signal=yourFunction(period);
 
 
   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);   
   }

Como você viu acima, escrevemos uma ligação multilinha acerca de uma linha e código.

   signal=yourFunction();

Quando escrevemos nossa estratégia, escrevemos nossas próprias funções (ao invés de yourFunction()) que devem retornar um dos seguintes três valores:

  • OP_BUY - comprando
  • OP_SELL - vendendo
  • OP_BALANCE - sem sinal

Bloco identificando "FRIEND ou FOE " (amigo ou inimigo)

Mais um ponto incerto ao escrever um EA é detectar os tickets para os pedidos que o EA irá trabalhar. Esse problema é geralmente resolvido por meio de ciclos for(), onde um ticket é selecionado usando a função OrderSelect(). Entretanto, você deverá ter em mente que nós podemos precisar detectar pedidos "amigáveis" mais de uma vez. Em alguns casos, devemos modificar StopLoss. Em outros casos, precisamos fechar pedidos de mercado. Em outros casos, podemos também precisar deletar alguns pedidos pendentes.

Pelas razões de processamento de pedidos padrões "amigáveis", nós decidimos preencher a matriz de pedidos "amigáveis" com todos os parâmetros necessários todas as vezes no início da função start(). Para isso, introduzimos duas matrizes em um nível global:

double Tickets[][9];// array to store information about "friendly" orders:
// Tickets[][0] - ticket number
// Tickets[][1] - order type
// Tickets[][2] - lots
// Tickets[][3] - open price
// Tickets[][4] - Stop Loss
// Tickets[][5] - TakeProfit
// Tickets[][6] - MagicNumber
// Tickets[][7] - expiration time
// Tickets[][8] - open time
// 
 
string CommentsTicket[1000][2];         //array to store symbols and comments on orders. 1000 lines would be enough
// CommentsTicket[][0] - symbol name
// CommentsTicket[][1] - comment on the order

A função que preenche essas matrizes é muito simples:

//+------------------------------------------------------------------+
//| It prepares the array of "friendly" orders                       |
//+------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // filling counter
   
   // let's make the array size large not to allocate memory for it every time
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// if the special function has not detected the order as "friendly" one,
            // make usual checks
 
            // check for Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  other checks...
            // ....
         
            // last check, that for MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // we haven't been stopped anywhere, so this is a "friendly" order
         //  fill out the array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // let's increase the counter of filled "friendly" orders
         count++;
         }
      }
   
   // and now let's truncate the array sizes to the minimum essential ones 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;   
   }

Como um exemplo, dei uma descrição mínima de critérios que nos permitem distinguir pedidos "amigáveis de "inimigos" em uma função generalizada usando OrderMagicNumber() e OrderSymbol(). Ao procurar em todos os pedidos no ciclo, pulamos os pedidos "inimigos" e preenchemos as matrizes com os dados de "amigos". Para fazer a função mais flexível, podemos chamar mais uma função definida pelo usuário nela, onde você pode descrever algum outro critério para detectar se o pedido é "amigo ou inimigo". Neste caso, adicionei a verificação de variáveis globais: Se há uma variável global com nosso nome do EA no terminal e seu valor é igual ao ticket de um determinado pedido, o pedido também será considerado como um "amigo". Isso será útil, se, por exemplo, a posição for aberta manualmente, e agora queremos que ela seja processada por nosso EA.

//| It returns 'true', if a Global variable with this name is available|
//+--------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // we don't use global variables in test mode!
   if (IsTesting()) return(true);// immediately return the positive result
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// there is such a global variable
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // found a GV with the value equal to 'ticket'
               res=true;  // so it is a "friendly" order
               break;
               }
         }
      }
//----
   return(res);   
   }

Já fizemos uma grande parte de nossas preparações. Agora o início da função start() se parece com isso:

 
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always update the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain the arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Each bar of the preset period    (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/


Cálculo dos níveis de parar perdas/obter lucros para pedidos existentes

Agora podemos ir para a próxima página. Obtemos uma matriz bidimensional de pedidos "amigos", Tickets[][9]. Agora vamos calcular os novos níveis de SL e TP sabendo das características atuais de cada ordem. Os novos valores calculados devem ser armazenados em algum lugar, então vamos criar mais uma matriz global para esse propósito:

double newSL_and_TP[][5];// array to store the new values of SL and TP
// newSL_and_TP[][0] - ticket number to be controlled
// newSL_and_TP[][1] - new values of SL
// newSL_and_TP[][2] - new values of TP
// newSL_and_TP[][3] - new Open price (for pending orders)
// newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Explicações podem ser necessárias somente para o parâmetro:

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Precisaremos deste parâmetro para modificar o mercado e pedidos pendentes em funções separadas. Bem, vamos criar uma função que tome a matriz Ticketsp[][9] e o sinal de inversão do sistema e preencha a nova matriz newSL_and_TP[][5] com valores (a escala na segunda dimensão é dada em parênteses). Essa chamada de função aparecerá como a seguir:

 CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

O primeiro parâmetro, ReversTradeSignal, pode ter os valores de 'verdadeiro' (isto é, o sistema está invertido) ou 'falso'. O segundo parâmetro é a matriz de pedidos "amigáveis" (se não há pedidos, seu tamanho é igual a zero). O terceiro parâmetro é uma matriz para ser preenchida com esta função. A função por si é dada abaixo:

//+------------------------------------------------------------------+
//|  It creates an array with the new values of SL and TP            |
//+------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // system reverse 
                      double arrayTickets[][9], // array of "friendly" orders
                      double &amp; arraySL_TP[][5]  // new values of SL, TP and openPrice
                      )
   {
   // first of all, zeroize the obtained array !!
   ArrayResize(arraySL_TP,0);
   // if the order array is empty, then exit   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;    
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      if (ReversTrade) //  reverse all levels considering the spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, obtain a new open price
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }         
//----
   return;
   }

Primeiramente, vamos definir o tamanho zero da matriz que conterá os novos valores de SL e TP. Então devemos conhecer o tamanho da matriz que contém os pedidos "amigáveis".

   int   size=ArrayRange(arrayTickets,0);
   // if the order array is empty, then exit   
   if (size==0) return;

Se não há pedidos, não há nada a fazer, então saímos com uma saída representada por uma matriz arraySL_TP[][] de tamanho zero. As matrizes de tamanho zero não implicam em instruções para fazer nada com essas matrizes. Então organizamos um ciclo que vê os valores de todos os pedidos passados a serem processados:

   for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, then receive a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

Calculamos nele os valores atuais de oldSL, oldTP e oldOpenPrice (porque podemos mudar os níveis de abertura de pedidos pendentes) e passar estes valores como parâmetros em funções para calcular os novos valores de SL, TP e OpenPrice.

 newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // if it is a pending order, obtain a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

então tudo que temos que fazer é aumentar por um o tamanho da matriz de novos valores SL, TP e OpenPrice, se pelo menos um destes níveis não for igual a zero (isto é, esse pedido deve ser modificado).

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

Agora consideramos a possibilidade de inverter o sistema de negociação. O que significa uma inversão? Significa que as compras são convertidas em vendas, e vice-versa. O preço de abertura muda pelo valor do spread (dispersão), já que compramos no preço de Venda e vendemos no preço de Compra. Além do mais, os níveis de SL e TP se transpõem, novamente, considerando o spread. Podemos programar ele de forma a considerar tudo:

if (ReversTrade) //  reverse all levels considering spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

Não forneço aqui o código completo de mudanças feitas para a função CalculateSL_and_TP() porque essa função é muito grande. Você pode vê-la no arquivo anexado Template_EA.mqt. Então podemos dizer que conseguimos resolver a tarefa de calcular novos valores para Stop Loss, Take Profit e OpenPrice. Agora temos que criar funções para serem chamadas deste bloco de cálculos. Essas são funções vazias a serem preenchidas ao criar um EA específico que nomearemos de "funções de fórmula".


Funções de fórmula

Abaixo estão as funções:

//+------------------------------------------------------------------+
//|  It calculates the level of Stop Loss                            |
//+------------------------------------------------------------------+
double getNewSL(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Stop Loss
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the level of  Take Profit                         |
//+------------------------------------------------------------------+
double getNewTP(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Take Profit
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the new open price for a pending order            |
//+------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // type of the order, for which we calculate it
                              double lots,      // volume, it can be useful
                              double openPrice, // open price
                              double stopLoss,  // current level of Stop Loss
                              double takeProfit // current level of Take Profit
                              )
   {
   double res=-1;
//----
//  here is the code of calculations for open price 
 
//----
   return(res);   
   }

Essas três funções são muito similares entre si, cada uma tem o mesmo conjunto de entradas e retorna um valor do tipo duplo na variável res. Essa variável é imediatamente inicializada com um valor negativo e, se não inserirmos nosso próprio código para cálculos, esse valore negativo será o retornado. Isso implica na ausência do nível calculado, já que o preço é sempre positivo.

Além disso, podemos também escrever aqui uma fórmula para yourFunction() ser chamada do bloco de calcular sinais de negócios, da função getTradeSignal():

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
//  (there must be a code producing trade signals considering timeframe workPeriod here)
//----
   return (res);   
   }

Essa função possui workPeriod - um período que precisamos usar ao chamar indicadores padrões ou personalizados. A variável de retorno res é inicializada com o valor de OP_BALANCE. Se não inserirmos nosso próprio código que mudará o valor dessa variável, é esse valor que a função retornará. Isso será recebido como um sinal sem negociação.

Não existem mais funções personalizadas de fórmulas nesse modelo. Tendo procurado as tecnologias do seu uso, você será capaz de inserir todas as fórmulas necessárias nos locais adequados do código.


Cálculo de parâmetros para um novo pedido

Agora temos que obter os valores de SL, TP, volume e outros parâmetros necessários para colocar um novo mercado ou pedido pendente. Para isso, vamos criar variáveis, os nomes delas falarão por si.

  double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

Essas variáveis terão seus valores obtidos em duas funções: uma função calcula os valores para um pedido de mercado, outra função faz o mesmo para um pedido pendente. Essa separação nos permitirá escrever nosso código em uma maneira mais fácil e mudá-lo depois. Primeiro, vamos considerar a função CalculateNewMarketValues():

//+------------------------------------------------------------------+
//|  It calculates the data for opening a new order                  |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
//----
   return;   
   }

A função recebe o sinal de negócio atual, trSignal, e calcula todos os parâmetros de retorno com base nisso.

Note que aqui e em todo lugar, todos os parâmetros devem ser inicializados com valores seguros.

Se não inserirmos nosso próprio código, a variável marketType manterá o valor de -1 (menos um) que significa nenhuma intenção de abrir um pedido. É bom lembrar aqui que as constantes de operações de negócios não possuem valores negativos. A função para cálculo de abertura de parâmetros para um pedido pendente é muito parecida:

//+------------------------------------------------------------------+
//|  It calculates the data for placing a pending order              |
//+------------------------------------------------------------------+
void CalculateNewPendingValues(int    trSignal, 
                               int    & pendingType, 
                               double & pendingOpenPrice, 
                               double & pendingLots, 
                               double & pendingSL, 
                               double & pendingTP, 
                               string & pendingComment)
   {
   // if there is no trade signal, exit
   if (trSignal==OP_BALANCE) return;
 
   pendingType      = -1; 
   pendingOpenPrice = 0; 
   pendingLots      = 0; 
   pendingSL        = 0; 
   pendingTP        = 0; 
   pendingComment   = 0;
//----
   //insert your own code to calculate all parameters
//----
   return;
   }

A única diferença é que ela calcula um parâmetro a mais - o preço de abertura do pedido pendente.


Modificação de pedidos

A próxima operação no trabalho de um EA é a modificação de pedidos "amigáveis". Duas funções separadas são usadas para esse propósito.

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

Elas são as mesmas, cada uma toma os seguintes parâmetros como entradas:

  • ReversTradeSignal - sinal de que o sistema de negócio está invertido;
  • ModifyMarketOrderEveryTick ou ModifyPendingEveryTick - sinal de modificação de pedido em cada tick;
  • ModifyMarketBarPeriod ou ModifyPendingBarPeriod - período de tempo em minutos, onde a modificação será realizada, se não haver necessidade de mudar os níveis de preço a cada tick;
  • A matriz newSL_and_TP[][5] que contém os tickets dos pedidos a serem modificados, e os novos valores de SL, TP (e OpenPrice, no caso de pedidos pendentes).

Vamos considerar a primeira função - ModifyMarkets():

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

A primeira verificação já é um padrão - é uma verificação para o tamanho zero da matriz newSL_and_TP[][] para modificação. A segunda verificação: primeiro verifique a necessidade de modificação em cada tick. Se não há essa necessidade (ModifyEveryTick=false), então verifique o aparecimento de uma nova barra no período de tempo de minutos ModifyBarPeriod . Se ele não passou a verificação, então saia e não faça nada:

   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }

Entretanto, se as verificações preliminares foram passadas com sucesso, podemos começar a modificar os pedidos. Ao mesmo tempo não podemos esquecer de considerar duas maneiras do sistema trabalhar: direto e inverso.

   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a direct system
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a reversed system
         }
      }

A única diferença entre eles é de que os valores de newSL_and_TP[i][1] e newSL_and_TP[i][2] (StopLoss e TakeProfit) são transpostos na função OrderSend().

A função usada para modificar pedidos pendentes é bem parecida, mas adicionamos a ela o recebimento de pedidos pendentes com preço aberto:

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

Eu gostaria de chamar aqui a sua atenção para o fato de que o tipo de pedido é verificado em ambas as funções:

         type=newSL_and_TP[i][4];

e, de acordo com o valor da variável 'tipo' (0 ou 1), o programa decide se vai processar o ticket ou pular. Isso é tudo sobre as funções usadas para modificar pedidos.


Fechando um pedido de mercado

Agora temos o código para o fechamento dos pedidos de mercado. Para isso vamos usar duas matrizes que contém informação sobre os pedidos a serem fechados, e duas funções que funcionam com essas matrizes.

/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);

A matriz ticketsToClose[][2] armazena os valores do ticket e tipo do pedido a ser fechado, a matriz lotsToClose[] contém informação sobre a quantidade de lotes a serem fechados por cada posição a ser fechada. A função PrepareTicketsToClose() recebe como entradas a matriz de pedidos, Tickets[][], e o valor do sinal de negócio atual. Um sinal de compra de negócios pode também ser uma instrução para fechar pedidos de venda. A função PrepareTicketsToClose() por si foi escrita em um volume menor.

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to close orders                     |
//+------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a market order, then skip it
      if (type>OP_SELL) continue;
 
      if (Revers) // reverse the order type
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // here we will decide the fate of each open order
      //  whether we retain it in the market or add to the array of orders to be closed
      if (type==OP_BUY)
         {
         //  
         // code that allows to retain Buy
         
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // code that allows to retain Sell
         
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // order type
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // here we will specify the amount of lots to be closed
      lots[closeSize] = arrayTickets[i][2]; // volume to be closed
      // they can be closed partially, then you should rewrite the code line above
      }
//----
   return;   
   }

Você pode adicionar as suas próprias condições nele para poder incluir um pedido ou outro na lista destes a serem fechados. Se o tamanho da matriz de entrada arrayTickets é zero (isto é, não temos pedidos para serem processados), então saia da função antes, como de costume.

A função CloseMarketOrders() não representa nenhuma dificuldade:

//+------------------------------------------------------------------+
//|  It closes orders with the given tickets                         |
//+------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal(); 
   Print("",size," orders should be closed, orders opened:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Close order #",ticket," type=",type," ",lots," lots" );
      Print(" ");
      RefreshRates(); // just in case, update the data of the market environment
 
      // buy closing block
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Buy order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      //  sell closing block
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Sell order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      } 
//----
   return;
   }

No ciclo, a matriz para fechamento o percorre, o ambiente de mercado é atualizado com a função RefreshRates(), e o programa tenta fechar o pedido no preço que corresponde ao tipo de pedido. Erros no fechamento são analisados no mínimo possível, você deve adicionar seu próprio algoritmo para processar a situação.


Exclusão de pedidos pendentes

A operação para deletar pedidos pendentes é muito parecida com a de fechar pedidos de mercado. Ela somente não contém uma matriz de volumes, já que precisamos somente conhecer o ticket de um pedido pendente para excluí-lo:

/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);

Correspondentemente, a sintaxe deste bloco de funções é muito similar, então darei ele aqui sem maiores explicações:

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to delete pending orders            |
//+------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a pending order, then skip
      if (type<=OP_SELL) continue;
      
      if (Revers) // reverse the order type
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // here we will decide the fate of each pending order
      //  whether we retain it or add to the array of orders to be deleted
      //  here we will give an example of a buying signal retaining 
      // pending orders OP_BUYLIMIT and OP_BUYSTOP
      // the same for selling signals
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // code that allows to retain Buy
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // code that allows to retain Sell
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;   
   }


Abrindo uma posição no mercado e colocando um pedido pendente

Assim, restam ainda duas últimas operações. Já recebemos um sinal de negociação, preparamos a lista de pedidos, modificamos, fechamos e excluímos todos os pedidos necessários. Agora podemos abrir uma posição pelo mercado, se for razoável, ou colocar um pedido pendente.

/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

A primeira função, OpenMarketOrder(), obtém todas as entradas necessárias, incluindo o sinal de sistema reverso.


///+------------------------------------------------------------------+
//|  It opens a position by market                                    |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// sign of system reverse
                     int    Type,       // order type - OP_BUY or OP_SELL
                     double lots,       // volume of the position to be opened
                     double SL,         // level of StopLoss
                     double TP,         // level of TakeProfit
                     string comment)    // comment on the order
   {
 
   //Print("Open order Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // reverse the signals
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)  
         {
         openType = OP_SELL; // Buy will become Sell
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL) 
         {
         openType = OP_BUY;  // Sell will become Buy
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY) 
      {
      price = Ask;
      Color = Blue; 
      }
   if (openType==OP_SELL) 
      {
      price=Bid;
      Color = Red; 
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (ticket>0)
      {
      Print("Failed to open a position by market");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  further processing of the situation, code independently
      }
//----
   return;
   }

Não há nada complicado nessa função, exceto o processamento da situação reversa. Para o sistema reverso, você deve relaxar SL e TP e adicionar uma mudança igual ao valor de spread. A principal coisa aqui é não esquecer que qualquer valor de Stop Loss ou Take Profit podem ser iguais a zero.

A função SendPendingOrder() é somente um pouco mais complicada, já que temos que considerar o fato de que podem haver dois tipos de pedidos pendentes para comprar e dois para vender. Quanto ao resto, é similar a OpenMarketOrder().

//+------------------------------------------------------------------+
//|  It places a pending order                                       |
//+------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// sign of system reverse
                      int    Type,
                      double OpenPrice, 
                      double Lots, 
                      double SL, 
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // reverse order type and levels
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Invalid order type Type=",Type," in function SendPendingOrder()!!!");                           
         
         }
      }
   else openType = Type;   
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (!res)
      {
      Print("Failed placing pending order");
      //  further processing of the situation, code independently
      }
 
//----
   return;
   }

Em ambas funções, o processamento de erro de solicitação de negociação é mínimo, você pode adicionar seu próprio código para isso.


Versão final da função start()

Uma vez tendo criado todas as funções necessárias, podemos dar outra olhada na função start() que foi passada de um modelo de texto para um código completo:

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always zeroize the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Every bar in the preset period   (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*   
   if (trSignal==OP_BUY) Print("Buy signal");                    
   if (trSignal==OP_SELL) Print("Sell signal");                    
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("The current signal is equal to ",trSignal);
*/
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 
 
   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
               (ModifyMarketOrderEveryTick = true)
               
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
               (ModifyPendingEveryTick = true)
        
        c) Modification of each open order on each new bar in the preset period (SL and TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modification of each pending order on each new bar in the preset period (OpenPrice, SL and TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);
 
/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE) 
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, 
            pendingTP,pendingComment);
//----
   return(0);
  }
//+------------------------------------------------------------------+

A lógica do EA para operação é totalmente visível, o entendimento dos detalhes estão escondidos nas funções correspondentes. Durante a depuração do programa, nós sabemos onde procurar por erros. Além do mais, toda a lógica do Expert Advisor é claramente separada: sabemos em qual função devemos mudar o código se desejarmos mudar o algoritmo para encontrar pedidos "amigáveis", sabemos onde inserir condições para fechar ou abrir uma posição, e em qual função devemos introduzir a Parada Móvel, se necessário. A única coisa que resta é usar o modelo pronto na prática.


Exemplo de uso

O modelo obtido, Template_EA.mqt, está anexado a esse artigo. Você pode salvá-lo como Expert.mqt na pasta C:\Program Files\MetaTrader 4\experts\templates\. Nesse caso, ao criar um novo EA, esse arquivo será sempre usado como um modelo para ele, e você terá todas as funções acima automaticamente inseridas. Há outra alternativa - salvá-lo na mesma pasta sem mudar seu nome. Então assim você será capaz de especificar esse arquivo como modelo manualmente, ao criar um novo Expert Advisor.



Agora podemos selecionar nosso modelo Template_EA, e pressionar "Próximo". Vamos escrever a seguinte estratégia simples:

  • Um sinal de compra é formado, quando a linha de sinal estocástico sai fora da área de sobrevenda e atravessa o nível predefinido de DownLevel da parte inferior para o topo (por padrão, é 10);
  • Um sinal de venda é formado, quando a linha de sinal estocástico sai fora da área de sobrecompra e atravessa o nível predefinido de UpLevel do topo para a parte inferior (por padrão, é 90);
  • Um StopLoss de proteção é colocado na distância de 100 pontos do preço de abertura (ele pode ser mudado);
  • Obter Lucros (Take Profit) é colocado na distância de 100 pontos do preço de abertura (ele pode ser mudado);
  • Os parâmetros do estocástico são definidos através dos parâmetros externos do EA e podem também ser alterados.

Vamos nomear nosso novo EA Osc_test e introduzir os parâmetros externos necessários:



Pressione "Concluir" e veja que o Assistente do Expert Advisor inseriu esses parâmetros em nosso EA criado com base no modelo acima.

Assim adicionamos os parâmetros necessários na criação de um EA:

//+------------------------------------------------------------------+
//|                                                     Osc_test.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- input parameters
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constant "off market"
#define OP_BALANCE 6

Agora vamos inserir em yourFunction() o código que produz sinais de compra e venda:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);   
   }

Isso levou somente quatro linhas. Somente resta precisar a função CalculateNewMarketValue() que prepara os dados para abrir uma posição no mercado.

//+------------------------------------------------------------------+
//|  It calculates the data to open a new order                      |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;   
   }

Como você pode ver, isso também exige a adição de somente 5 (cinco!) linhas. Então, escrevemos somente o quanto de código precisamos para descrever nossa estratégia simples. Essa é a primeira vantagem dessa abordagem.

A criação de um modelo exigirá a elaboração de todos os detalhes da concepção de um EA típico. Entretanto, isso terá um retorno no futuro, quando você criar estratégias.


Vantagens adicionais

Bem, há mais ainda por vir. Vamos começar a testar o EA obtido em qualquer símbolo e qualquer período de tempo. Escolha EURUSD H1, por exemplo, com as configurações padrão:


Nesse caso, não importa se há qualquer lucro ou não. Vamos olhar no relatório de teste:


A quantidade total de negociações é 430, 241 delas foram vendendo e 189 delas foram comprando. Agora vamos inverter o sistema: onde haviam negociações de venda, vamos começar a comprar, e vice-versa. Para isso, vamos definir o valor 'verdadeiro' no parâmetro ReversTradeSignal. Esse é o sinal de inversão do sistema:


Inicie os testes sem alterar nenhum outro parâmetro. Abaixo está o relatório de teste obtido:


De fato, agora temos 241 negociações de compra e 189 negociações de venda. Bem, a quantidade de negociações de compra e venda foram transpostas. A percentagem de negociações rentáveis também foi invertida. Não precisamos reescrever o EA para verificar como um EA inverso funciona!

Entretanto, essa não é toda a história. Temos o parâmetro TradeDay, você se lembra? Por padrão, ele é igual a zero, mas o que acontece se quisermos que nosso EA somente negocie nas sextas-feiras? Defina seu valor para 5:


Inicie os testes sem tocar em nenhum outro parâmetro. Veja o resultado. Podemos ver que o relatório do Testador nos mostrou como seria se o EA negociasse somente nas sextas-feiras com um sistema inverso:


Podemos ver que somente ocorreram 81 negociações em comparação às iniciais 430. Isso significa que outras negociações foram em outros dias da semana. Nesse caso não estamos preocupados muito de que o resultado seja ainda lucrativo. Suponha que queremos saber como o EA iria negociar no histórico se permitíssemos ele a fazer trocas em todos os dias menos nas sextas-feiras. Para isso, temos o parâmetro ReversDay - somente mude ele para 'verdadeiro'.


Vamos iniciar os testes e então ver o relatório:


Ocorreram 430 negociações, subtraímos os negócios da sexta-feira (81), e obtivemos 349. Todos os números somam: 430-81=349. Assim, invertemos corretamente os dias de negociação. E não precisamos reprogramar o EA.


Conclusão

Eu vejo duas desvantagens sérias nesse artigo. Por um lado, é muito conciso e não dá descrições detalhadas de quaisquer funções. Por outro lado, é um pouco longo para a primeira leitura. Entretanto, espero que existirá eventualmente um tipo de lobby suportando essa abordagem para criação de EAs no MQL4. Essa abordagem é adequada para trabalho em equipe melhor do que qualquer outra: Seria mais razoável criar os modelos necessários por uma tempestade de ideias ao invés de tentar melhorar somente um algoritmo de negócios continuamente.

Você pode criar um modelo especial para negócios simultâneos de vários símbolos, pode criar um código de processamento de erro especial para os erros retornados por funções de negociação. Você pode também adicionar funções de informação, funções de registro, funções criando relatórios especiais em resultados de testes ou em negociações em tempo real. Você pode adicionar alguma limitação nas negociações não somente por dias da semana, mas também por horas, selecionando as sessões de negociação. Você pode também acumular todas as funções de serviço (interface) em um arquivo *.mqh separado de modo a somente ver as funções a serem redefinidas. Existem muitas possibilidades com essa abordagem.

A vantagem principal é que sendo desenvolvida somente uma vez, o modelo pode ser usado de forma contínua. Ao mesmo tempo, a probabilidade de erros ocorrerem em seus novos EAs também diminui.

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

Arquivos anexados |
Osc_test.mq4 (36.96 KB)
Template_EA.mqt (35.82 KB)
Caça de tendências Caça de tendências
O artigo descreve um algoritmo de aumento de volume de lucros em uma negociação. Sua implementação usando os recursos do MQL4 é apresentada no artigo.
Especulação confortável Especulação confortável
Esse artigo descreve o método para criar uma ferramenta para a especulação (scalping) confortável. Entretanto, tal abordagem a uma abertura de negócios pode ser aplicada a qualquer negociação.
Expert Advisors baseado em sistemas de trading populares e alquimia da otimização de robô de trading Expert Advisors baseado em sistemas de trading populares e alquimia da otimização de robô de trading
Esse artigo trata do algoritmo de implementação dos sistemas de negociação mais simples. O artigo será útil para investidores iniciantes e desenvolvedores de EA.
Usando o MetaTrader 4 para análise de padrões baseados em tempo Usando o MetaTrader 4 para análise de padrões baseados em tempo
A análise de padrões baseados em tempo pode ser usada no mercado financeiro para determinar o melhor momento para entrar em uma negociação ou momento no qual uma negociação deve ser totalmente evitada. Aqui usamos o MetaTrader 4 para analisar os dados históricos de mercado e produzir resultados otimizados que podem ser úteis para aplicação em sistemas de negociações mecânicas.