English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Controlando o declive da curva de equilíbrio durante o trabalho de um Expert Advisor

Controlando o declive da curva de equilíbrio durante o trabalho de um Expert Advisor

MetaTrader 5Negociação | 27 janeiro 2014, 14:40
1 504 0
Dmitriy Skub
Dmitriy Skub

Introdução

Este artigo descreve uma das abordagens, que permite melhorar a performance de Consultores Especialistas pela criação de um feedback. Neste caso, o feedback será baseado na medição do declive da curva de equilíbrio. O controle do declive é performado automaticamente por regular o volume de trabalho. Um Consultor Especialista pode negociar nos seguintes modos: com um volume de corte, com trabalho em quantidades de lotes (de acordo com um inicialmente ajustado) e com um volume intermediário. O modo de trabalho é trocado automaticamente.

Diferentes características reguladoras são usadas na cadeia de feedback: em passos, em passos com histerese, linear. Ela permite ajustar o sistema de controle do declive da curva de equilíbrio para as características de um certo sistema.

A principal ideia é automatizar o processo de tomar decisões por um negociador enquanto monitorando seu próprio sistema de transações. é sensato reduzir riscos durante períodos desfavoráveis de seu trabalho. Ao retornar ao modo normal de trabalho os riscos podem ser restaurados ao nível inicial.

é claro, este sistema não é uma panaceia, e não irá transformar um Consultor Especialista perdedor em um lucrativo. De alguma forma, esta adição a GD (gestão de dinheiro) do Consultor Especialista que o mantém de obter perdas consideráveis em uma conta.

Este artigo inclui uma biblioteca, a qual permite incorporar esta função ao código de qualquer Consultor Especialista.


Princípio de Operação

Vamos dar uma olhada no princípio de operação do sistema, que controla o declive da curva de equilíbrio. Assumindo que nós temos um negociador de Consultor Especialista. Sua curva hipotética de equilíbrio mostra-se como no seguinte:

Princípio de operação do sistema que controla o declive da curva de equilíbrio

Figura 1. Princípio de operação do sistema que controla o declive da curva de equilíbrio

Curva inicial de equilíbrio para o Consultor Especialista que usa volume constante de operações de negócio mostradas acima. Transações fechadas são mostradas com os pontos vermelhos. Vamos conectar estes pontos com uma linha curva, a qual representa a mudança no equilíbrio do Consultor Especialista durante a negociação (linha preta densa).

Agora iremos continuamente rastrear o ângulo do declive desta linha para o eixo de horário (mostrado com linhas azuis finas). Ou, para ser mais preciso, antes de abrir cada transação por um sinal, calcularemos o ângulo do declive por duas negociações previamente fechadas ( ou por duas transações, para a descrição ser mais simples). Se o ângulo do declive se tornar menos do que o valor especificado, então nosso sistema de controle começa a funcionar; ele reduz o volume de acordo com o valor calculado do ângulo e a função reguladora especificada.

De tal maneira, se a transação entra em um período infrutífero, o volume diminui de Vmax. para Vmin. dentro do período de negociação Т3...Т5. Após o ponto de negociação Т5 ser executado com um volume mínimo especificado, - no modo de rejeição do volume de transações. Uma vez que a lucratividade do Consultor Especialista é restaurado e o ângulo do declive da curva de equilíbrio aumenta acima do valor especificado, o volume começa a crescer. Isto ocorre dentro do intervalo Т8...Т10. Após o ponto Т10, o volume de operações de transação restaura o estado inicial Vmax.

A curva de equilíbrio formada como um resultado de tal regulação é mostrada na parte mais baixa da fig. 1. Você pode observar que o levantamento inicial de B1 para B2 diminuiu e se tornou de B1 para B2*. você também pode observar que o lucro diminui levemente dentro do período de restauração do volume máximo Т8...Т10 - este é o reverso da medalha.

A cor verde destaca a parte da curva de equilíbrio quando a negociação era performada com um volume mínimo especificado. A cor amarela representa as partes da transição do volume máximo para o mínimo e de volta. Muitas variantes da transição são possíveis aqui:

  • em passos - o volume muda em discretos passos do volume máximo ao mínimo e de volta;
  • linear - o volume é modificado linearmente dependendo do ângulo do declive da curva de equilíbrio dentro do intervalo regulamentado;
  • em passos com histerese - a transição do volume máximo para o mínimo e a volta é performada em diferentes valores do ângulo de declive;

Vamos ilustrar isso com imagens:

Tipos de características reguladoras

Figura 2. Tipos de características reguladoras

Características reguladoras afetas as taxas do sistema controlador - o atraso da ativação/desativação, o processo de transição do volume máximo para mínimo e a volta. é recomendado escolher uma característica em uma base experimental quando alcançando os melhores resultados de testes.

Assim, nós melhoramos o sistema de negociação com o feedback baseado no ângulo de declive da curva de equilíbrio. Note que tal regulação do volume é adequada apenas para esses sistemas, os quais não tem o volume como uma parte do sistema de negociações. Por exemplo, se o princípio de Martingale é usado, você não pode usar este sistema diretamente sem mudanças no nosso Consultor Especialista inicial.

Em adição, nós precisamos chamar a atenção para os seguintes pontos importantes:

  • a efetividade da administração do declive da linha de equilíbrio depende diretamente da razão no volume de trabalho no modo normal de operação e no volume no modo de rejeição de volume. Quanto maior a razão, mais efetiva a administração. Isto é o motivo pelo qual o volume de trabalho inicial deve ser consideravelmente maior do que o mínimo possível.
  • o período médio de alteração de ascensões e quedas do equilíbrio do Consultor Especialista deve ser consideravelmente maior do que o tempo de reação do sistema controle. Em outro caso, o sistema não irá administrar para regular o declive da curva de equilíbrio. Quanto maior a razão da média de período para a reação for, mair efetivo o sistema é. Este requisito engloba quase todo sistema de regulação automática.

Implementação no MQL5 Utilizando Programação Orientada a Objetos

Vamos escrever uma biblioteca que executa a abordagem descrita acima. Par afazê-la, vamos utilizar o novo recuro do MQL5 - abordagem orientada a objeto. Esta abordagem permite facilmente desenvolver e expandir nossa biblioteca no futuro sem reescrever grandes partes do código desde o início.


Classe TradeSymbol

Já que o teste multi-moedas é implementado na nova plataforma MetaTrader 5, precisamos uma classe, a qual encapsula em si mesma todo o trabalho com qualquer símbolo de trabalho. Ela permite usar esta biblioteca em Consultores Especialistas multi-moedas. Esta classe não engloba o sistema controlador diretamente, é auxiliar. Então, esta classe será usada para operações com o símbolo de trabalho.

//---------------------------------------------------------------------
//  Operations with work symbol:
//---------------------------------------------------------------------
class TradeSymbol
{
private:
  string  trade_symbol;                          // work symbol

private:
  double  min_trade_volume;                      // minimum allowed volume for trade operations
  double  max_trade_volume;                      // maximum allowed volume for trade operations
  double  min_trade_volume_step;                 // minimum change of volume
  double  max_total_volume;                      // maximum change of volume
  double  symbol_point;                          // size of one point
  double  symbol_tick_size;                      // minimum change of price
  int     symbol_digits;                        // number of digits after decimal point

protected:

public:
  void    RefreshSymbolInfo( );                  // refresh market information about the work symbol
  void    SetTradeSymbol( string _symbol );      // set/change work symbol
  string  GetTradeSymbol( );                     // get work symbol
  double  GetMaxTotalLots( );                    // get maximum cumulative volume
  double  GetPoints( double _delta );            // get change of price in points

public:
  double  NormalizeLots( double _requied_lot );  // get normalized trade volume
  double  NormalizePrice( double _org_price );   // get normalized price with consideration of step of change of quote

public:
  void    TradeSymbol( );                       // constructor
  void    ~TradeSymbol( );                      // destructor
};

A estrutura da classe é muito simples. O propósito é obter, armazenar e processar a informação do mercado atual por um símbolo especificado. Os principais métodos são TradeSymbol::RefreshSymbolInfo, TradeSymbol::NormalizeLots, TradeSymbol::NormalizePrice. Vamos considerá-los um a um.


O método TradeSymbol::RefreshSymbolInfo tem o propósito de atualizar a informação de mercado pelo símbolo de trabalho.

//---------------------------------------------------------------------
//  Refresh market information by work symbol:
//---------------------------------------------------------------------
void
TradeSymbol::RefreshSymbolInfo( )
{
//  If a work symbol is not set, don't do anything:
  if( GetTradeSymbol( ) == NULL )
  {
    return;
  }

//  Calculate parameters necessary for normalization of volume:
  min_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MIN );
  max_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MAX );
  min_trade_volume_step = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_STEP );

  max_total_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_LIMIT );

  symbol_point = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_POINT );
  symbol_tick_size = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_TRADE_TICK_SIZE );
  symbol_digits = ( int )SymbolInfoInteger( GetTradeSymbol( ), SYMBOL_DIGITS );
}

Preste atenção a um ponto importante que é usado em vários métodos. Já que a realização atual do MQL5 não permite usar um construtor com parâmetros, você deve chamar o seguinte método para configuração primária de símbolos de trabalho:

void    SetTradeSymbol( string _symbol );      // set/change work symbol


O método TradeSymbol::NormalizeLots é usado para obter o volume correto e normalizado. Nós sabemos que o tamanho de uma posição não pode ser menos do que o valor mínimo possível permitido pelo corretor. O mínimo passo de mudança de uma posição é também determinado pelo corretor, e pode ser diferente. Este método retorna o valor mais próximo do volume por baixo.

Ele também verifica se o volume se uma suposta posição excede o valor máximo permitido pelo corretor.

//---------------------------------------------------------------------
//  Get normalized trade volume:
//---------------------------------------------------------------------
//  - input necessary volume;
//  - output is normalized volume;
//---------------------------------------------------------------------
double
TradeSymbol::NormalizeLots( double _requied_lots )
{
  double   lots, koeff;
  int      nmbr;

//  If a work symbol is not set, don't do anything:
  if( GetTradeSymbol( ) == NULL )
  {
    return( 0.0 );
  }

  if( this.min_trade_volume_step > 0.0 )
  {
    koeff = 1.0 / min_trade_volume_step;
    nmbr = ( int )MathLog10( koeff );
  }
  else
  {
    koeff = 1.0 / min_trade_volume;
    nmbr = 2;
  }
  lots = MathFloor( _requied_lots * koeff ) / koeff;

//  Lower limit of volume:
  if( lots < min_trade_volume )
  {
    lots = min_trade_volume;
  }

//  Upper limit of volume:
  if( lots > max_trade_volume )
  {
    lots = max_trade_volume;
  }

  lots = NormalizeDouble( lots, nmbr );
  return( lots );
}


O método TradeSymbol::NormalizePrice é usado para obter o preço correto e normalizado. Já que o número de dígitos significativos após o ponto decimal (precisão de preço) deve ser determinado para um dado símbolo, nós precisamos truncar o preço. Em adição a isto, alguns símbolos (por exemplo, futuros) tem um passo mínimo de mudança de preço maior do que um ponto. é por isso que precisamos fazer os valores do preço serem múltiplos de uma mínima discricidade.

//---------------------------------------------------------------------
//  Normalization of price with consideration of step of price change:
//---------------------------------------------------------------------
double
TradeSymbol::NormalizePrice( double _org_price )
{
//  Minimal step of quote change in points:
  double  min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 );

  double  norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits );
  return( norm_price );
}

O preço não normalizado necessário é introduzido na função. E ele retornar o preço normalizado, o qual é mais próximo ao necessário.

O propósito dos outros métodos é claramente descrito em comentários; não requer qualquer descrição adicional.


Classe TBalanceHistory

Esta classe, destina-se para operação com o histórico de equilíbrio de uma conta, o que é claro pelo seu nome. é também uma classe base para várias classes descritas abaixo. O propósito principal desta classe é o acesso ao histórico de transações de um Consultor Especialista. Em adição, você pode filtrar o histórico pelo símbolo de trabalho, pelo "número mágico", pela data do início do monitoramento do Consultor Especialista ou pelos três elementos simultaneamente.

//---------------------------------------------------------------------
//  Operations with balance history:
//---------------------------------------------------------------------
class TBalanceHistory
{
private:
  long      current_magic;            // value of "magic number" when accessing the history of deals ( 0 - any number )
  long      current_type;             // type of deals ( -1 - all )
  int       current_limit_history;    // limit of depth of history ( 0 - all history )
  datetime   monitoring_begin_date;   // date of start of monitoring history of deals
  int       real_trades;              // number of actual trades already performed

protected:
  TradeSymbol  trade_symbol;          // operations with work symbol

protected:
//  "Raw" arrays:
  double    org_datetime_array[ ];    // date/time of trade
  double    org_result_array[ ];      // result of trade

//  Arrays with data grouped by time:
  double    group_datetime_array[ ];  // date/time of trade
  double    group_result_array[ ];    // result of trade

  double    last_result_array[ ];     // array for storing results of last trades ( points on the Y axis )
  double    last_datetime_array[ ];   // array for storing time of last trades ( points on the X axis )

private:
  void      SortMasterSlaveArray( double& _m[ ], double& _s[ ] );  // synchronous ascending sorting of two arrays

public:
  void      SetTradeSymbol( string _symbol );                      // set/change work symbol
  string    GetTradeSymbol( );                                    // get work symbol
  void      RefreshSymbolInfo( );                                 // refresh market information by work symbol
  void      SetMonitoringBeginDate( datetime _dt );                // set date of start of monitoring
  datetime  GetMonitoringBeginDate( );                            // get date of start of monitoring
  void      SetFiltrParams( long _magic, long _type = -1, int _limit = 0 );// set parameters of filtration of deals

public:
// Get results of last trades:
  int       GetTradeResultsArray( int _max_trades );

public:
  void      TBalanceHistory( );       // constructor
  void      ~TBalanceHistory( );      // destructor
};

As configurações de filtragem quando lendo os resultados das últimas transações e histórico são definidos usando o método TBalanceHistory::SetFiltrParams. Ele tem os seguintes parâmetros de entrada:

  • _magic - "número mágico" de transações que deve ser lido do histórico. Se o valor zero é especificado então transações com qualquer "número mágico" serão lidas.
  • _type - tipo de transações que devem ser lidas. Podem ter os seguintes valores - DEAL_TYPE_BUY (para a leitura de transações longas apenas), DEAL_TYPE_SELL (para leitura de transações curtas apenas) e -1 (para a leitura de ambas).
  • _limit - limita a profundidade do histórico de transações analisado. Se é igual a zero, todo o histórico disponível é analisado.

Como padrão, os seguintes valores são definidos quando o objeto da classe TBalanceHistory é criado: _magic = 0, _type = -1, _limit = 0.


O principal método desta classe é TBalanceHistory::GetTradeResultsArray. Destina-se para o preenchimento de arranjos (arrays) de membros de classes last_result_array e last_datetime_array com os resultados das últimas transações. O método tem os seguintes parâmetros de entrada:

  • _max_trades - número máximo de transações que devem ser lidas do histórico e serem escrita nos arranjos de saída. Já que precisamos de pelo menos dois pontos para calculas o ângulo do declive, este valor não deve ser menos que dois. Se este valor é igual a zero, todo o histórico disponível de transações é analisado. Praticamente, o número de pontos necessário para o cálculo do declive da curva de equilíbrio é especificado aqui.
//---------------------------------------------------------------------
//  Reads the results of last (by time) trades to arrays:
//---------------------------------------------------------------------
//  - returns the number of actually read trades but not more than specified;
//---------------------------------------------------------------------
int
TBalanceHistory::GetTradeResultsArray( int _max_trades )
{
  int       index, limit, count;
  long      deal_type, deal_magic, deal_entry;
  datetime   deal_close_time, current_time;
  ulong     deal_ticket;                        // ticket of deal
  double    trade_result;
  string    symbol, deal_symbol;

  real_trades = 0;

//  Number of trades should be no less than two:
  if( _max_trades < 2 )
  {
    return( 0 );
  }

//  If a work symbol is not specified, don't do anything:
  symbol = trade_symbol.GetTradeSymbol( );
  if( symbol == NULL )
  {
    return( 0 );
  }

//  Request the history of deals and orders from the specified time to the current moment:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    return( 0 );
  }

//  Calculate number of trades:
  count = HistoryDealsTotal( );

//  If there are less trades in the history than it is necessary, then exit:
  if( count < _max_trades )
  {
    return( 0 );
  }

//  If there are more trades in the history than it is necessary, then limit them:
  if( current_limit_history > 0 && count > current_limit_history )
  {
    limit = count - current_limit_history;
  }
  else
  {
    limit = 0;
  }

//  If needed, adjust dimension of "raw" arrays by the specified number of trades:
  if(( ArraySize( org_datetime_array )) != ( count - limit ))
  {
    ArrayResize( org_datetime_array, count - limit );
    ArrayResize( org_result_array, count - limit );
  }

//  Fill the "raw" array with trades from history base:
  real_trades = 0;
  for( index = count - 1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );

//  If those are not closed deals, don't go further:
    deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
    if( deal_entry != DEAL_ENTRY_OUT )
    {
      continue;
    }

//  Check "magic number" of deal if necessary:
    deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
    if( current_magic != 0 && deal_magic != current_magic )
    {
      continue;
    }

//  Check symbol of deal:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      continue;
    }
                
//  Check type of deal if necessary:
    deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
    if( current_type != -1 && deal_type != current_type )
    {
      continue;
    }
    else if( current_type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
    {
      continue;
    }
                
//  Check time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

//  So, we can read another trade:
    org_datetime_array[ real_trades ] = deal_close_time / 60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }

//  if there are less trades than necessary, return:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

  count = real_trades;

//  Sort the "raw" array by date/time of closing the order:
  SortMasterSlaveArray( org_datetime_array, org_result_array );

// If necessary, adjust dimension of group arrays for the specified number of points:
  if(( ArraySize( group_datetime_array )) != count )
  {
    ArrayResize( group_datetime_array, count );
    ArrayResize( group_result_array, count );
  }
  ArrayInitialize( group_datetime_array, 0.0 );
  ArrayInitialize( group_result_array, 0.0 );

//  Fill the output array with grouped data ( group by the identity of date/time of position closing ):
  for( index = 0; index < count; index++ )
  {
//  Get another trade:
    deal_close_time = ( datetime )org_datetime_array[ index ];
    trade_result = org_result_array[ index ];

//  Now check if the same time already exists in the output array:
    current_time = ( datetime )group_datetime_array[ real_trades ];
    if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 )
    {
      real_trades++;                      // move the pointer to the next element
      group_result_array[ real_trades ] = trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
    else
    {
      group_result_array[ real_trades ] += trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
  }
  real_trades++;                          // now this is the number of unique elements

//  If there are less trades than necessary, exit:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

  if( ArraySize( last_result_array ) != _max_trades )
  {
    ArrayResize( last_result_array, _max_trades );
    ArrayResize( last_datetime_array, _max_trades );
  }

//  Write the accumulated data to the output arrays with reversed indexation:
  for( index = 0; index < _max_trades; index++ )
  {
    last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ];
    last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ];
  }

//  In the output array replace the results of single trades with the accumulating total:
  for( index = 1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - 1 ];
  }

  return( _max_trades );
}

Verificações obrigatórias são performadas no início - se um símbolo de trabalho é especificado e se os parâmetros de entrada estão corretos.

Então lemos o histórico de transações e ordens da data especificada para o momento atual. é executado na seguinte parte do código:

//  Request the history of deals and orders from the specified time to the current moment:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    return( 0 );
  }

//  Calculate number of trades:
  count = HistoryDealsTotal( );

//  If there are less trades in the history than it is necessary, then exit:
  if( count < _max_trades )
  {
    return( 0 );
  }

Em adição, o número total de transações no histórico é verificado. Se é menor do que especificado, ações seguintes são sem sentido. Assim que os arranjos "brutos" estão preparados, o ciclo de preenchê-los com a informação do histórico de transações é executado. é feito da seguinte maneira:

//  Fill the "raw" array from the base of history of trades:
  real_trades = 0;
  for( index = count - 1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );

//  If the trades are not closed, don't go further:
    deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
    if( deal_entry != DEAL_ENTRY_OUT )
    {
      continue;
    }

//  Check "magic number" of deal if necessary:
    deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
    if( _magic != 0 && deal_magic != _magic )
    {
      continue;
    }

//  Check symbols of deal:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      continue;
    }
                
//  Check type of deal if necessary:
    deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
    if( _type != -1 && deal_type != _type )
    {
      continue;
    }
    else if( _type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
    {
      continue;
    }
                
//  Check time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

//  So, we can rad another trade:
    org_datetime_array[ real_trades ] = deal_close_time / 60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }

//  If there are less trades than necessary, exit:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

No início, o ticket da transação do histórico é lido usando a função HistoryDealGetTicket; leitura adicional de detalhes da transação é executada usando o ticket obtido. Já que estamos interessados apenas em transações fechadas (iremos analisar o equilíbrio), o tipo de transação é checado primeiro. é feito por chamar a função HistoryDealGetInteger com o parâmetro DEAL_ENTRY. Se a função retorna DEAL_ENTRY_OUT, então é o fechamento de uma posição.

Após aquele "número mágico" da transação, o tipo de transação (como o parâmetro de entrada do método é especificado) e símbolo da transação são verificados. Se todos os parâmetros da transação atendem aos requisitos, então o último parâmetro é verificado - horário do fechamento da transação. é feito da seguinte maneira:

//  Check the time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

A data/horário da transação é comparada com a data/horário dado para início do monitoramento do histórico. Se a data/horário da transação é maior do que a dada, então nós vamos ler nossa transação para o arranjo - ler o resultado da transação em pontos e o horário da transação em minutos (neste acaso, o horário de fechamento). Após isso, o contador de transações lidas real_trades é aumentado; e o ciclo continua.

Uma vez que os arranjos "brutos" são preenchidos com a quantidade necessária de informação, devemos ordenar o arranjo onde o horário do fechamento de transações é armazenado. Ao mesmo tempo, precisamos mantes a correspondência do horário de fechamento no arranjo org_datetime_array e os resultados de transações no arranjo org_result_array. Isto é feito utilizando o método escrito especialmente:

TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] ). O primeiro parâmetro é _master - o arranjo que é ordenada de forma ascendente. O Segundo parâmetro é _slave - o arranjo, os elementos quais devem ser movidos em sincronia com os elementos do primeiro arranjo. A ordenação é performada por meio do método "bolha.

Após todas operações descritas acima, temos dois arranjos com os horários e resultados de transações organizados pelo horário. Já que apenas um ponto na curva de equilíbrio (ponto no eixo Y) pode corresponder a cada momento no horário (ponto no eixo X), precisamos agrupar os elementos do arranjo com o mesmo horário de fechamento (se houver). A seguinte parte do código executa esta operação:

//  Fill the output array with grouped data ( group by identity of date/time of closing of position ):
  real_trades = 0;
  for( index = 0; index < count; index++ )
  {
//  Get another trade:
    deal_close_time = ( datetime )org_datetime_array[ index ];
    trade_result = org_result_array[ index ];

//  Now check, if the same time already exists in the output array:
    current_time = ( datetime )group_datetime_array[ real_trades ];
    if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 )
    {
      real_trades++;                      // move the pointer to the next element
      group_result_array[ real_trades ] = trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
    else
    {
      group_result_array[ real_trades ] += trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
  }
  real_trades++;                          // now this is the number of unique elements

Praticamente, todas as transações com o "esmo" horário de fechamento são somadas aqui. Os resultados são escritos nos arranjos TBalanceHistory::group_datetime_array (horário de fechamento) e TBalanceHistory::group_result_array (resultados das negociações). Após isso nós ordenamos dois arranjos com elementos únicos. A identidade do horário neste caso é considerada dentro de um minuto. Esta transformação pode ser graficamente ilustrada:

Grupamento de transações com o mesmo horário

Figura 3. Grupamento de transações com o mesmo horário

Todas as transações dentro de um minuto (parte esquerda da figura) são agrupadas em uma única com o arredondamento de horário e somando os resultados (parte direita da figura). Isto permite suavizar a "vibração" do horário de fechamento de transações e melhoria na estabilidade da regulação.

Após isso é preciso fazer outras duas transformações dos arranjos obtidos. Reverta a ordem dos elementos para fazer a primeira transação corresponder ao elemento zero; e substituir os resultados de transações simples com o total cumulativo, ou seja com o equilíbrio. é feito com o seguinte fragmento do código:

//  Write the accumulated data into output arrays with reversed indexation:
  for( index = 0; index < _max_trades; index++ )
  {
    last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ];
    last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ];
  }

//  Replace the results of single trades with the cumulative total in the output array:
  for( index = 1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - 1 ];
  }


Classe TBalanceSlope

Esta classe destina-se a fazer operações com a curva de equilíbrio de uma conta. Ela é gerada da classe TBalanceHistory; e herda todos os seus dados e métodos protegidos e públicos. Vamos analisar detalhadamente sua estrutura:

//---------------------------------------------------------------------
//  Operations with the balance curve:
//---------------------------------------------------------------------
class TBalanceSlope : public TBalanceHistory
{
private:
  double    current_slope;               // current angle of slope of the balance curve
  int       slope_count_points;          // number of points ( trades ) for calculation of slope angle
        
private:
  double    LR_koeff_A, LR_koeff_B;      // rates for the equation of the straight-line regression
  double    LR_points_array[ ];          // array of point of the straight-line regression

private:
  void      CalcLR( double& X[ ], double& Y[ ] );  // calculate the equation of the straight-line regression

public:
  void      SetSlopePoints( int _number );        // set the number of points for calculation of angle of slope
  double    CalcSlope( );                         // calculate the slope angle

public:
  void      TBalanceSlope( );                     // constructor
  void      ~TBalanceSlope( );                    // destructor
};


Iremos determinar o ângulo do declive da curva de equilíbrio pelo ângulo do declive da linha de regressão linear desenhada para a quantidade especificada de pontos (transações) na curva de equilíbrio. Deste modo, primeiro de tudo, precisamos calcular a equação da regressão em linha reta da seguinte forma: A*x + B. O seguinte método executa esta tarefa:

//---------------------------------------------------------------------
//  Calculate the equation of the straight-line regression:
//---------------------------------------------------------------------
//  input parameters:
//    X[ ] - arras of values of number series on the X axis;
//    Y[ ] - arras of values of number series on the Y axis;
//---------------------------------------------------------------------
void
TBalanceSlope::CalcLR( double& X[ ], double& Y[ ] )
{
  double    mo_X = 0, mo_Y = 0, var_0 = 0, var_1 = 0;
  int       i;
  int       size = ArraySize( X );
  double    nmb = ( double )size;

//  If the number of points is less than two, the curve cannot be calculated:
  if( size < 2 )
  {
    return;
  }

  for( i = 0; i < size; i++ )
  {
    mo_X += X[ i ];
    mo_Y += Y[ i ];
  }
  mo_X /= nmb;
  mo_Y /= nmb;

  for( i = 0; i < size; i++ )
  {
    var_0 += ( X[ i ] - mo_X ) * ( Y[ i ] - mo_Y );
    var_1 += ( X[ i ] - mo_X ) * ( X[ i ] - mo_X );
  }

//  Value of the A coefficient:
  if( var_1 != 0.0 )
  {
    LR_koeff_A = var_0 / var_1;
  }
  else
  {
    LR_koeff_A = 0.0;
  }

//  Value of the B coefficient:
  LR_koeff_B = mo_Y - LR_koeff_A * mo_X;

//  Fill the array of points that lie on the regression line:
  ArrayResize( LR_points_array, size );
  for( i = 0; i < size; i++ )
  {
    LR_points_array[ i ] = LR_koeff_A * X[ i ] + LR_koeff_B;
  }
}

Aqui nós usamos o método de mínimos quadrados para calcular o erro mínimo de posição da linha de regressão relativa ao dado inicial. O arranjo que armazenar as coordenadas Y, que se encontram na linha calculada, também é preenchido. Este arranjo não é usado por enquanto e destina-se para desenvolvimento futuro.


O método principal que é usado na classe dada é TBalanceSlope::CalcSlope. Ele retorna o ângulo do declive da curva de equilíbrio, o qual é calculado pela quantidade especificada das últimas transações. Aqui esta sua realização:

//---------------------------------------------------------------------
//  Calculate slope angle:
//---------------------------------------------------------------------
double
TBalanceSlope::CalcSlope( )
{
//  Get result of trading from the history of trades:
  int      nmb = GetTradeResultsArray( slope_count_points );
  if( nmb < slope_count_points )
  {
    return( 0.0 );
  }

//  Calculate the regression line by the results of last trades:
  CalcLR( last_datetime_array, last_result_array );
  current_slope = LR_koeff_A;

  return( current_slope );
}

Primeiro de tudo, a quantidade específica de de últimos pontos da curva de equilíbrio é analisada. Isto é feito por chamar o método da classe base TBalanceSlope::GetTradeResultsArray. Se a quantidade de pontos de leitura não é menor do que o especificado, a linha de regressão é calculada. Isto é feito usando o método TBalanceSlope::CalcLR. Preenchidos no passo anterior, os arranjos last_result_array e last_datetime_array, que pertencem à classe base, são usados como parâmetros.

O restante dos métodos são simples e não requerem uma descrição detalhada.


Classe TBalanceSlopeControl

é a classe base, a qual administra o declive da curva de equilíbrio pela modificação do volume de trabalho. Ela origina-se da classe TBalanceSlope , e herda todos seus métodos e dados públicos e protegidos. O único propósito desta classe é calcular o volume de trabalho atual dependendo do atual ângulo do declive da curva de equilíbrio. Vamos dar uma olhada detalhada:

//---------------------------------------------------------------------
//  Managing slope of the balance curve:
//---------------------------------------------------------------------
enum LotsState
{
  LOTS_NORMAL = 1,            // mode of trading with normal volume
  LOTS_REJECTED = -1,         // mode of trading with lowered volume
  LOTS_INTERMEDIATE = 0,      // mode of trading with intermediate volume
};
//---------------------------------------------------------------------
class TBalanceSlopeControl : public TBalanceSlope
{
private:
  double    min_slope;          // slope angle that corresponds to the mode of volume rejection
  double    max_slope;          // slope angle that corresponds to the mode of normal volume
  double    centr_slope;        // slope angle that corresponds to the mode of volume switching without hysteresis

private:
  ControlType  control_type;    // type of the regulation function

private:
  double    rejected_lots;      // volume in the rejection mode
  double    normal_lots;        // volume in the normal mode
  double    intermed_lots;      // volume in the intermediate mode

private:
  LotsState current_lots_state; // current mode of volume

public:
  void      SetControlType( ControlType _control );  // set type of the regulation characteristic
  void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

public:
  double    CalcTradeLots( double _min_lots, double _max_lots );  // get trade volume

protected:
  double    CalcIntermediateLots( double _min_lots, double _max_lots, double _slope );

public:
  void      TBalanceSlopeControl( );   // constructor
  void      ~TBalanceSlopeControl( );  // destructor
};


Antes de calcular o volume atual, precisamos definir parâmetros iniciais. Isto é feito chamando os seguintes métodos:

  void      SetControlType( ControlType _control );  // set type of the regulation characteristic

Entrada parameter_control - este é o tipo de regulação característica. Pode ter o seguinte valor:

  • STEP_WITH_HYSTERESISH - em passos com característica regulação de histerese;
  • STEP_WITHOUT_HYSTERESIS - em passos sem característica regulação de histerese;
  • LINEAR - característica de regulação linear;
  • NON_LINEAR - característica de regulação não linear (não implementada nesta versão);


  void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

Parâmetros de entrada são os seguintes:

  • _min_slope - ângulo e declive da curva de equilíbrio que correspondente a negociar com o volume mínimo;
  • _max_slope - ângulo e declive da curva de equilíbrio que correspondente a negociar com o volume máximo;
  • _centr_slope - ângulo e declive da curva de equilíbrio que correspondente a característica de regulação em passos sem histerese;


O volume é calculado usando o seguinte método:

//---------------------------------------------------------------------
//  Get trade volume:
//---------------------------------------------------------------------
double
TBalanceSlopeControl::CalcTradeLots( double _min_lots, double _max_lots )
{
//  Try to calculate slope of the balance curve:
  double    current_slope = CalcSlope( );

//  If the specified amount of trades is not accumulated yet, trade with minimal volume:
  if( GetRealTrades( ) < GetSlopePoints( ))
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots( _min_lots );
    return( rejected_lots );
  }

//  If the regulation function is stepped without hysteresis:
  if( control_type == STEP_WITHOUT_HYSTERESIS )
  {
    if( current_slope < centr_slope )
    {
      current_lots_state = LOTS_REJECTED;
      rejected_lots = trade_symbol.NormalizeLots( _min_lots );
      return( rejected_lots );
    }
    else
    {
      current_lots_state = LOTS_NORMAL;
      normal_lots = trade_symbol.NormalizeLots( _max_lots );
      return( normal_lots );
    }
  }

//  If the slope of linear regression for the balance curve is less than the allowed one:
  if( current_slope < min_slope )
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots( _min_lots );
    return( rejected_lots );
  }

//  If the slope of linear regression for the balance curve is greater than specified:
  if( current_slope > max_slope )
  {
    current_lots_state = LOTS_NORMAL;
    normal_lots = trade_symbol.NormalizeLots( _max_lots );
    return( normal_lots );
  }

//  The slope of linear regression for the balance curve is within specified borders (intermediate state):
  current_lots_state = LOTS_INTERMEDIATE;

//  Calculate the value of intermediate volume:
  intermed_lots = CalcIntermediateLots( _min_lots, _max_lots, current_slope );
  intermed_lots = trade_symbol.NormalizeLots( intermed_lots );

  return( intermed_lots );
}

Pontos significativos principais da implementação do método TBalanceSlopeControl::CalcTradeLots são os seguintes:

  • Até a quantidade mínimo especificada de transações ser acumulada, negociar com o volume mínimo. é lógico, pois não é conhecido, qual o período (lucrativo ou não) em que o Conselheiro Especialista está, logo após que você o configurar para negociação.
  • Se a função de regulação é a em passos sem histerese, então para definir o ângulo de mudança entre os modos de negociação por meio do método TBalanceSlopeControl::SetControlParams você deve usar apenar o parâmetro _centr_slope. Os parâmetros _min_slope e _max_slope são ignorados. Isto é feito para executar a otimização correta por este parâmetro no testador de estratégias do MetaTrader 5.

Dependendo do ângulo calculado do declive, negociar com volume mínimo, máximo ou intermediário. O volume intermediário é calculado pelo método simples - TBalanceSlopeControl::CalcIntermediateLots. Este método é protegido e usado dentro da classe. Seu código é como mostrado abaixo:

//---------------------------------------------------------------------
//  Calculation of intermediate volume:
//---------------------------------------------------------------------
double
TBalanceSlopeControl::CalcIntermediateLots( double _min_lots, double _max_lots, double _slope )
{
  double    lots;

//  If the regulation function is stepped with hysteresis:
  if( control_type == STEP_WITH_HYSTERESISH )
  {
    if( current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope )
    {
      lots = _min_lots;
    }
    else if( current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope )
    {
      lots = _max_lots;
    }
  }
//  If the regulation function is linear:
  else if( control_type == LINEAR )
  {
    double  a = ( _max_lots - _min_lots ) / ( max_slope - min_slope );
    double  b = normal_lots - a * .max_slope;
    lots = a * _slope + b;
  }
//  If the regulation function is non-linear ( not implemented yet ):
  else if( control_type == NON_LINEAR )
  {
    lots = _min_lots;
  }
//  If the regulation function is unknown:
  else
  {
    lots = _min_lots;
  }

  return( lots );
}

Outros métodos desta classe não requerem qualquer descrição.


Exemplo de Incorporação do Sistema em um Consultor Especialista

Vamos considerar o processo de implementação do sistema de controle do ângulo da curva de equilíbrio em um Consultor Especialista passo a passo.


Passo 1 - adicionar a instrução para conectar a biblioteca desenvolvida ao Consultor Especialista:

#include  <BalanceSlopeControl.mqh>


Passo 2 - adicionar as variáveis externar passa configurar parâmetros do sistema de controle do declive da linha de equilíbrio para o Consultor Especialista:

//---------------------------------------------------------------------
//  Parameters of the system of controlling the slope of the balance curve;
//---------------------------------------------------------------------
enum SetLogic 
{
  No = 0,
  Yes = 1,
};
//---------------------------------------------------------------------
input SetLogic     UseAutoBalanceControl = No;
//---------------------------------------------------------------------
input ControlType  BalanceControlType = STEP_WITHOUT_HYSTERESIS;
//---------------------------------------------------------------------
//  Amount of last trades for calculation of LR of the balance curve:
input int          TradesNumberToCalcLR = 3;
//---------------------------------------------------------------------
//  Slope of LR to decrease the volume to minimum:
input double       LRKoeffForRejectLots = -0.030;
//---------------------------------------------------------------------
//  Slope of LR to restore the normal mode of trading:
input double       LRKoeffForRestoreLots = 0.050;
//---------------------------------------------------------------------
//  Slope of LR to work in the intermediate mode:
input double       LRKoeffForIntermedLots = -0.020;
//---------------------------------------------------------------------
//  Decrease the initial volume to the specified value when the LR is inclined down
input double       RejectedLots = 0.10;
//---------------------------------------------------------------------
//  Normal work volume in the mode of MM with fixed volume:
input double       NormalLots = 1.0;


Passo 3 - adicionar o objeto do tipo TBalanceSlopeControl no Consultor Especialista:

TBalanceSlopeControl  BalanceControl;

Esta declaração pode ser adicionada no início do Consultor Especialista, antes das definições de funções.


Passo 4 - adicionar o código para inicialização do sistema de controle da curva de equilíbrio para a função OnInit do Consultor Especialista:

//  Adjust our system of controlling the slope of the balance curve:
  BalanceControl.SetTradeSymbol( Symbol( ));
  BalanceControl.SetControlType( BalanceControlType );
  BalanceControl.SetControlParams( LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots );
  BalanceControl.SetSlopePoints( TradesNumberToCalcLR );
  BalanceControl.SetFiltrParams( 0, -1, 0 );
  BalanceControl.SetMonitoringBeginDate( 0 );


Passo 5 - adicionar o método de chamada para atualizar a informação de mercado atual para a função OnTick do Consultor Especialista:

//  Refresh market information:
  BalanceControl.RefreshSymbolInfo( );

A chamada deste método pode ser adicionada ao início da função OnTick ou depois da checagem do novo limite que vem (para Consultores Especialistas com tal checagem).


Passo 6 - adicionar o código para cálculo do volume atual antes do código onde posições são abertas:

  if( UseAutoBalanceControl == Yes )
  {
    current_lots = BalanceControl.CalcTradeLots( RejectedLots, NormalLots );
  }
  else
  {
    current_lots = NormalLots;
  }

Se um sistema de Administração de Dinheiro é usado no Consultor Especialista, então ao invés de NormalLots você deve escrever o método TBalanceSlopeControl::CalcTradeLots - o volume atual calculado pelo sistema AD do Consultor Especialista.

Teste o Consultor Especialista BSCS-TestExpert.mq5 com o sistema embutido descrito acima anexado a este artigo. Princípio de sua operação é baseado na interseção de níveis do indicador CCI. Este Consultor Especialista é desenvolvido para testes e não é apropriado para contas reais. Iremos testá-lo no período H4 (2008.07.01 - 2010.09.01) de EURUSD.

Vamos analisar o resultado de trabalhar com este CE. A tabela de mudança no equilíbrio com o sistema de controle do declive desativada é mostrada abaixo. Para visualizá-la, defina o valor No para o parâmetro externo UseAutoBalanceControl.

Gráfico inicial da mudança de equilíbrio

Figura 4. Gráfico inicial da mudança de equilíbrio


Agora defina o parâmetro externo UseAutoBalanceControl para Yes e teste o Consultor Especialista. Você vai obter o gráfico com o sistema de controle do declive do equilíbrio ativado.

Gráfico da mudança do equilíbrio com o sistema de controle ativado

Figura 5. Gráfico da mudança do equilíbrio com o sistema de controle ativado

Você pode ver que a maioria dos períodos no gráfico superior (fig. 4) parecem como cortados, e eles tem uma forma plana no gráfico inferior (fig. 5). Este é o resultado de trabalhar o nosso sistema. Você pode comparar os parâmetros principais do trabalho do Consultor Especialista:

Parâmetro
UseAutoBalanceControl = No UseAutoBalanceControl = Yes
Lucro líquido total: 18 378.0017 261.73
Fator de lucro: 1,471,81
Fator de recuperação: 2,66 3,74
Retorno esperado: 117,81110,65
Rebaixamento absoluto de equilíbrio:1 310.50131,05
Rebaixamento absoluto de igualdade: 1 390.50514,85
Máximo rebaixamento de equilíbrio: 5 569.50 (5.04%) 3 762.15 (3.35%)
Máximo rebaixamento de igualdade:6 899.50 (6.19%)
4 609.60 (4.08%)


Melhores parâmetros entre os comparados estão destacados com a cor verde. Lucro e retorno esperados diminuíram ligeiramente; este é o outro lado da regulação, que aparece como um resultado de atrasos da troca entre estados de volume de trabalho. Ao todo, há uma melhora nas taxas de trabalho do Consultor Especialista. Especialmente, melhoras no rebaixamento e fator de lucro.


Conclusão

Vejo diversas maneiras de melhorar este sistema:
  • Usando negociações virtuais quando o Consultor Especialista entrar em um período desfavorável de trabalho. Então o volume normal de trabalho não importará mais. Irá permitir reduzir o rebaixamento.
  • Usando algoritmos mais complexos para determinar o estado atual do trabalho do Consultor Especialista (lucrativo ou não). Por exemplo, podemos tentar aplicar uma rede neural para tal análise. Investigação adicional é necessária neste caso, é claro.

Assim, consideramos o princípio e resultado do trabalhar o sistema, o que permite melhorar características de qualidade de um Consultor Especialista. Operação conjunta com o sistema de administração de dinheiro, em alguns casos, permite aumentar a lucratividade sem aumentar o risco.

Lembro você mais uma vez: nenhum sistema auxiliar pode fazer um Consultor Especialista lucrativo de um perdedor.

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

Arquivos anexados |
Como criar rapidamente um Consultor Especialista para o Campeonato de Negociações Automáticas 2010 Como criar rapidamente um Consultor Especialista para o Campeonato de Negociações Automáticas 2010
A fim de desenvolver um especialista para participar no Automated Trading Championship 2010 (Campeonato de Negociações Automáticas 2010), vamos usar um modelo de um conselheiro especialista pronto. Até mesmo um programador MQL5 iniciante será capaz desta tarefa, porque para suas estratégias as classes básicas, funções e modelos já estão desenvolvidos. é suficiente escrever uma quantidade mínima de código para implementar sua ideia de negociação.
O método ideal para calcular o volume da posição total pelo número mágico especificado O método ideal para calcular o volume da posição total pelo número mágico especificado
O problema do cálculo do volume de posição total do símbolo especificado e número mágico é considerado neste artigo. O método proposto requer apenas a parte mínima necessária do histórico de negócios, descobre o tempo mais próximo quando a posição total foi igual a zero, e realiza os cálculos com os negócios recentes. O trabalho com variáveis globais do terminal de cliente também é considerado.
Usando a função TesterWithdrawal() para Modelar as Retiradas de Lucro Usando a função TesterWithdrawal() para Modelar as Retiradas de Lucro
Este artigo descreve a utilização da função TesterWithDrawal() para estimar riscos nos sistemas de negócio que implicam na remoção de uma determinada parte dos ativos durante sua operação. Além disso, ele descreve o efeito desta função no algoritmo de cálculo do rebaixamento de igualdade no Strategy tester. Esta função é útil quando otimizar parâmetros de seus Expert Advisors.
Como Encomendar um Robô de Negociação em MQL5 e MQL4 Como Encomendar um Robô de Negociação em MQL5 e MQL4
A "Freelance" é o maior serviço freelance para a encomenda de robôs de negociação em MQL4 e indicadores técnicos. Centenas de desenvolvedores profissionais estão prontos para desenvolver aplicativos de negociação personalizados para a plataforma MetaTrader 4/5.