English Русский Deutsch 日本語
preview
Usando algoritmos de otimização para configurar parâmetros de EA em tempo real

Usando algoritmos de otimização para configurar parâmetros de EA em tempo real

MetaTrader 5Testador | 16 julho 2024, 09:04
16 0
Andrey Dik
Andrey Dik

Conteúdo

1. Introdução
[fuzzy]Introdução2. Arquitetura de um EA com auto-otimização
3. Virtualização de indicadores
4. Virtualização de estratégia
5. Funcionalidade de teste



1. Introdução

Frequentemente recebo perguntas sobre como aplicar algoritmos de otimização ao trabalhar com EAs e estratégias em geral. Neste artigo, gostaria de abordar os aspectos práticos do uso de algoritmos de otimização.

No mundo financeiro de hoje, onde cada milissegundo pode fazer uma grande diferença, a negociação algorítmica está se tornando cada vez mais necessária. Os algoritmos de otimização desempenham um papel crucial na criação de estratégias de negociação eficientes. Talvez alguns céticos acreditem que os algoritmos de otimização e a negociação não têm terreno comum. No entanto, neste artigo, mostrarei como essas duas áreas podem interagir e quais benefícios podem ser obtidos dessa interação.

Para traders novatos, entender os princípios básicos dos algoritmos de otimização pode ser uma ferramenta poderosa para encontrar negociações lucrativas e minimizar riscos. Para profissionais experientes, um conhecimento profundo nessa área pode abrir novos horizontes e ajudar a criar estratégias de negociação sofisticadas que superem as expectativas.

Auto-otimização em um EA é um processo que envolve o EA adaptando seus parâmetros de estratégia de negociação para alcançar um melhor desempenho com base em dados históricos e nas condições atuais do mercado. O processo pode incluir os seguintes aspectos:

  • Coleta e análise de dados. O EA deve coletar e analisar dados históricos do mercado. Isso pode envolver o uso de várias técnicas de análise de dados, como análise estatística, aprendizado de máquina e inteligência artificial.
  • Definição de metas. O EA deve ter metas claramente definidas que ele se esforça para alcançar. Isso pode ser maximizar o lucro, minimizar o risco ou atingir um certo nível de rentabilidade.
  • Aplicação de algoritmos de otimização. Para alcançar os melhores resultados, um EA pode usar vários algoritmos de otimização. Esses algoritmos ajudam o EA a encontrar os valores ideais dos parâmetros da estratégia.
  • Teste e validação. Após a otimização, o EA deve ser testado e validado em relação às condições atuais do mercado para garantir sua eficiência. Os testes ajudam a avaliar o desempenho do EA e sua capacidade de se adaptar às mudanças nas condições do mercado.
  • Monitoramento e atualização. O EA deve monitorar constantemente seu desempenho e atualizar seus parâmetros de estratégia, se necessário. Os mercados estão em constante mudança, e o EA deve estar disposto a se adaptar a novas condições e mudanças nas tendências e volatilidade.

Existem vários cenários principais para o uso de algoritmos de otimização na negociação:

  • Otimização dos parâmetros da estratégia de negociação. Algoritmos de otimização podem ser usados para ajustar os parâmetros das estratégias de negociação. Usando esses métodos, podemos determinar os melhores valores para parâmetros como períodos de médias móveis, níveis de stop loss e take profit, ou outros parâmetros associados a sinais e regras de negociação.
  • Otimização do tempo de entrada/saída do mercado. Algoritmos de otimização podem ajudar a determinar os melhores momentos para entrar e sair de posições com base em dados históricos e nas condições atuais do mercado. Por exemplo, algoritmos de otimização podem ser usados para determinar intervalos de tempo ideais para sinais de negociação.
  • Gestão de portfólio. Algoritmos de otimização podem ajudar a determinar a alocação ideal de ativos em um portfólio para atingir metas específicas. Por exemplo, podemos usar técnicas de otimização, como a Otimização Média-Variância, para encontrar a combinação mais eficiente de ativos considerando o retorno esperado e o risco. Isso pode incluir a determinação da mistura ideal entre ações, títulos e outros ativos, bem como a otimização do tamanho das posições e da diversificação do portfólio.
  • Desenvolvimento de estratégias de negociação. Algoritmos de otimização podem ser usados para desenvolver novas estratégias de negociação. Por exemplo, podemos usar programação genética para buscar evolutivamente regras ótimas para entrar e sair de posições com base em dados históricos.
  • Gestão de risco. Algoritmos de otimização podem ajudar a gerenciar o risco na negociação. Por exemplo, podemos usar algoritmos de otimização para calcular o tamanho ideal da posição ou determinar um nível de stop loss dinâmico que minimize as perdas potenciais.
  • Seleção dos melhores instrumentos de negociação. Algoritmos de otimização podem ajudar na escolha dos melhores instrumentos ou ativos para negociação. Por exemplo, algoritmos de otimização podem ser usados para classificar ativos com base em vários critérios, como rentabilidade, volatilidade ou liquidez.
  • Previsão dos mercados financeiros. Algoritmos de otimização podem ser usados para prever os mercados financeiros. Algoritmos de otimização podem ser usados para ajustar os parâmetros dos modelos preditivos ou para selecionar combinações ótimas de modelos preditivos.

Estes são apenas alguns exemplos de cenários para o uso de algoritmos de otimização na negociação. No geral, os algoritmos de otimização podem ajudar a automatizar e melhorar vários aspectos da negociação, desde a busca de estratégias ideais até a gestão de risco e portfólio.


2. Arquitetura de um EA com auto-otimização

Para garantir a auto-otimização do EA, várias esquemas são possíveis, mas uma das mais simples e minimamente necessária para implementar quaisquer capacidades e funcionalidades exigidas é a apresentada na Figura 1.

Na linha do tempo "História", o EA está no ponto "tempo agora", onde a decisão de otimização é feita. O "EA" chama a "Função de Gerente", que gerencia o processo de otimização. O EA passa os "parâmetros de otimização" para esta função.

Por sua vez, o gerente solicita um conjunto de parâmetros do "algoritmo de otimização" ou "AO", que agora e daqui em diante será referido como "conjunto". Depois disso, o gerente transfere o conjunto para a estratégia de negociação virtual "EA Virt", que é um análogo completo da estratégia real que funciona e realiza operações de negociação, "EA".

"EA Virt" realiza negociações virtuais do ponto "passado" na história até o ponto "tempo agora". O gerente executa "EA Virt" tantas vezes quanto especificado no tamanho da população dos "parâmetros de otimização". "EA Virt", por sua vez, retorna o resultado da execução na história na forma de "resultado ff".

"resultado ff" é o resultado de uma função de aptidão, ou aptidão, ou um critério de otimização, que pode ser qualquer coisa à discrição do usuário. Isso pode ser, por exemplo, um saldo, fator de lucro, expectativa matemática, ou um critério complexo, bem como um integral ou diferencial cumulativo, medido em muitos pontos na linha do tempo "História". Assim, o resultado da função de aptidão, ou "resultado ff", é o que o usuário considera um indicador importante da qualidade da estratégia de negociação.

Em seguida, "resultado ff", que é uma avaliação de um conjunto específico, é passado pelo gerente ao algoritmo de otimização.

Quando a condição de parada é atingida, o gerente transfere o melhor conjunto para o "EA", após o qual o EA continua trabalhando (negociando) com novos parâmetros atualizados do ponto "tempo agora" até o ponto de "reotimização", onde é otimizado novamente até uma profundidade de história dada.

O ponto de reotimização pode ser escolhido por vários motivos. Pode ser um número estritamente definido de barras históricas, como no exemplo abaixo, ou alguma condição específica, por exemplo, uma diminuição nos indicadores de negociação até um certo nível crítico.

Esquema

Figura 1. Estrutura de auto-otimização do EA

De acordo com o esquema do algoritmo de otimização "algoritmo de otimização ALGO", pode ser considerado como uma "caixa preta" que realiza seu trabalho de forma autônoma (no entanto, tudo fora também é uma "caixa preta" para ele), independentemente da estratégia de negociação específica, gerente e estratégia virtual. O gerente solicita um conjunto do algoritmo de otimização e envia de volta uma avaliação desse conjunto. Essa avaliação é usada pelo algoritmo de otimização para determinar o próximo conjunto. Esse ciclo continua até que o melhor conjunto de parâmetros seja encontrado para atender aos requisitos do usuário. Assim, o algoritmo de otimização busca os parâmetros ideais - precisamente aqueles que satisfazem as necessidades do usuário especificadas através da função de aptidão em "EA Virt".


3. Virtualização de indicadores

Para executar o EA em dados históricos, precisamos criar uma cópia virtual de uma estratégia de negociação que realizará as mesmas operações de negociação como quando trabalhando em uma conta de negociação. Quando os indicadores não são usados, a virtualização das condições lógicas dentro do EA se torna relativamente simples. Só precisamos descrever as ações lógicas de acordo com um ponto no tempo na série de preços. Ao mesmo tempo, o uso de indicadores é uma tarefa mais complexa e, na maioria das vezes, as estratégias de negociação dependem de vários indicadores.

O problema é que, ao procurar parâmetros ótimos de indicadores, é necessário criar manipuladores de indicadores com o conjunto atual em uma determinada iteração. Após a conclusão da execução em dados históricos, esses manipuladores devem ser excluídos, caso contrário, a RAM pode se encher rapidamente, especialmente se houver um grande número de opções possíveis para um conjunto de parâmetros (conjuntos). Isso não é um problema se esse procedimento for realizado em um gráfico de símbolo, mas a exclusão de manipuladores não é permitida no testador.

Para resolver o problema, precisaremos "virtualizar" o cálculo do indicador dentro do EA executável para evitar o uso de manipuladores. Vamos tomar o indicador Estocástico como exemplo.

A parte de cálculo de cada indicador contém a função padrão OnCalculate. Essa função deve ser renomeada, por exemplo, para "Calcular", e deixada praticamente inalterada. 

Precisamos projetar o indicador como uma classe (uma estrutura também é adequada). Vamos chamá-lo de "C_Stochastic". Na declaração da classe, precisamos registrar os buffers principais do indicador como campos públicos (buffers de cálculo adicionais podem ser privados) e declarar a função de inicialização Init, que precisa passar os parâmetros do indicador para ela.

//——————————————————————————————————————————————————————————————————————————————
class C_iStochastic
{
  public: void Init (const int InpKPeriod,       // K period
                     const int InpDPeriod,       // D period
                     const int InpSlowing)       // Slowing
  {
    inpKPeriod = InpKPeriod;
    inpDPeriod = InpDPeriod;
    inpSlowing = InpSlowing;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &high  [],
                         const double &low   [],
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMainBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtHighesBuffer [];
  private: double ExtLowesBuffer  [];

  private: int inpKPeriod; // K period
  private: int inpDPeriod; // D period
  private: int inpSlowing; // Slowing
};
//——————————————————————————————————————————————————————————————————————————————

Assim como o cálculo do próprio indicador no método "Calcular". O cálculo do indicador não difere em nada do indicador incluído na entrega padrão do terminal. A única diferença é a distribuição de tamanho dos buffers de indicador e sua inicialização

Este é um exemplo muito simples para entender o princípio da virtualização do indicador. O cálculo é realizado para toda a profundidade dos períodos especificados nos parâmetros do indicador. É possível organizar a capacidade de calcular adicionalmente apenas a última barra e implementar buffers circulares, mas o objetivo do artigo é mostrar um exemplo simples que requer intervenção mínima na conversão do indicador em uma forma virtual e é acessível a usuários com habilidades mínimas de programação.

//——————————————————————————————————————————————————————————————————————————————
int C_iStochastic::Calculate (const int rates_total,
                              const int prev_calculated,
                              const double &high  [],
                              const double &low   [],
                              const double &close [])
{
  if (rates_total <= inpKPeriod + inpDPeriod + inpSlowing) return (0);

  ArrayResize (ExtHighesBuffer, rates_total);
  ArrayResize (ExtLowesBuffer,  rates_total);
  ArrayResize (ExtMainBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);

  ArrayInitialize (ExtHighesBuffer, 0.0);
  ArrayInitialize (ExtLowesBuffer,  0.0);
  ArrayInitialize (ExtMainBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);

  int i, k, start;

  start = inpKPeriod - 1;

  if (start + 1 < prev_calculated)
  {
    start = prev_calculated - 2;
    Print ("start ", start);
  }
  else
  {
    for (i = 0; i < start; i++)
    {
      ExtLowesBuffer  [i] = 0.0;
      ExtHighesBuffer [i] = 0.0;
    }
  }

  //--- calculate HighesBuffer[] and ExtHighesBuffer[]
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double dmin =  1000000.0;
    double dmax = -1000000.0;

    for (k = i - inpKPeriod + 1; k <= i; k++)
    {
      if (dmin > low  [k]) dmin = low  [k];
      if (dmax < high [k]) dmax = high [k];
    }

    ExtLowesBuffer  [i] = dmin;
    ExtHighesBuffer [i] = dmax;
  }

  //--- %K
  start = inpKPeriod - 1 + inpSlowing - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtMainBuffer [i] = 0.0;
  }

  //--- main cycle
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum_low  = 0.0;
    double sum_high = 0.0;

    for (k = (i - inpSlowing + 1); k <= i; k++)
    {
      sum_low  += (close [k] - ExtLowesBuffer [k]);
      sum_high += (ExtHighesBuffer [k] - ExtLowesBuffer [k]);
    }

    if (sum_high == 0.0) ExtMainBuffer [i] = 100.0;
    else                 ExtMainBuffer [i] = sum_low / sum_high * 100;
  }

  //--- signal
  start = inpDPeriod - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtSignalBuffer [i] = 0.0;
  }

  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum = 0.0;
    for (k = 0; k < inpDPeriod; k++) sum += ExtMainBuffer [i - k];
    ExtSignalBuffer [i] = sum / inpDPeriod;
  }

  //--- OnCalculate done. Return new prev_calculated.
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

Além disso, darei um exemplo de virtualização do indicador MACD:

//——————————————————————————————————————————————————————————————————————————————
class C_iMACD
{
  public: void Init (const int InpFastEMA,       // Fast   EMA period
                     const int InpSlowEMA,       // Slow   EMA period
                     const int InpSignalSMA)     // Signal SMA period
  {
    inpFastEMA   = InpFastEMA;
    inpSlowEMA   = InpSlowEMA;
    inpSignalSMA = InpSignalSMA;

    maxPeriod = InpFastEMA;
    if (maxPeriod < InpSlowEMA)   maxPeriod = InpSlowEMA;
    if (maxPeriod < InpSignalSMA) maxPeriod = InpSignalSMA;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMacdBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtFastMaBuffer [];
  private: double ExtSlowMaBuffer [];

  private: int ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);
  private: int SimpleMAOnBuffer      (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);

  private: int inpFastEMA;   // Fast EMA period
  private: int inpSlowEMA;   // Slow EMA period
  private: int inpSignalSMA; // Signal SMA period
  private: int maxPeriod;
};
//——————————————————————————————————————————————————————————————————————————————

Parte especificada do indicador:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::Calculate (const int rates_total,
                        const int prev_calculated,
                        const double &close [])
{
  if (rates_total < maxPeriod) return (0);

  ArrayResize (ExtMacdBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);
  ArrayResize (ExtFastMaBuffer, rates_total);
  ArrayResize (ExtSlowMaBuffer, rates_total);

  ArrayInitialize (ExtMacdBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);
  ArrayInitialize (ExtFastMaBuffer, 0.0);
  ArrayInitialize (ExtSlowMaBuffer, 0.0);

  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpFastEMA, close, ExtFastMaBuffer);
  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpSlowEMA, close, ExtSlowMaBuffer);

  int start;
  if (prev_calculated == 0) start = 0;

  else start = prev_calculated - 1;

  //--- calculate MACD
  for (int i = start; i < rates_total && !IsStopped (); i++) ExtMacdBuffer [i] = ExtFastMaBuffer [i] - ExtSlowMaBuffer [i];

  //--- calculate Signal
  SimpleMAOnBuffer (rates_total, prev_calculated, 0, inpSignalSMA, ExtMacdBuffer, ExtSignalBuffer);

  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

O cálculo da suavização exponencial não precisa ser alterado:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save and clear 'as_series' flags
  bool as_series_price  = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int    start_position;
  double smooth_factor = 2.0 / (1.0 + period);

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    for (int i = 0; i < begin; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    start_position = period + begin;
    buffer [begin] = price [begin];

    for (int i = begin + 1; i < start_position; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);

  //--- restore as_series flags
  ArraySetAsSeries (price,  as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);
  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

O cálculo da suavização simples também não requer alterações:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::SimpleMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save as_series flags
  bool as_series_price = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int start_position;

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    start_position = period + begin;

    for (int i = 0; i < start_position - 1; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    double first_value = 0;

    for (int i = begin; i < start_position; i++) first_value += price [i];

    buffer [start_position - 1] = first_value / period;
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = buffer [i - 1] + (price [i] - price [i - period]) / period;

  //--- restore as_series flags
  ArraySetAsSeries (price, as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);

  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————


4. Virtualização de estratégia

Um dos leitores dos meus artigos sobre algoritmos de otimização, LUIS ALBERTO BIANUCCI, gentilmente forneceu o código de um EA baseado no indicador Estocástico. Ele me pediu para criar um exemplo baseado neste código, a fim de demonstrar uma maneira de organizar o autoaprendizado no EA usando a biblioteca AO Core e considerar este exemplo no artigo. Assim, outros usuários poderão usar esse método ao conectar algoritmos de otimização em seus próprios desenvolvimentos. Gostaria de enfatizar que este método é adequado para conectar quaisquer algoritmos de otimização discutidos na minha série "Algoritmos de otimização populacional", devido ao fato de que os algoritmos são projetados de forma universal e podem ser aplicados com sucesso em qualquer projeto do usuário.

Já analisamos a virtualização de um indicador como parte de um EA. Agora passamos a considerar a virtualização de uma estratégia. No início do código do EA, declararemos a importação da biblioteca, os arquivos de inclusão da biblioteca de negociação padrão e o arquivo de inclusão do estocástico virtual.

Em seguida, vem o "input" - os parâmetros do EA, entre os quais InpKPeriod_P e InpUpperLevel_P são os mais notáveis. Eles representam o período e os níveis do indicador Estocástico e precisam ser otimizados.

input string   InpKPeriod_P        = "18|9|3|24";  //STO K period:      it is necessary to optimize
input string   InpUpperLevel_P  = "96|88|2|98"; //STO upper level: it is necessary to optimize

Os parâmetros são declarados com o tipo string, os parâmetros são compostos e incluem valores padrão, valor inicial de otimização, passo e valor final.

//——————————————————————————————————————————————————————————————————————————————
#import "\\Market\\AO Core.ex5"
bool   Init (int colonySize, double &range_min [], double &range_max [], double &range_step []);
//------------------------------------------------------------------------------
void   Preparation    ();
void   GetVariantCalc (double &variant [], int pos);
void   SetFitness     (double value,       int pos);
void   Revision       ();
//------------------------------------------------------------------------------
void   GetVariant     (double &variant [], int pos);
double GetFitness     (int pos);
#import
//——————————————————————————————————————————————————————————————————————————————

#include <Trade\Trade.mqh>;
#include "cStochastic.mqh"


input group         "==== GENERAL ====";
sinput long         InpMagicNumber      = 132516;       //Magic Number
sinput double       InpLotSize          = 0.01;         //Lots

input group         "==== Trading ====";
input int           InpStopLoss         = 1450;         //Stoploss
input int           InpTakeProfit       = 1200;         //Takeprofit

input group         "==== Stochastic ==|value|start|step|end|==";
input string        InpKPeriod_P        = "18|9|3|24";  //STO K period   : it is necessary to optimize
input string        InpUpperLevel_P     = "96|88|2|98"; //STO upper level: it is necessary to optimize

input group         "====Self-optimization====";
sinput bool         SelfOptimization    = true;
sinput int          InpBarsOptimize     = 18000;        //Number of bars in the history for optimization
sinput int          InpBarsReOptimize   = 1440;         //After how many bars, EA will reoptimize
sinput int          InpPopSize          = 50;           //Population size
sinput int          NumberFFlaunches    = 10000;        //Number of runs in the history during optimization
sinput int          Spread              = 10;           //Spread

MqlTick Tick;
CTrade  Trade;

C_iStochastic IStoch;

double Set        [];
double Range_Min  [];
double Range_Step [];
double Range_Max  [];

double TickSize = 0.0;

Ao inicializar o EA na função OnInit, definiremos o tamanho dos arrays de parâmetros de acordo com o número de parâmetros sendo otimizados: Set - um conjunto de parâmetros, Range_Min - valores mínimos de parâmetros (valores iniciais), Range_Step - passo de parâmetro e Range_Max - valores máximos de parâmetros. Extraia os valores correspondentes dos parâmetros de string e atribua-os aos arrays.

//——————————————————————————————————————————————————————————————————————————————
int OnInit ()
{
  TickSize = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE);

  ArrayResize (Set,        2);
  ArrayResize (Range_Min,  2);
  ArrayResize (Range_Step, 2);
  ArrayResize (Range_Max,  2);

  string result [];
  if (StringSplit (InpKPeriod_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [0] = (double)StringToInteger (result [0]);
  Range_Min  [0] = (double)StringToInteger (result [1]);
  Range_Step [0] = (double)StringToInteger (result [2]);
  Range_Max  [0] = (double)StringToInteger (result [3]);

  if (StringSplit (InpUpperLevel_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [1] = (double)StringToInteger (result [0]);
  Range_Min  [1] = (double)StringToInteger (result [1]);
  Range_Step [1] = (double)StringToInteger (result [2]);
  Range_Max  [1] = (double)StringToInteger (result [3]);

  IStoch.Init ((int)Set [0], 1, 3);

  //  set magicnumber to trade object
  Trade.SetExpertMagicNumber (InpMagicNumber);

  //---
  return (INIT_SUCCEEDED);
}
//——————————————————————————————————————————————————————————————————————————————

Na função OnTick do código do EA, inserimos o bloco de chamada de auto-otimização - a função "Optimize", que é o "gerente" no diagrama da Figura 1 e inicia a otimização. Use os valores do array Set onde as variáveis externas que precisam ser otimizadas deveriam ser usadas.

//——————————————————————————————————————————————————————————————————————————————
void OnTick ()
{
  //----------------------------------------------------------------------------
  if (!IsNewBar ())
  {
    return;
  }

  //----------------------------------------------------------------------------
  if (SelfOptimization)
  {
    //--------------------------------------------------------------------------
    static datetime LastOptimizeTime = 0;

    datetime timeNow  = iTime (_Symbol, PERIOD_CURRENT, 0);
    datetime timeReop = iTime (_Symbol, PERIOD_CURRENT, InpBarsReOptimize);

    if (LastOptimizeTime <= timeReop)
    {
      LastOptimizeTime = timeNow;
      Print ("-------------------Start of optimization----------------------");

      Print ("Old set:");
      ArrayPrint (Set);

      Optimize (Set,
                Range_Min,
                Range_Step,
                Range_Max,
                InpBarsOptimize,
                InpPopSize,
                NumberFFlaunches,
                Spread * SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE));

      Print ("New set:");
      ArrayPrint (Set);

      IStoch.Init ((int)Set [0], 1, 3);
    }
  }

  //----------------------------------------------------------------------------
  if (!SymbolInfoTick (_Symbol, Tick))
  {
    Print ("Failed to get current symbol tick"); return;
  }

  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates (_Symbol, PERIOD_CURRENT, 0, (int)Set [0] + 1 + 3 + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  int calc = IStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return;

  double buff0 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 2];
  double buff1 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 3];

  //----------------------------------------------------------------------------
  // count open positions
  int cntBuy, cntSell;
  if (!CountOpenPositions (cntBuy, cntSell))
  {
    Print ("Failed to count open positions");
    return;
  }

  //----------------------------------------------------------------------------
  // check for buy
  if (cntBuy == 0 && buff1 <= (100 - (int)Set [1]) && buff0 > (100 - (int)Set [1]))
  {
    ClosePositions (2);

    double sl = NP (Tick.bid - InpStopLoss   * TickSize);
    double tp = NP (Tick.bid + InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA");
  }

  //----------------------------------------------------------------------------
  // check for sell
  if (cntSell == 0 && buff1 >= (int)Set [1] && buff0 < (int)Set [1])
  {
    ClosePositions (1);

    double sl = NP (Tick.ask + InpStopLoss   * TickSize);
    double tp = NP (Tick.ask - InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA");
  }
}
//——————————————————————————————————————————————————————————————————————————————

A função Otimizar realiza as mesmas ações que geralmente são realizadas em scripts de teste de algoritmos de otimização na série de artigos "Algoritmos de otimização populacional":

1. Inicialização do algoritmo de otimização.
2.1. Preparação da população.
2.2. Obtenção de um conjunto de parâmetros do algoritmo de otimização.
2.3. Cálculo da função de aptidão com parâmetros passados para ela.
2.4. Atualização da melhor solução.
2.5. Obtenção da melhor solução do algoritmo.

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  double parametersSet [];
  ArrayResize(parametersSet, ArraySize(set));

  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  Init(inpPopSize, range_min, range_max, range_step);

  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    Preparation ();

    for (int set = 0; set < inpPopSize; set++)
    {
      GetVariantCalc (parametersSet, set);
      SetFitness     (VirtualStrategy (parametersSet, inpBarsOptimize, spread), set);
    }

    Revision ();
  }

  Print ("Fitness: ", GetFitness (0));
  GetVariant (parametersSet, 0);
  ArrayCopy (set, parametersSet, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

A função VirtualStrategy realiza o teste da estratégia em dados históricos (no diagrama da Figura 1, isso é "EA Virt"). Ela recebe o array de parâmetros "set", "barsOptimize" - o número de barras para otimização e o valor "spread".

Primeiro, os dados são preparados. Os dados históricos são carregados no array "rates". Em seguida, os arrays "hi", "lo" e "cl", necessários para calcular o Estocástico, são criados.

Em seguida, o indicador Estocástico é inicializado e calculado com base nos dados históricos. Se o cálculo falhar, a função retorna o valor "-DBL_MAX" (o pior valor possível da função de aptidão).

Isso é seguido pelo teste da estratégia em dados históricos com sua lógica correspondendo totalmente ao código principal do EA. O objeto "deals" é criado para armazenar negociações. Em seguida, há uma execução nos dados históricos, onde as condições para abrir e fechar posições são verificadas para cada barra com base no valor do indicador e nos níveis "upLevel" e "dnLevel". Se as condições forem atendidas, a posição é aberta ou fechada.

Após a conclusão da execução nos dados históricos, a função verifica o número de negociações concluídas. Se não houve negociações, a função retorna o valor "-DBL_MAX". Caso contrário, a função retorna o saldo final.

O valor de retorno da função VirtualStrategy é o valor da função de aptidão. Neste caso, este é o valor do saldo final em pontos (como foi dito anteriormente, a função de aptidão pode ser um saldo, um fator de lucro ou qualquer outro indicador da qualidade do resultado das operações de negociação com base em dados históricos).

É importante notar que a estratégia virtual deve corresponder o mais próximo possível à estratégia do EA. Neste exemplo, a negociação é realizada a preços de abertura, o que corresponde ao controle da abertura da barra no EA principal. Se a lógica da estratégia de negociação for executada em cada tick, então o usuário precisa se preocupar em baixar o histórico de ticks durante o teste virtual e ajustar a função VirtualStrategy de acordo.

//——————————————————————————————————————————————————————————————————————————————
double VirtualStrategy (double &set [], int barsOptimize, double spread)
{
  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, 0, barsOptimize + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return -DBL_MAX;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  C_iStochastic iStoch;
  iStoch.Init ((int)set [0], 1, 3);

  int calc = iStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return -DBL_MAX;

  //============================================================================
  //test of strategy on history-------------------------------------------------
  S_Deals deals;

  double iStMain0 = 0.0;
  double iStMain1 = 0.0;
  double upLevel  = set [1];
  double dnLevel  = 100.0 - set [1];
  double balance  = 0.0;

  //running through history-----------------------------------------------------
  for (int i = 2; i < dataCount; i++)
  {
    if (i >= dataCount)
    {
      deals.ClosPos (-1, rates [i].open, spread);
      deals.ClosPos (1, rates [i].open, spread);
      break;
    }

    iStMain0 = iStoch.ExtMainBuffer [i - 1];
    iStMain1 = iStoch.ExtMainBuffer [i - 2];

    if (iStMain0 == 0.0 || iStMain1 == 0.0) continue;

    //buy-------------------------------
    if (iStMain1 <= dnLevel && dnLevel < iStMain0)
    {
      deals.ClosPos (-1, rates [i].open, spread);

      if (deals.GetBuys () == 0) deals.OpenPos (1, rates [i].open, spread);
    }

    //sell------------------------------
    if (iStMain1 >= upLevel && upLevel > iStMain0)
    {
      deals.ClosPos (1, rates [i].open, spread);

      if (deals.GetSels () == 0) deals.OpenPos (-1, rates [i].open, spread);
    }
  }
  //----------------------------------------------------------------------------

  if (deals.histSelsCNT + deals.histBuysCNT <= 0) return -DBL_MAX;
  return deals.balance;
}
//——————————————————————————————————————————————————————————————————————————————

Se quisermos usar o algoritmo de otimização da série "Algoritmos de otimização populacional" (o algoritmo "Evolução dos Grupos Sociais", ou ESG, é fornecido como exemplo no arquivo do artigo), então precisamos especificar o caminho para o algoritmo no EA:

#include "AO_ESG.mqh"

Na função Otimizar, declare o objeto do algoritmo ESG e configure os valores limite dos parâmetros otimizados. Em seguida, a função Otimizar ficaria assim para usar o ESG:

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  C_AO_ESG AO;
  
  int    Population_P     = 200;   //Population size
  int    Groups_P         = 100;   //Number of groups
  double GroupRadius_P    = 0.1;   //Group radius
  double ExpansionRatio_P = 2.0;   //Expansion ratio
  double Power_P          = 10.0;  //Power
  
  AO.Init (ArraySize (set), Population_P, Groups_P, GroupRadius_P, ExpansionRatio_P, Power_P);
  
  for (int i = 0; i < ArraySize (set); i++)
  {
    AO.rangeMin  [i] = range_min  [i];
    AO.rangeStep [i] = range_step [i];
    AO.rangeMax  [i] = range_max  [i];
  }
  
  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    AO.Moving ();
    
    for (int set = 0; set < ArraySize (AO.a); set++)
    {
      AO.a [set].f = VirtualStrategy (AO.a [set].c, inpBarsOptimize, spread);
    }

    AO.Revision ();
  }

  Print ("Fitness: ", AO.fB);
  ArrayCopy (set, AO.cB, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

As estratégias de algoritmo de busca apresentadas na minha série de artigos de otimização fornecem uma abordagem simples e clara para analisar e compará-las entre si, em sua forma mais pura - "como está". Eles não incluem métodos para acelerar a busca, como eliminar duplicatas e algumas outras técnicas, e requerem mais iterações e tempo.


5. Funcionalidade de teste

Vamos testar nosso EA de auto-otimização baseado no indicador Estocástico por um período de um ano com os parâmetros mostrados abaixo na captura de tela, primeiro no modo "falso" do parâmetro SelfOptimization, ou seja, sem auto-otimização.

photo_

Figura 2. Configurações do EA

OriginalTest

Figura 3. Resultados com auto-otimização desativada

SelfOpt

Figura 4. Resultados com auto-otimização ativada


Resumo

Neste artigo, analisamos a organização da auto-otimização em um EA. Este método é muito simples e requer intervenção mínima no código-fonte do EA. Para cada estratégia específica, recomenda-se conduzir uma série de experimentos para determinar os comprimentos ótimos dos segmentos históricos, nos quais a otimização e a negociação ocorrem. Esses valores são individuais e dependentes da estratégia.

É importante entender que a otimização não pode levar a resultados positivos se a própria estratégia não tiver conjuntos lucrativos. É impossível extrair ouro da areia se não houver ouro lá em primeiro lugar. A otimização é uma ferramenta útil para melhorar o desempenho da estratégia, mas não pode criar conjuntos lucrativos onde não existem. Portanto, primeiro você deve desenvolver uma estratégia que tenha potencial de lucro e, em seguida, usar a otimização para melhorá-la.

As vantagens dessa abordagem são a capacidade de testar uma estratégia em dados históricos usando o teste walk-forward e encontrar critérios de otimização adequados que correspondam a uma estratégia específica. O teste walk-forward nos permite avaliar a eficiência de uma estratégia em dados históricos, levando em consideração as mudanças nas condições do mercado ao longo do tempo. Isso ajuda a evitar a sobre-otimização, quando uma estratégia funciona bem apenas em um certo período da história, mas não pode ser aplicada com sucesso em tempo real. Assim, o teste walk-forward fornece uma avaliação mais confiável do desempenho da estratégia.

O teste walk-forward (WFT) é uma técnica para avaliar e testar estratégias de negociação nos mercados financeiros. É usado para determinar a eficiência e sustentabilidade das estratégias de negociação com base em dados históricos e sua capacidade de proporcionar lucratividade no futuro.

A ideia básica do WFT é dividir os dados disponíveis em vários períodos: um período histórico usado para desenvolver e ajustar uma estratégia (período de treinamento) e períodos subsequentes usados para avaliar e testar a estratégia (períodos de teste). O processo é repetido várias vezes. Cada vez, o período de treinamento é movido para frente por um passo, e o período de teste também é movido para frente. Assim, a estratégia é testada em vários intervalos de tempo para garantir que seja capaz de se adaptar às mudanças nas condições do mercado.

O teste walk-forward é uma maneira mais realista de avaliar estratégias porque leva em consideração as mudanças nas condições do mercado ao longo do tempo. Também ajuda a evitar o sobretreinamento de uma estratégia em dados históricos e fornece uma compreensão mais precisa de seu desempenho no mundo real.

No arquivo anexado a este artigo, você encontrará exemplos que demonstram como conectar algoritmos de otimização ao EA. Você poderá estudar o código e aplicá-lo à sua estratégia específica para obter resultados ótimos.

O exemplo dado não é destinado a negociações em contas reais, pois não há verificações necessárias, e destina-se apenas a demonstrar a possibilidade de auto-otimização.

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

Arquivos anexados |
Operações baseadas em ângulos para traders Operações baseadas em ângulos para traders
Este artigo abordará operações baseadas em ângulos. Vamos examinar métodos para construir ângulos e utilizá-los no trading.
Construindo e testando sistemas de negociação com o Canal Keltner Construindo e testando sistemas de negociação com o Canal Keltner
Neste artigo, tentaremos fornecer sistemas de negociação usando um conceito muito importante no mercado financeiro, que é a volatilidade. Forneceremos um sistema de negociação baseado no indicador Canal Keltner após compreendê-lo e como podemos codificá-lo e criar um sistema de negociação baseado em uma estratégia simples de negociação e testá-lo em diferentes ativos.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Balanceando riscos ao negociar múltiplos instrumentos simultaneamente Balanceando riscos ao negociar múltiplos instrumentos simultaneamente
Este artigo permitirá que um iniciante escreva uma implementação de um script do zero para balancear riscos ao negociar múltiplos instrumentos simultaneamente. Além disso, pode dar aos usuários experientes novas ideias para implementar suas soluções em relação às opções propostas neste artigo.