Otimização. Algumas idéias simples
Introdução
Tendo encontrado uma estratégia coerente para o EA negociar, nós lançamos ele no gráfico EURUSD, certo? Pode a estratégia ser mais rentável em outros pares de moedas? Há outros pares de moedas em que a estratégia dará melhores resultados, sem necessidade de aumentar o lote em progressão geométrica?
O que vai acontecer se nós escolhermos EURUSD e o período padrão H1 e, em seguida, se não estamos satisfeitos com o resultado, alteramos para EURJPY em H4?
Além disso, se temos um sistema de operação de 64 bits, o que nos permite parar de se preocupar com a velocidade de testar o sistema, podemos esquecer combinações absurdas dos parâmetros de entrada do sistema de negociação, que tomam parte na enumeração completa durante a otimização e que resulta que nós temos que negligenciar nos relatórios finais?
Eu resolvi esses "pequenos problemas" sozinho e neste artigo vou partilhar as soluções eficazes. Eu aprecio no entanto, que pode haver outras soluções mais ideais.
Otimização por Período
A MQL5 fornece um conjunto completo de períodos: a partir de M1, M2, M3, M4, ... H1, H2, ... até gráficos mensais. No total, existem 21 períodos. No processo de otimização, no entanto, queremos saber quais os períodos que se encaixam em nossa estratégia - períodos de curto prazo como М1 e М5, os de médio prazo - como H2 e H4 ou os de longo prazo - D1 e W1.
Inicialmente nós não precisamos dessa diversidade de opções. Em qualquer caso, se nós conseguirmos ver podemos ver que a estratégia se prova eficaz no período М5, então, na próxima fase da otimização nós podemos verificar se ela irá funcionar em М3 ou М6.
Se usarmos uma variável do tipo ENUM_TIMEFRAMES como parâmetro de entrada:
input ENUM_TIMEFRAMES marcoTF= PERIOD_M5;
então, o otimizador irá oferecer 21 variações de otimização. Será que realmente precisamos desse montante?
Inicialmente nós não precisamos. Como podemos simplificar a otimização? Primeiramente, nós podemos definir uma enumeração:
enum mis_MarcosTMP { _M1= PERIOD_M1, _M5= PERIOD_M5, _M15=PERIOD_M15, // _M20=PERIOD_M20, _M30=PERIOD_M30, _H1= PERIOD_H1, _H2= PERIOD_H2, _H4= PERIOD_H4, // _H8= PERIOD_H8, _D1= PERIOD_D1, _W1= PERIOD_W1, _MN1=PERIOD_MN1 };
onde podemos adicionar ou excluir os períodos de interesse. Para otimização, definimos a variável de entrada no início do código:
input mis_MarcosTMP timeframe= _H1;
e definimos uma nova função na biblioteca .mqh:
//----------------------------------------- DEFINIÇÃO DO PERÍODO ---------------------------------------------------------- ENUM_TIMEFRAMES defMarcoTiempo(mi_MARCOTMP_CORTO marco) { ENUM_TIMEFRAMES resp= _Period; switch(marco) { case _M1: resp= PERIOD_M1; break; case _M5: resp= PERIOD_M5; break; case _M15: resp= PERIOD_M15; break; //case _M20: resp= PERIOD_M20; break; case _M30: resp= PERIOD_M30; break; case _H1: resp= PERIOD_H1; break; case _H2: resp= PERIOD_H2; break; case _H4: resp= PERIOD_H4; break; //case _H8: resp= PERIOD_H8; break; case _D1: resp= PERIOD_D1; break; case _W1: resp= PERIOD_W1; break; case _MN1: resp= PERIOD_MN1; } return(resp); }
Declaração de uma nova variável no escopo de variáveis globais:
ENUM_TIMEFRAMES marcoTmp= defMarcoTiempo(marcoTiempo); //Período é definido como uma variável global
"marcoTmp" é uma variável global, que vai ser utilizada pelo EA para definir um período de tempo necessário do gráfico. Na tabela dos parâmetros de otimização, o intervalo de lançamento da variável "marcoTiempo" pode ser definida. Isto irá cobrir apenas os passos de nosso interesse sem gastar tempo e recursos na análise de М6 ou М12. Dessa forma, podemos analisar os resultados do trabalho do EA em diferentes intervalos de tempo.
Certamente, isto pode ser feito com
ENUM_TIMEFRAMES marcoTmp= (ENUM_TIMEFRAMES)marcoTiempo;
Isso fica evidente após meses, até mesmo anos de programação, se você é um perfeccionista e passa pelo código muitas vezes para tentar simplificá-lo. Ou se você estiver usando um VPS e tentando manter sua conta para baixo, otimizando o desempenho do computador.
Otimizando um Símbolo ou um Conjunto de Símbolos
No Strategy Tester do MetaTrader 5 há um modo de otimização, o que facilita a execução do EA em todos os símbolos selecionados na janela do MarketWatch. Esta função, no entanto, não permite a organização da otimização como se o símbolo selecionado fosse um outro parâmetro. Então, se existem 15 símbolos selecionados, em seguida, o testador irá executar 15 vezes. Como podemos descobrir qual símbolo é o melhor para o nosso EA? Se este é um EA multi-moeda, então qual grupo de símbolos nos dará o melhor resultado e com qual parâmetro definido? Variáveis string não são otimizadas no MQL5. Como isso pode ser feito?
Codifique um símbolo ou um par de símbolos pelo valor do parâmetro de entrada da seguinte forma:
input int selecDePar= 0; string cadParesFX= selecPares(selecDePar);
O parâmetro "selecDePar" é utilizado como um parâmetro de otimização, que é convertido em uma variável string. Use a variável "cadParesFX" no EA. O par/pares de moedas (para este código é irrelevante saber se a estratégia é multi-moeda ou não) vão ser armazenados nessa variável, juntamente com outros parâmetros regulares da otimização.
//------------------------------------- SELECIONANDO O CONJUNTO DOS PARES ------------------------------------- string selecPares(int combina= 0) { string resp="EURUSD"; switch(combina) { case 1: resp= "EURJPY"; break; case 2: resp= "USDJPY"; break; case 3: resp= "USDCHF"; break; case 4: resp= "GBPJPY"; break; case 5: resp= "GBPCHF"; break; case 6: resp= "GBPUSD"; break; case 7: resp= "USDCAD"; break; case 8: resp= "CADJPY"; break; case 9: resp= "XAUUSD"; break; case 10: resp= "EURJPY;USDJPY"; break; case 11: resp= "EURJPY;GBPJPY"; break; case 12: resp= "GBPCHF;GBPJPY"; break; case 13: resp= "EURJPY;GBPCHF"; break; case 14: resp= "USDJPY;GBPCHF"; break; case 15: resp= "EURUSD;EURJPY;GBPJPY"; break; case 16: resp= "EURUSD;EURJPY;GBPCHF"; break; case 17: resp= "EURUSD;EURJPY;USDJPY"; break; case 18: resp= "EURJPY;GBPCHF;USDJPY"; break; case 19: resp= "EURJPY;GBPUSD;GBPJPY"; break; case 20: resp= "EURJPY;GBPCHF;GBPJPY"; break; case 21: resp= "USDJPY;GBPCHF;GBPJPY"; break; case 22: resp= "EURUSD;USDJPY;GBPJPY"; break; case 23: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD"; break; case 24: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD;AUDUSD"; break; } return(resp); }
Dependendo de qual é o nosso objetivo, nós definimos combinações de pares e informamos o testador sobre o intervalo a ser analisado. Dê o Strategy Tester um comando para otimizar o parâmetro "selecDePar" no intervalo de 15 a 22 (veja a imagem abaixo). O que fazemos quando queremos comparar os resultados de uma moeda única? Nesse caso, executamos a otimização no intervalo de 0 a 9.
Por exemplo, o EA recebe o valor da cadParesFX = parâmetro "EURUSD; EURJPY; GBPCHF". Em OnInit() chama-se a função "cargaPares()", que preenche o array dinâmico arrayPares[] com strings, dividido pelo símbolo ";" no parâmetro cadParesFX. Todas as variáveis globais precisam ser carregadas em arrays dinâmicos, que salvam os valores de todos os símbolos, incluindo o controle da abertura de uma nova barra em um símbolo, se possível. No caso, estamos trabalhando com um símbolo, as dimensões do array será igual a um.
//-------------------------------- CONVERSÃO DA STRING DE PAR DE MOEDA PARA UM ARRAY ----------------------------------------------- int cargaPares(string cadPares, string &arrayPares[]) { //converte "EURUSD;GBPUSD;USDJPY" para {"EURUSD", "GBPUSD", "USDJPY"}; devolvendo o número de paresFX string caract= ""; int i= 0, k= 0, contPares= 1, longCad= StringLen(cadPares); if(cadPares=="") { ArrayResize(arrayPares, contPares); arrayPares[0]= _Symbol; } else { for (k= 0; k<longCad; k++) if (StringSubstr(cadPares, k, 1)==";") contPares++; ArrayResize(arrayPares, contPares); ZeroMemory(arrayPares); for(k=0; k<longCad; k++) { caract= StringSubstr(cadPares, k, 1); if (caract!=";") arrayPares[i]= arrayPares[i]+caract; else i++; } } return(contPares); }
Na função OnInit() ela é implementada da seguinte maneira:
string ar_ParesFX[]; //Array, contendo nomes dos pares para o EA trabalhar com ele int numSimbs= 1; //Variável, que contém informações sobre o número dos símbolos que estão funcionando int OnInit() { //... numSimbs= cargaPares(cadParesFX, ar_ParesFX); //Retorna o array ar_ParesFX com os pares para o funcionamento no EA //... }
Se numSimbs>1, a função OnChartEvent() é chamada. Isso funciona para um sistema de multi-moeda. Caso contrário, a função OnTick() é usada:
void OnTick() { string simb=""; bool entrar= (nSimbs==1); if(entrar) { .../... simb= ar_ParesFX[0]; gestionOrdenes(simb); .../... } return; } //+-----------------------------------------------------------------+ //| MANIPULADOR DE EVENTOS | //+-----------------------------------------------------------------+ void OnChartEvent(const int idEvento, const long& lPeriodo, const double& dPrecio, const string &simbTick) { bool entrar= nSimbs>1 && (idEvento>=CHARTEVENT_CUSTOM); if(entrar) { .../... gestionOrdenes(simbTick); .../... } }
Isto significa que todas as funções do papel do parâmetro de entrada deve conter, pelo menos, o símbolo sob investigação. Por exemplo, em vez da função Dígitos(), nós devemos usar o seguinte:
//--------------------------------- SÍMBOLOS DE UM SÍMBOLO ------------------------------------ int digitosSimb(string simb= NULL) { int numDig= (int)SymbolInfoInteger(simb, SYMBOL_DIGITS); return(numDig); }
Em outras palavras, temos de esquecer as funções Symbol() ou Point(), bem como outras variáveis tradicionais do МetaТtarder 4, tais como Ask ou Bid
//----------------------------------- VALOR DO PONTO no preço (Point())------------------------------ double valorPunto(string simb= NULL) { double resp= SymbolInfoDouble(simb, SYMBOL_POINT); return(resp); } //--------------------------- preço ASK-BID ----------------------------------------- double precioAskBid(string simb= NULL, bool ask= true) { ENUM_SYMBOL_INFO_DOUBLE precioSolic= ask? SYMBOL_ASK: SYMBOL_BID; double precio= SymbolInfoDouble(simb, precioSolic); return(precio); }
Nós também nos esquecemos sobre a função de controle sobre a abertura da barra, que está presente nesses códigos. Se os ticks recebidos em EURUSD estão dizendo sobre a abertura de um novo barra, em seguida, os ticks USDJPY pode não ter sido recebidos nos próximos 2 seg. Como resultado, no próximo tick USDJPY o EA irá descobrir que uma novo barra estrá abrindo para este símbolo, mesmo se para EURUSD este evento tiver levado 2 segundos atrás.
//------------------------------------- NOVA VELA MULTI-MOEDA ---------------------------------------------- bool nuevaVelaMD(string simb= NULL, int numSimbs= 1, ENUM_TIMEFRAMES marcoTmp= PERIOD_CURRENT) { static datetime arrayHoraNV[]; static bool primVez= true; datetime horaVela= iTime(simb, marcoTmp, 0); //Recebido o horário de abertura da vela atual bool esNueva= false; int codS= buscaCadArray(simb, nombreParesFX); if(primVez) { ArrayResize(arrayHoraNV, numSimbs); ArrayInitialize(arrayHoraNV, 0); primVez= false; } esNueva= codS>=0? arrayHoraNV[codS]!= horaVela: false; if(esNueva) arrayHoraNV[codS]= horaVela; return(esNueva); }
Este método permitiu-me descobrir durante uma otimização que passou:
- o EA funciona bem em EURUSD,
- funciona muito mal em EURJPY,
- e funciona de forma satisfatória em USDJPY
- Nos pares EURUSD, GBPCHF, EURJPY ele funciona muito bem (caso real).
Isto é válido para o período de М5 e uma certa combinação de outros parâmetros de otimização, mas não para Н1 ou Н2.
Há um momento estranho aqui. Perguntei apoio técnico sobre o assunto. Eu não sei do por que isso está acontecendo, mas o resultado da otimização difere dependendo do símbolo que nós selecionarmos no Strategy Tester. É por isso que para verificar o resultado, eu mantive este par fixo através do desenvolvimento de estratégias e certifiquei-me que este é um daqueles pares que podem ser analisadas no Otimizador.
Otimização de uma Combinação de Parâmetros
Às vezes, algumas combinações ilógicas fora de todas as combinações dos parâmetros que participam na otimização virá ser adequada. Alguns deles fazem a estratégia razoável. Por exemplo, se a variável de entrada "maxSpread" define o valor do spread definido para uma operação de negociação, nós otimizamos essa variável para otimizar vários pares onde a média do spread da corretora é menor do que 30 e XAUUSD é 400. É um absurdo para analisar os pares s eles excederem 50 e XAUUSD é inferior a 200. Depois de passar os dados para o otimizador, defina "maxSpread entre 0 e 600, com o intervalo de 20", mas tal definições em conjunto com outros parâmetros produz numerosas combinações que não fazem sentido.
Seguindo o padrão descrito na seção anterior, definimos os pares para otimização na função "selecPares()". É atribuído para o EURUSD a opção 0 e para XAUUSD a 9. Em seguida, defina uma variável global do tipo bool "paramCorrect".
bool paramCorrect= (selecDePar<9 && maxSpread<50) || (selecDePar==9 && maxSpread>200);
Carregue a ação em OnInit() somente se paramCorrect está na posição correta true.
int OnInit() { ENUM_INIT_RETCODE resp= paramCorrect? INIT_SUCCEEDED: INIT_PARAMETERS_INCORRECT; if (paramCorrect) { //... nSimbs= cargaPares(cadParesFX, nombreParesFX); //Retorna o array nombreParesFX contendo os pares para trabalhar com o EA //... Função da inicialização do EA } return(resp); }
Se o paramCorrect está na posição incorreta false, então o EA não executa qualquer ação na OnInit() e retorna o INIT_PARAMETERS_INCORRECT para o Strategy Tester, o que significa um conjunto de dados de entrada incorreta. Quando o Strategy Tester receber o valor INIT_PARAMETERS_INCORRECT da OnInit(), então este conjunto de parâmetros não são passadas para outros agentes de teste para a implementação e a linha na tabela com resultados de otimização é preenchido com zeros e destacados em vermelho (veja a imagem abaixo) .
O motivo do desligamento do programa é passado para OnDeinit() como uma variável de entrada e que ajuda a entender a razão do fechamento do EA. Emora esta seja uma questão diferente.
void OnDeinit(const int motivo) { if(paramCorrect) { //funções do programa para desligamento } infoDeInit(motivo); return; } //+-------------------------------------- INFORMAÇÃO SOBRE O DESLIGAMENTO DO PROGRAMA----------------------- string infoDeInit(int codDeInit) { //Informa sobre o motivo do desligamento do programa string texto= "inicialização do programa...", text1= "CIERRE por: "; switch(codDeInit) { case REASON_PROGRAM: texto= text1+"O EA terminou o seu trabalho com a função ExpertRemove()"; break;// 0 case REASON_ACCOUNT: texto= text1+"A conta foi alterada"; break; //6 case REASON_CHARTCHANGE: texto= text1+"Símboro ou período alterado"; break; //3 case REASON_CHARTCLOSE: texto= text1+"O gráfico foi fechado"; break; //4 case REASON_PARAMETERS: texto= text1+"Parâmetros de entrada alterados pelo usuário"; break; //5 case REASON_RECOMPILE: texto= text1+"O programa foi recompilado"; break; //2 case REASON_REMOVE: texto= text1+"O programa foi deletado do gráfico"; break; //1 case REASON_TEMPLATE: texto= text1+"Outro modelo gráfico foi usado"; break; //7 case REASON_CLOSE: texto= text1+"O terminal foi fechado"; break; //9 case REASON_INITFAILED: texto= text1+"O manipulador OnInit() retornou um valor diferente de zero"; break; //8 default: texto= text1+"Outra razão"; } Print(texto); return(texto); }
A coisa é que, se o conjunto de parâmetros recebidos pelo Otimizador no estágio previsto define "paramCorrect" para false (por exemplo, se o spread de EURUSD for fixado em 100 pontos), então nós não executaremos o EA e este passo da otimização tornará-se zero sem o uso desnecessário de recursos de seu computador e despesas com aluguel de agentes para a sua conta MQL5.сommunity.
Certamente, todos os referidos acima podem ser implementados na OnTesterInit() com as funções ParameterGetRange() e ParameterSetRange(), mas o padrão descrito parece ser mais simples. Isto é garantido que funcione enquanto o padrão com a OnTesterInit() não for tão consistente.
Conclusão
Temos discutido acelerar a busca por períodos ideais no МetaТrader 5, otimizando o parâmetro "símbolo", quando o МetaТrader 5 não permitir otimizar variáveis de string e tornar-se indiferente ao número de símbolos que o EA está usando. Vimos também uma ilustração de como reduzir o número de etapas de otimização diminuindo os conjuntos ilógicos dos parâmetros de entrada, mantendo o desempenho do computador e salvando fundos.
As idéias acima não são novas e elas podem ser implementados por um novato ou um programador com alguma experiência. Essas idéias foram o resultado de uma longa busca por informações e o uso do programa de depuração. Estas são idéias muito simples, mas eficientes. Você poderia me perguntar por que eu estou compartilhando isso se eu quero que a MQL5 gere lucro? A resposta é superar a "solidão" de um programador.
Obrigado pela sua atenção. Se vocês lerem até o fim sendo um programador experiente, por favor, não me julguem muito duramente.
Traduzido do espanhol pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/es/articles/1052
- 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
Caso tenha interesse, tenho uma sugestão que poderia lhe ser útil. Seria interessante otimizar desta maneira?
Parâmetros externos para otimizar:
Numero de ativos: 1
Ativos: EURUSD
Ou
Numero de ativos: 2
Ativos: EURUSD; USDJPY
ou
Ativo1: EURUSD
Ativo2: USDJPY
Etc.
Otimizando assim a quantidade de Numero de ativos e Ativos. No seu caso a otimização é feita individualmente para cada ativo ou um setup para todos os ativos ao mesmo tempo?
Uma outra maneira seria automatizar a otimização assim que colocar o EA para rodar em algum gráfico. Para EAs que só operam por fechamento de vela (no meu caso por exemplo), seria possível otimizar pelas velas do gráfico, sem ser pelo Strategy Tester?