MetaEditor : Modelos como um local para se apoiar
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/.
- 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"
//+------------------------------------------------------------------+ //| 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.
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:
- Obtenha os valores para as médias móveis do período curto e período longo.
- Verifique se sofrem alguma interseção.
- Se a menor intercede a maior da parte inferior à superior então compre, StopLoss=N pontos.
- Se a menor atravessa a maior da parte superior à inferior então venda, StopLoss=N pontos.
- Se uma posição longa aberta estiver disponível, feche-a ao sinal de venda.
- 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:
- Bloco para receber sinais de negócios.
- Abrir um pedido de mercado ou colocando um pedido pendente.
- 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 & 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.
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, 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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso