Uma Formulação Genérica de Otimização (GOF) para Implementar Max Personalizado com Restrições
Introdução — Noções Básicas de Otimização
Problemas de otimização têm duas fases: 1) formulação do problema e 2) solução do problema. Na primeira fase, há três componentes principais: as variáveis de entrada, as funções objetivo e as funções de restrição. Na segunda fase, a solução do problema é realizada numericamente com um algoritmo de otimização.
Variáveis (notação x_1, x_2, x_3, …, x_n): As variáveis são os "botões" que podemos ajustar para maximizar a função objetivo. Existem variáveis de diferentes tipos: inteiro, real e Booleano. Em consultores especializados, podemos usar variáveis como: o período da média móvel, a relação TP/SL na entrada, o SL em pips, etc.
Funções Objetivo (notação f_i(x)): Se houver mais de uma função objetivo, o problema é chamado de problema de otimização multiobjetivo. O algoritmo de otimização do MetaTrader 5 espera apenas uma função objetivo, portanto, para considerar mais de um objetivo, precisamos combiná-los em um só. A maneira mais simples de combinar funções objetivo é criando uma soma ponderada delas. Alguns desenvolvedores propuseram usar a operação de multiplicação para combiná-las, e isso pode funcionar em algumas situações, mas preferimos a soma como uma abordagem melhor. Neste artigo, usamos uma soma "direcionada e ponderada" de objetivos, como será explicado abaixo. Como exemplos de funções objetivo, temos: saldo, meta de lucro, taxa de vitória, retorno anual, lucro líquido, etc. Funções de Restrição (notação g_i(x)): Estas são funções que geram um valor que o usuário deseja limitar. O limite pode ser um limite superior, como em g_i(x) ≤ U_i, onde g_i(x) é a i_ésima restrição, e U_i é o limite superior. Da mesma forma, uma restrição de limite inferior é como g_i(x) ≥ L_i, onde L_i é o limite inferior.O algoritmo de otimização do MetaTrader 5 leva em consideração apenas as restrições impostas às variáveis de entrada (também conhecidas como restrições laterais), por exemplo, 3 <= x_1 <= 4, mas nenhum outro tipo de restrição pode ser usado. Portanto, precisamos incluir a presença de qualquer restrição adicional g_i(x) na função objetivo final e única, F(x), como será mostrado abaixo. Exemplos de funções de restrição g_i(x) são: limitar o número de perdas consecutivas, ou o índice de Sharpe, ou a taxa de vitória, etc.
Algoritmos de Otimização
Em termos gerais, existem dois tipos principais de algoritmos de otimização. O primeiro tipo é o mais clássico, baseado no cálculo de gradientes de todas as funções envolvidas no problema de otimização (isso remonta aos tempos de Isaac Newton). O segundo tipo é mais recente (desde a década de 1970) e não utiliza informações de gradiente. Entre esses dois tipos, podem haver algoritmos que combinam as duas abordagens mencionadas, mas não precisamos abordá-los aqui. O algoritmo do MetaTrader 5 chamado “Algoritmo Genético Rápido” — na aba Configurações do terminal MetaTrader 5 — pertence ao segundo tipo. Isso nos permite ignorar a necessidade de calcular gradientes para funções objetivo e de restrição. Além disso, graças à natureza sem gradiente do algoritmo do MetaTrader 5, conseguimos incluir funções de restrição que não seriam apropriadas para algoritmos baseados em gradiente. Mais sobre isso será discutido abaixo.
Um ponto importante é que o algoritmo do MetaTrader 5 chamado “Algoritmo Completo Lento” não é realmente um algoritmo de otimização, mas sim uma avaliação exaustiva de todas as combinações possíveis de valores para todas as variáveis de entrada dentro das restrições laterais.
A Função Objetivo F(x) Construída a partir de Múltiplos Objetivos f_i(x)
Nesta seção, abordaremos a combinação de múltiplas funções objetivo em uma única função objetivo F.
Como mencionado na Introdução, usamos a soma para combinar múltiplos objetivos. O leitor é convidado a alterar o código-fonte para usar uma combinação multiplicativa, se assim desejar. A fórmula de combinação por soma é:
onde
- x é o vetor de n variáveis de entrada
- f_i(x) é a i_ésima função objetivo
- W_i é o i_ésimo peso
- T_i é o i_ésimo alvo desejado para a i_ésima função objetivo
O alvo serve como um valor de normalização (divisor) para que um determinado objetivo seja comparável a outros objetivos na soma. Em outras palavras, a soma ponderada é para funções objetivo "normalizadas". O peso serve como tal, como um peso que multiplica o objetivo normalizado (f_i(x)/T_i) de modo que o usuário possa enfatizar qual objetivo normalizado é mais importante que o outro. Os pesos W_i são convexificados dividindo a soma de W_i*f_i(x)/T_i pela soma dos W_i.
Seleção de W_i: A convexificação dos pesos permite ao usuário focar em seu valor relativo, em vez de seu valor absoluto. Por exemplo, suponha que haja três objetivos: retorno anual, fator de recuperação e fator de lucro. Esses pesos: W_1=10, W_2=5, W_3=1, têm o mesmo efeito que W_1=100, W_2=50, W_3=10, ou seja, o objetivo normalizado 1 (retorno anual/T_1) é considerado duas vezes mais importante que o objetivo normalizado 2 (fator de recuperação/T_2), e dez vezes mais importante que o objetivo normalizado 3 (fator de lucro/T_3). Não há valores certos ou errados, desde que sejam positivos e expressem a importância relativa com base no critério do usuário.
Seleção de T_i: A seleção dos alvos T_i deve ser feita com cuidado após algumas simulações, antes de fazer a otimização completa. A razão para isso é estimar as faixas de cada função objetivo e definir valores de T_i que produzirão funções normalizadas (f_i(x)/T_i) de magnitude comparável. Por exemplo, suponha que sua conta tenha um saldo inicial de 10.000; seu algoritmo EA faz com que o saldo final seja em torno de 20.000 antes de ser otimizado; o fator de lucro é 0,9. Se você configurar seu problema de otimização com dois objetivos: f_1 = saldo, f_2 = fator de lucro, então um bom valor para os alvos seria: T_1 = 30k, T_2 = 2, o que fará com que ambas as funções normalizadas fiquem na mesma ordem de magnitude (valores comparáveis). Uma vez que você execute uma otimização completa, pode descobrir que o EA gera um saldo final muito alto e um fator de lucro semelhante. Nesse ponto, você pode ajustar os valores T_i se as funções normalizadas estiverem em ordens de magnitude diferentes. Os valores-alvo também devem ser números reais positivos. Use seu julgamento. Mais sobre este tópico será discutido quando abordarmos a saída do código GOF.
Adicionando Restrições
A formulação da função objetivo como a soma ponderada dos objetivos individuais é bastante padrão. Agora, introduzimos uma maneira simples de incluir funções de restrição no problema de otimização de tal forma que o algoritmo Fast-Genetic-based do MetaTrader 5 ainda funcione. Vamos usar uma versão modificada do método de Lagrange, que subtrai uma penalidade do único objetivo de otimização. Para esse propósito, definiremos novas funções de penalidade que medem a quantidade de violação para cada restrição:
Como você pode verificar, P_i(x) é positiva quando g_i(x) é violada e zero caso contrário. Se U_i ou L_i forem zero, substituímos por 1 para evitar divisão por zero.
No método do multiplicador de Lagrange, há um multiplicador por restrição, e seus valores são a solução de um sistema de equações. Na nossa versão modificada, assumimos que todos os multiplicadores são iguais e têm um valor constante k_p (veja a fórmula abaixo). Essa simplificação funciona aqui porque o algoritmo de otimização não precisa calcular gradientes de nenhuma função no problema de otimização.A função objetivo final F(x) é
Nota: o símbolo “:=” significa atribuição, não definição matemática.
O multiplicador k_p desempenha o papel dos multiplicadores de Lagrange e é usado para forçar projetos inviáveis (aqueles que violam pelo menos uma restrição) a ter um objetivo muito baixo. Essa redução do valor do objetivo fará com que o algoritmo genético no MetaTrader 5 classifique esse projeto muito baixo, tornando-o improvável de ser usado para reprodução na próxima geração de projetos.
O multiplicador k_o não faz parte do método do multiplicador de Lagrange. Ele é usado para aumentar a função objetivo para projetos viáveis, expandindo o lado positivo do eixo Y nos gráficos de otimização no terminal MetaTrader 5.
O usuário pode alterar os valores de K_o e K_p na seção de Miscellaneous (Diversos) de entrada. Recomendamos que os valores de k_o e K_p sejam potências de 10 (por exemplo, 10, 100, 1000, etc.).
Captura de tela da aba Inputs do terminal MetaTrader 5
Existem três seções de entrada no GOF: funções objetivo, funções de restrição e diversos.
Abaixo está a seção de Funções Objetivo. Podem ser incluídos até 5 objetivos na formulação do problema, de um total de 18 funções possíveis que serão mostradas posteriormente. Adicionar mais de 5 objetivos pode ser feito seguindo o código, mas qualquer coisa além de 3 objetivos torna a seleção de pesos e alvos mais difícil. Adicionar mais de 18 funções possíveis pode ser feito seguindo o código.
Abaixo está a seção de função de restrição rígida. Podem ser adicionadas até 10 restrições à formulação de otimização, de um total de 14 funções de restrição implementadas no GOF. Adicionar mais de 10 restrições pode ser feito seguindo o código. Adicionar novas restrições à lista de 14 também pode ser feito seguindo o código.
Abaixo está a Seção de Parâmetros de Otimização Diversos. Mais sobre essa seção será discutido nos próximos parágrafos.
Usando GenericOptimizationFormulation.mqh no seu EA
Se você é uma pessoa que tem pouca paciência para ler todo o artigo, primeiro fornecemos os passos para usar este código no seu EA para que você possa experimentá-lo primeiro e, em seguida, ler o restante do artigo com mais percepção. Abaixo estão os comentários iniciais no arquivo GenericOptimizationFormulation.mqh:
/*
Para usar este arquivo no seu EA, você deve fazer o seguinte:
Edite o arquivo .mq5 do EA
Insira as duas linhas seguintes após as variáveis de entrada e antes de OnInit()
ulong gof_magic = um_valor_ulong;
#include <YOUR_INCLUDE_LOCATION\GenericOptimizationFormulation.mqh>
Salve e compile seu arquivo EA
Se você receber erros de compilador, certifique-se de que:
substituiu um_valor_ulong pela variável que contém o número mágico ou pelo valor numérico mágico
substituiu YOUR_INCLUDE_LOCATION pelo nome da pasta onde seus arquivos de inclusão estão
*/
Esperamos que a inserção acima seja autoexplicativa. Mostraremos um exemplo usando o EA Moving Average do MetaTrader 5 mais tarde. Agora, vamos continuar com a explicação do código-fonte.
O Código-Fonte: GenericOptimizationFormulation.mqh
Agora que apresentamos as fórmulas para a função objetivo e restrições, discutimos a implementação em mql5 com trechos do código.
Solicitações aos leitores:
- Diz-se que um software com mais de sete linhas de código tem uma probabilidade não nula de ter um bug. O GOF tem mais de mil linhas, então a probabilidade é definitivamente não nula. Incentivamos o leitor a fornecer feedback nos comentários para melhorar o código.
- Se você encontrar uma maneira melhor de escrever o código, estamos ansiosos para ver suas melhores linhas de código para melhorar o GOF.
- Adicione e melhore o código com suas próprias funções objetivo e restrições. Por favor, compartilhe-as nos comentários também.
Primeiro, precisamos incluir algumas bibliotecas para fazer algumas estatísticas e álgebra:
#include <Math\Stat\Lognormal.mqh> #include <Math\Stat\Uniform.mqh> #include <Math\Alglib\alglib.mqh>
Funções Objetivo para escolher:
enum gof_FunctionDefs { // functions to build objective MAX_NONE=0, // 0] None MAX_AnnRetPct, // 1] Annual Return % MAX_Balance, // 2] Balance MAX_NetProfit, // 3] Net Profit MAX_SharpeRatio, // 4] Sharpe Ratio MAX_ExpPayOff, // 5] Expected Payoff MAX_RecovFact, // 6] Recovery Factor MAX_ProfFact, // 7] Profit Factor MAX_LRcrr3, // 8] LRcrr^3 MAX_NbrTradesPerWeek, // 9] #Trades/week MAX_WinRatePct, // 10] Win Rate % MAX_Rew2RiskRatio, // 11] Reward/Risk(RRR=AvgWin/AvgLoss) MAX_OneOverLRstd, // 12] 1/(LR std%) MAX_OneHoverWorstTradePct, // 13] 100/(1+|WorstLoss/Init.Dep*100|) MAX_LR, // 14] LRslope*LRcorr/LRstd MAX_OneHoverEqtyMaxDDpct, // 15] 100/(1+EqtyMaxDD%)) MAX_StratEfficiency, // 16] Seff=Profit/(TotalTrades*AvgLot) MAX_KellyCrit, // 17] Kelly Criterion MAX_OneOverRoRApct // 18] 1/Max(0.01,RoRA %) };
Existem 18 objetivos possíveis para escolher um máximo de 5 para formular o problema de otimização. O usuário pode adicionar mais funções ao código-fonte seguindo o mesmo padrão de implementação. Os nomes das funções objetivo são destinados a serem autoexplicativos, exceto por algumas mencionadas abaixo:
- LRcrr^3: o coeficiente de correlação de regressão linear elevado à potência de 3.
- 1/(LR std%): LR std é o desvio padrão da regressão linear. O inverso mede quão próxima a linha de patrimônio está de uma linha reta.
- 100/(1+|WorstLoss/Init.Dep*100|): a pior perda dividida pelo depósito inicial é uma medida de desempenho ruim. O inverso disso é uma medida de bom desempenho.
- LRslope*LRcorr/LRstd: este é um objetivo multiplicativo de três funções da regressão linear: a inclinação, o coeficiente de correlação e o desvio padrão.
- Seff=Profit/(TotalTrades*AvgLot): é uma medida de eficiência da estratégia. Preferimos estratégias com alto lucro, pequeno número de negociações com tamanho de lote pequeno.
- 1/Max(0.01,RoRA %): RoRA é o risco de ruína da sua conta. Isso é calculado usando uma simulação de Monte Carlo que discutiremos mais tarde.
Restrições Rígidas para escolher:
enum gof_HardConstrains { hc_NONE=0, // 0] None hc_MaxAccountLoss_pct, // 1] Account Loss % InitDep hc_maxAllowed_DDpct, // 2] Equity DrawDown % hc_maxAllowednbrConLossTrades, // 3] Consecutive losing trades hc_minAllowedWin_pct, // 4] Win Rate % hc_minAllowedNbrTradesPerWeek, // 5] # trades/week hc_minAllowedRecovFactor, // 6] Recov Factor hc_minAllowedRRRFactor, // 7] Reward/Risk ratio hc_minAllowedAnnualReturn_pct, // 8] Annual Return in % hc_minAllowedProfFactor, // 9] Profit Factor hc_minAllowedSharpeFactor, // 10] Sharpe Factor hc_minAllowedExpPayOff, // 11] Expected PayOff hc_minAllowedMarginLevel, // 12] Smallest Margin Level hc_maxAllowedTradeLoss, // 13] Max Loss trade hc_maxAllowedRoRApct // 14] Risk of Ruin(%) }; enum gof_HardConstType { hc_GT=0, // >= Greater or equal to hc_LT // <= Less or equal to };Existem 14 restrições possíveis para escolher um máximo de 10 para formular o problema de otimização. O usuário pode adicionar mais restrições ao código-fonte seguindo o mesmo padrão de implementação. Os nomes das funções de restrição são destinados a serem autoexplicativos. Existem dois tipos de restrições, hc_GT para restrições com limite inferior e ht_LT para restrições com limite superior. Mais sobre isso quando mostrarmos como usá-las.
Opções de Risco de Ruína
enum gof_RoRaCapital { roraCustomPct=0, // Custom % of Ini.Dep. roraCustomAmount, // Custom Capital amount roraIniDep // Initial deposit };
Ao calcular o risco de ruína da sua conta, há três maneiras de definir o dinheiro da "conta". A primeira opção é como uma porcentagem do depósito inicial. A segunda opção é um valor de capital fixo dado na moeda da sua conta. A terceira é um caso especial da primeira opção, se a porcentagem for 100%. O risco de ruína é explicado mais adiante com o código.
Decimais da função objetivo
Como mencionado anteriormente, podemos optar por exibir informações adicionais da simulação usando os dois decimais na coluna de resultados. Aqui estão as opções:
enum gof_objFuncDecimals { fr_winRate=0, // WinRate % fr_MCRoRA, // MonteCarlo Sim Risk of Ruin Account % fr_LRcorr, // LR correlation fr_ConLoss, // Max # Consecutive losing Trades fr_NONE // None };
fr_winRate é a taxa de vitória da simulação em porcentagem. Por exemplo, se a taxa de vitória for 34%, o resultado do objetivo será 0,39. Se a taxa de vitória for 100%, será exibido 0,99.
fr_MCRoRA é o risco de ruína da conta em porcentagem. Por exemplo, se o risco de ruína da conta for 11%, o resultado do objetivo será 0,11.
fr_LRcorr é o coeficiente de correlação da regressão linear. Por exemplo, se o coeficiente for 0,88, o resultado do objetivo será 0,88.
fr_ConLoss é o maior número de negociações consecutivas perdedoras. Por exemplo, se o número for 7, o resultado do objetivo será 0,07. Se o número for superior a 99, será exibido 0,99.
fr_NONE é usado quando você não deseja ver nenhuma informação nos decimais.
Funções Objetivo
A próxima seção no código é a seleção de funções objetivo individuais (máximo de 5). Abaixo está um trecho de apenas a primeira função, juntamente com seu alvo e peso.
input group "- Build Custom Objective to Maximize:" sinput gof_FunctionDefs gof_Func1 = MAX_AnnRetPct; // Select Objective Function to Maximize 1: sinput double gof_Target1 = 200; // Target 1 sinput double gof_Weight1 = 1; // Weight 1
Funções de Restrição
input group "- Hard Constraints:" sinput bool gof_IncludeHardConstraints = true;//if false, all constraints are ignored sinput gof_HardConstrains gof_HC_1=hc_minAllowedAnnualReturn_pct; // Select Constraint Function 1: sinput gof_HardConstType gof_HCType_1=hc_GT; // Type 1 sinput double gof_HCBound_1=50; // Bound Value 1
Há uma opção para desativar todas as restrições rígidas definindo gof_IncludeHardConstraints=false. Em seguida, há a seleção da primeira restrição, seu tipo e seu valor limite. Todas as dez restrições usam o mesmo formato.
Parâmetros de Otimização Diversos
input group "------ Misc Optimization Params -----" sinput gof_objFuncDecimals gof_fr = fr_winRate; // Choose Result-column's decimals sinput gof_RoRaCapital gof_roraMaxCap = roraCustomPct; // Choose capital method for Risk of Ruin sinput double gof_RoraCustomValue = 10; // Custom Value for Risk of Ruin (if needed) sinput bool gof_drawSummary = false; // Draw summary on chart sinput bool gof_printSummary = true; // Print summary on journal sinput bool gof_discardLargestProfit = false; // Subtract Largest Profit from Netprofit sinput bool gof_discardLargestLoss = false; // Add Largest Loss to Net profit sinput double gof_PenaltyMultiplier = 100; // Multiplier for Penalties (k_p) sinput double gof_ObjMultiplier = 100; // Multiplier for Objectives (k_o)
Na seção acima, o usuário escolherá:
- gof_fr: A quantidade a ser exibida como decimais na coluna de Resultados.
- gof_roraMaxCap: O método para calcular o capital RoRA.
- gof_RoraCustomValue: O valor do capital ou % do depósito inicial para RoRA. Isso depende da sua seleção na linha anterior. Isso depende da sua seleção na linha anterior.
- gof_drawSummary: Você pode escolher desenhar o resumo do relatório GOF no gráfico.
- gof_printSummary: Você pode escolher imprimir o resumo do relatório GOF na Aba de Jornal.
- gof_discardLargestProfit: Você pode subtrair o maior lucro do lucro líquido para desencorajar estratégias que favorecem um único grande ganho.
- gof_discardLargestLoss: Você pode adicionar a maior perda ao lucro líquido para desencorajar estratégias que têm uma grande perda.
- gof_PenaltyMultiplier: o multiplicador "K_p" mostrado anteriormente na definição da função objetivo.
- gof_ObjMultiplier: o multiplicador "K_o" mostrado anteriormente na definição da função objetivo.
Os valores padrão na seção de diversos devem funcionar bem.
As próximas linhas do código são para definir variáveis e buscar valores da função MetaTrader 5 TesterStatistics(). Depois disso, vem a seção principal do GOF:
//------------ GOF ---------------------- // Printing and displaying results from the simulation GOFsummaryReport(); // calculate the single objective function double SingleObjective = calcObjFunc(); // calculate the total penalty from constraint violations if(gof_IncludeHardConstraints) gof_constraintTotalPenalty=calcContraintTotalPenalty(gof_displayContraintFlag); // Compute customMaxCriterion // gof_PenaltyMultiplier pushes infeasible designs to have low objective values // gof_PenaltyMultiplier expand the positive side of the Y axis double customMaxCriterion=gof_constraintTotalPenalty>0? SingleObjective-gof_PenaltyMultiplier*gof_constraintTotalPenalty: gof_ObjMultiplier*SingleObjective; // add additional simulation result as two decimal digits in the result column customMaxCriterion=AddDecimalsToCustomMax(customMaxCriterion); // Printing and displaying more results from GOF FinishGOFsummaryReport(customMaxCriterion); return (NormalizeDouble(customMaxCriterion,2));
O código acima mostra:
- GOFsummaryReport() para preparar o relatório resumido do GOF que vai na aba Jornal e no gráfico.
- calcObjFunc() para calcular a função objetivo única combinada.
- calcContraintTotalPenalty() para calcular a penalidade total devido a violações de restrições.
- customMaxCriterion é então calculado como mostrado na Introdução, como a soma da função objetivo única menos a penalidade total das violações de restrições.
- AddDecimalsToCustomMax() é usado para adicionar as informações nos decimais de customMaxCriterion.
- FinishGOFsummaryReport() é para finalizar e imprimir o Relatório Resumido do GOF.
O restante do código é uma implementação direta das fórmulas apresentadas na introdução. A única parte que vale a pena discutir é o cálculo do risco de ruína.
Risco de Ruína usando Simulações de Monte Carlo
O risco de ruína poderia ser calculado com uma fórmula simples, mas escolhemos usar uma simulação de Monte Carlo em vez disso, porque a fórmula simples não estava dando resultados sensíveis. Para a abordagem de Monte Carlo, precisamos da média de ganhos e perdas, os desvios padrão desses ganhos e perdas, a taxa de vitória e o número de negociações na simulação. Além disso, precisamos fornecer o capital que define a ruína da conta.
double MonteCarlo_RiskOfRuinAccount(double WinRatePct, double AvgWin, double AvgLoss, double limitLoss_money, int nTrades) { // 10000 Montecarlo simulations, each with at least 100 trades. // Ideally, if we had lots of trades in the history, we could use a Markov Chain transfer probability matrix // we are limiting the statistics to mean & stdev, without knowledge of a transfer probability information double posDealsMean,posDealsStd,negDealsMean,negDealsStd; CalcDealStatistics(gof_dealsEquity, posDealsMean,posDealsStd,negDealsMean,negDealsStd); // seeding the random number generator MathSrand((int)TimeLocal()+1); // ignore posDealsMean and negDealsMean. Use AvgWin and AvgLoss instead AvgLoss=MathAbs(AvgLoss); WinRatePct=MathMin(100,MathMax(0,WinRatePct)); // case when win rate is 100%: if((int)(WinRatePct*nTrades/100)>=nTrades) { WinRatePct=99; // just to be a bit conservative if winrate=100% AvgLoss=AvgWin/2; // a guessengineering value negDealsStd=posDealsStd;// a guessengineering value } // Use log-normal distribution function. Mean and Std are estimated as: double win_lnMean =log(AvgWin*AvgWin/sqrt(AvgWin*AvgWin+posDealsStd*posDealsStd)); double loss_lnMean=log(AvgLoss*AvgLoss/sqrt(AvgLoss*AvgLoss+negDealsStd*negDealsStd)); double win_lnstd =sqrt(log(1+(posDealsStd*posDealsStd)/(AvgWin*AvgWin))); double loss_lnstd =sqrt(log(1+(negDealsStd*negDealsStd)/(AvgLoss*AvgLoss))); double rand_Win[],rand_Loss[]; double r[]; // limit amount of money that defines Ruin limitLoss_money=MathAbs(limitLoss_money); bool success; int ruinCount=0; // counter of ruins int successfulMCcounter=0; int nTradesPerSim=MathMax(100,nTrades);// at least 100 trades per sim int nMCsims=10000; // MC sims, each one with nTradesPerSim for(int iMC=0; iMC<nMCsims; iMC++) { success=MathRandomUniform(0,1,nTradesPerSim,r); // generate nTradesPerSim wins and losses for each simulation // use LogNormal distribution success&=MathQuantileLognormal(r,win_lnMean,win_lnstd,true,false,rand_Win); success&=MathQuantileLognormal(r,loss_lnMean,loss_lnstd,true,false,rand_Loss); if(!success)continue; successfulMCcounter++; //simulate nTradesPerSim double eqty=0; // start each simulation with zero equity for(int i=0; i<nTradesPerSim; i++) { // draw a random number in [0,1] double randNumber=(double)MathRand()/32767.; // select a win or a loss depending on the win rate and the random number // and add to the equity eqty+=randNumber*100 < WinRatePct? rand_Win[i]: -rand_Loss[i]; // check if equity is below the limit (ruin) // count the number of times there is a ruin if(eqty<= -limitLoss_money) { ruinCount++; break; } } } // compute risk of ruin as percentage double RiskOfRuinPct=(double)(ruinCount)/successfulMCcounter*100.; return(RiskOfRuinPct); }
Inserimos muitas linhas de comentários no código acima para facilitar o entendimento. A função CalcDealStatistics(), também incluída no código-fonte, é onde são calculados os desvios padrão dos ganhos e perdas. A principal suposição no cálculo do Risco de Ruína é que o histórico de negociações segue uma distribuição Log-Normal para garantir que as amostras da distribuição sejam valores positivos e negativos para ganhos e perdas, respectivamente.
Idealmente, se tivéssemos muitas negociações no histórico, poderíamos usar uma matriz de probabilidade de transição de Cadeia de Markov em vez de assumir a "log-normalidade" do histórico de negociações. Como os históricos de negociações têm cerca de algumas centenas de negociações (no máximo), não há informações suficientes para construir a matriz de probabilidade de transição de Cadeia de Markov com boa precisão.
Interpretando o Risco de Ruína de Monte Carlo
Um resultado de simulação de Monte Carlo precisa ser interpretado como uma probabilidade do risco de perder o capital, não como um valor preditivo. Por exemplo, se o valor de retorno da simulação de Monte Carlo for 1%, isso significa que há 1% de chance (probabilidade) de que sua estratégia de negociação elimine todo o capital em risco. Isso não significa que você perderá 1% do capital em risco.
Adicionando Decimais ao Máximo Personalizado
Isso é algo complicado de fazer. Se o leitor encontrar uma maneira melhor, por favor, compartilhe. Uma vez que os valores decimais são calculados (denominados dec no código), o objetivo (obj) é modificado da seguinte forma:
obj=obj>0? MathFloor(obj*gof_ObjPositiveScalefactor)+dec/100.: -(MathFloor(-obj*gof_ObjNegativeScalefactor)+dec/100.);
Como você pode ver, se obj é um valor positivo, ele é multiplicado por um grande número (gof_ObjPositiveScalefactor=1e6), truncado, e então o valor "dec" é dividido por 100 e adicionado como decimal. Quando o valor obj é negativo (implicando que há muitas restrições violadas), o obj é multiplicado por um número diferente (gof_ObjPositiveScalefactor = 1e3) para comprimir o eixo vertical para valores negativos.
Implementando GOF no MetaTrader 5 Moving Average EA
Aqui está um exemplo para mostrar como implementar o GOF no MetaTrader 5 Moving Averages.mq5 expert advisor:
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift //--- int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; #define MA_MAGIC 1234501 // changes needed to use the GenericOptimizationFormulation (GOF): ulong gof_magic= MA_MAGIC; #include <GenericOptimizationFormulation.mqh> // end of changes
É isso! Apenas duas linhas precisam ser adicionadas ao arquivo EA.
Exemplos de Otimização usando GOF
Exemplo 1: Um objetivo, sem restrições (como se estivéssemos usando o MetaTrader 5 Max Balance na aba de Configurações
A aba de entrada está abaixo. Como há apenas um objetivo, o alvo e o peso não são relevantes. Observe como todas as restrições estão desativadas com a primeira variável booleana na seção de Restrições Rígidas:
As seções diversas são:
Resultados são mostrados abaixo. Veja como a coluna "Resultado" não é o saldo da conta, mas o objetivo modificado com a taxa de acertos em decimais.
Podemos ver que a melhor combinação de variáveis (9,9,3) produz um lucro de 939,81 e uma taxa de acertos de 41%.
Simulamos a combinação ideal (primeira linha) na tabela. O Relatório Resumido GOF impresso na aba Jornal é:
Vamos revisar o relatório resumido GOF em mais detalhes no próximo exemplo.
Exemplo 2: Três objetivos e cinco restrições
A aba de configurações é a mesma de antes. As variáveis de entrada e intervalos também são as mesmas. A aba de entrada para as variáveis GOF é mostrada abaixo.
Objetivos:
- Retorno anual com meta de 50% e peso de 100
- O fator de recuperação com meta de 10 e peso de 10
- A porcentagem da taxa de vitória com meta de 100% e peso 10
Restrições:
- Perda da conta como % do depósito inicial para ser menor ou igual a 10%
- Drawdown de capital % para ser menor ou igual a 10%
- Número de negociações perdedoras consecutivas para ser menor ou igual a 5
- Taxa de vitória para ser maior ou igual a 35%
- Risco de ruína da conta para ser menor ou igual a 0,5%
O capital para calcular o Risco de Ruína foi definido como 10% do Depósito Inicial, ou 1000 unidades da moeda da conta, conforme mostrado na seção Diversos acima.
O melhor design acabou sendo (10,6,6) como você pode ver na primeira linha da tabela de otimização.
Observe que o otimizador encontrou uma solução com lucro menor em comparação com o primeiro exemplo (805,64 vs 839,81), mas este novo design satisfaz todas as cinco restrições e maximiza três objetivos combinados.
O Relatório Resumido do GOF
Ao simular a primeira linha na tabela de otimização acima, obtemos o relatório resumido do GOF abaixo:
Há três seções no Resumo do GOF. A primeira seção contém muitas quantidades da aba BackTest. Existem algumas quantidades adicionais que não estão presentes na aba BackTest: lucro anualizado, duração do teste em anos, razão recompensa/risco (RRR), volume médio e maior, desvios padrão de ganho e perda e nível de margem mínimo atingido durante a simulação.
A segunda seção é para as Funções Objetivo. Aqui há quatro valores para cada objetivo: o valor do objetivo, o alvo, o peso e a porcentagem de contribuição. A porcentagem de contribuição é a contribuição dessa função objetivo para o objetivo único total. Neste exemplo, o retorno anual contribuiu com 95,1%, o fator de recuperação contribuiu com 1,4% e a taxa de vitória contribuiu com 3,5% para um total de 100% do objetivo único total. Alvos e Pesos afetam essas contribuições.
A terceira seção é para Restrições. Uma mensagem de "passou" ou "falhou" é impressa para cada restrição, e uma comparação entre o valor real da restrição e o limite de entrada também é mostrada.
Para fins de comparação, executamos o primeiro design do exemplo #1 (9, 9, 3) através da mesma formulação de otimização do exemplo 2. Abaixo está o resumo desta simulação. Observe como há uma violação de uma restrição. O número de perdas consecutivas é 6, que é maior que o valor limite de 5 dado na formulação da otimização. Portanto, mesmo que o design MaxBalance (9,9,3) tenha melhor lucro do que o design MultiObjective/Constrained (10,6,6), é o design (10,6,6) que satisfaz todas as restrições.
Recomendações ao usar GenericOptimizationFormulation.mqh
A maior liberdade para escolher múltiplos objetivos e múltiplas restrições deve ser exercida com cuidado. Aqui estão algumas recomendações gerais:
- Use no máximo 3 objetivos. O código permite até 5 objetivos, mas a seleção de alvos e pesos, que afetam o resultado final, torna-se mais difícil à medida que o número de objetivos é superior a 3.
- Use restrições baseadas em suas preferências e não as defina muito apertadas ao selecionar limites superiores (U_i) e inferiores (L_i). Se seus limites forem muito apertados, você não obterá nenhuma combinação viável de variáveis de entrada.
- Se você não souber qual valor limite dar para uma determinada restrição, poderá mover a restrição para a seção de objetivos e ver como ela se comporta (magnitude, sinal, etc.) inspecionando o relatório resumido do GOF.
- Ajuste k_o e k_p se quiser gráficos melhores ou se achar que o otimizador não está produzindo os resultados esperados.
- Lembre-se, o design ideal (a linha superior na tabela de Otimização) não é necessariamente o design mais lucrativo, mas o design com a função objetivo mais alta com base na sua seleção de objetivos individuais e restrições.
- Recomendamos que, após a otimização, você classifique os designs por outras colunas, como lucro, fator de recuperação, drawdown, expectativa de retorno, fator de lucro, etc. A linha superior em cada classificação pode ser uma candidata que você pode considerar para simular para revisar o relatório resumido do GOF.
- Uma boa seleção de objetivos e restrições foi mostrada no exemplo #2. Use-os como ponto inicial para sua experimentação.
Coisas que Deram Errado
Você pode configurar uma formulação de otimização que não esteja fornecendo o resultado esperado. Aqui estão alguns motivos pelos quais isso pode estar acontecendo:
- As restrições estão muito apertadas e o algoritmo de otimização genética do MetaTrader 5 levará muitas gerações para chegar a uma função objetivo positiva e, em alguns casos, pode não chegar lá. Solução: relaxe suas restrições.
- As restrições estão em conflito umas com as outras. Solução: verifique se as restrições são logicamente consistentes.
- Os gráficos na otimização têm um viés para os valores negativos no eixo y (ou seja, o lado negativo ocupa mais espaço que o lado positivo). Solução: aumente K_o ou diminua K_p, ou ambos.
- Alguns designs que você gosta não aparecem no topo da tabela de otimização. Lembre-se de que alvos e pesos afetam o objetivo de otimização e, além disso, uma única violação de restrição pode enviar o objetivo para baixo na tabela. Solução: reformule seu problema de otimização ajustando alvos, pesos e restrições.
Conclusão
Esperamos que esta Formulação de Otimização Genérica seja útil para você. Agora você tem a liberdade extra de escolher múltiplos objetivos e múltiplas restrições para configurar o problema de otimização que você desejar.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/14365
- 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