OnTester

É chamada em EAs quando ocorre o evento Tester para executar as ações necessárias no final do teste.

double  OnTester(void);

Valor retornado

Valor de um critério de otimização personalizado para avaliar os resultados do teste.  

Observação

A função OnTester() só pode ser usada em EAs durante o teste e é projetada principalmente para calcular um determinado valor a ser usado como critério "Custom max" ao otimizar parâmetros de entrada.

Durante a otimização genética, a classificação de resultados numa geração é feita em ordem decrescente. Isso significa que, do ponto de vista do critério de otimização, os melhores são os resultados com o maior valor. Os piores valores para esta classificação são colocados no final e, posteriormente, descartados, não participando da formação da próxima geração.

Assim, usando a função OnTester(), você pode não apenas criar e salvar seus próprios relatórios de resultados de testes, mas também controlar o processo de otimização para encontrar os melhores parâmetros da estratégia de negociação.

Exemplo de cálculo de um critério de otimização personalizado. A ideia é calcular a regressão linear do gráfico de saldo descrita no artigo Otimizando uma estratégia usando o gráfico do saldo e comparando os resultados com o critério "Balance + max Sharpe Ratio"

//+------------------------------------------------------------------+
//|                                              OnTester_Sample.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "Exemplo de EA com manipulador OnTester()"
#property description "Como critério de otimização personalizado "
#property description "é retornado o coeficiente de regressão linear do gráfico de saldo,"
#property description "dividido pelo erro quadrático médio do desvio"
//--- conectamos a classe de operações de negociação
#include <Trade\Trade.mqh>
//--- parâmetros de entrada do EA
input double Lots               = 0.1;     // Volume
input int    Slippage           = 10;      // Derrapagem admissível
input int    MovingPeriod       = 80;      // Período de média móvel
input int    MovingShift        = 6;       // Deslocamento de média móvel
//--- variáveis globais
int    IndicatorHandle=0;  // identificador do indicador
bool   IsHedging=false;    //sinal da conta 
CTrade trade;              // para realizar operações de negociação
//--- 
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| Verificando as condições para abertura da posição                |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- negociamos apenas no início da barra nova
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- volume de ticks
   if(rt[1].tick_volume>1)
      return;
//--- obtemos os valores da média móvel
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- verificamos a presença do sinal
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- a vela se abriu mais alto, mas fechou abaixo da média móvel
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_BUY;    // sinal de compra
   else // a vela se abriu mais abaixo, mas fechou acima da média móvel
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_SELL;// sinal de venda
     }
//--- verificações adicionais
   if(signal!=WRONG_VALUE)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
        {
         double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);
         trade.PositionOpen(_Symbol,signal,Lots,price,0,0);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Verificando as condições de fechamento da posição                |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- negociamos apenas no início da barra nova
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- obtemos os valores da média móvel
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- posição já foi selecionada usando PositionSelect()
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);
//--- a vela se abriu mais acima, mas fechou abaixo da média móvel - fechamos a posição curta
   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
//--- a vela se abriu mais abaixo, mas fechou acima da média móvel - fechamos a posição longa
   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- verificações adicionais
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         trade.PositionClose(_Symbol,Slippage);
     }
//---
  }
//+------------------------------------------------------------------+
//| Selecionamos a posição com base no tipo de conta: Netting ou Hedging        
//+------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- seleção da posição para a conta Hedging
   if(IsHedging)
     {
      uint total=PositionsTotal();
      for(uint i=0; i<total; i++)
        {
         string position_symbol=PositionGetSymbol(i);
         if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))
           {
            res=true;
            break;
           }
        }
     }
//--- seleção da posição para a conta Netting
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---verificação do Magic number
     }
//--- resultado da execução
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- definimos o tipo de negociação: Netting ou Hedging
   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- inicializamos o objeto para o controle correto das posições
   trade.SetExpertMagicNumber(EA_MAGIC);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetDeviationInPoints(Slippage);
//--- criamos o indicador Moving Average
   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(IndicatorHandle==INVALID_HANDLE)
     {
      printf("Erro ao criar o indicador iMA");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- se a posição já estiver aberta, verificamos condição de fechamento
   if(SelectPosition())
      CheckForClose();
// verificamos a condição para abertura da posição
   CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//--- valor do critério de otimização personalizado (quanto mais, melhor)
   double ret=0.0;
//--- obtemos os resultados dos trades na matriz
   double array[];
   double trades_volume;
   GetTradeResultsToArray(array,trades_volume);
   int trades=ArraySize(array);
//--- se há menos de 10 trades, o teste não gerou resultados positivos
   if(trades<10)
      return (0);
//--- resultado médio no trade
   double average_pl=0;
   for(int i=0;i<ArraySize(array);i++)
      average_pl+=array[i];
   average_pl/=trades;
//--- exibimos uma mensagem para o modo de teste único
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
      PrintFormat("%s: Trades=%d, Lucro médio=%.2f",__FUNCTION__,trades,average_pl);
//--- calculamos os coeficientes de regressão linear para o gráfico de lucro
   double a,b,std_error;
   double chart[];
   if(!CalculateLinearRegression(array,chart,a,b))
      return (0);
//--- calculamos o erro de desvio do gráfico em relação à linha de regressão
   if(!CalculateStdError(chart,a,b,std_error))
      return (0);
//--- calculamos o rácio do lucro de tendência em relação ao desvio padrão
   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- retornamos o valor do critério de otimização personalizado
   return(ret);
  }
//+------------------------------------------------------------------+
//| Obtendo a matriz de lucros/perdas de transações                  |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
  {
//--- consultamos o histórico de negociação completo
   if(!HistorySelect(0,TimeCurrent()))
      return (false);
   uint total_deals=HistoryDealsTotal();
   volume=0;
//--- definimos o tamanho inicial da matriz pelo número de transações no histórico
   ArrayResize(pl_results,total_deals);
//--- contador de trades que fixam o resultado da negociação - lucro ou perda
   int counter=0;
   ulong ticket_history_deal=0;
//--- passar por todos os trades
   for(uint i=0;i<total_deals;i++)
     {
      //--- selecionamos o trade 
      if((ticket_history_deal=HistoryDealGetTicket(i))>0)
        {
         ENUM_DEAL_ENTRY deal_entry  =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);
         long            deal_type   =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);
         double          deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);
         double          deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);
         //--- estamos interessados apenas em operações de negociação        
         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
            continue;
         //--- somente trades com fixação de lucro/perda
         if(deal_entry!=DEAL_ENTRY_IN)
           {
            //--- escrevemos o resultado da negociação na matriz e aumentamos o contador de trades
            pl_results[counter]=deal_profit;
            volume+=deal_volume;
            counter++;
           }
        }
     }
//--- definimos o tamanho final da matriz
   ArrayResize(pl_results,counter);
   return (true);
  }
//+------------------------------------------------------------------+
//| Calculando a regressão linear de tipo y=a*x+b                    |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double  &change[],double &chartline[],
                               double  &a_coef,double  &b_coef)
  {
//--- verificamos se há suficientes dados
   if(ArraySize(change)<3)
      return (false);
//--- criamos a matriz do gráfico com acumulação
   int N=ArraySize(change);
   ArrayResize(chartline,N);
   chartline[0]=change[0];
   for(int i=1;i<N;i++)
      chartline[i]=chartline[i-1]+change[i];
//--- agora calculamos os coeficientes de regressão
   double x=0,y=0,x2=0,xy=0;
   for(int i=0;i<N;i++)
     {
      x=x+i;
      y=y+chartline[i];
      xy=xy+i*chartline[i];
      x2=x2+i*i;
     }
   a_coef=(N*xy-x*y)/(N*x2-x*x);
   b_coef=(y-a_coef*x)/N;
//---
   return (true);
  }
//+------------------------------------------------------------------+
//|  Calcula o erro quadrático médio do desvio para os a e b definidos    
//+------------------------------------------------------------------+
bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)
  {
//--- soma dos quadrados dos erros
   double error=0;
   int N=ArraySize(data);
   if(N<=2)
      return (false);
   for(int i=0;i<N;i++)
      error+=MathPow(a_coef*i+b_coef-data[i],2);
   std_err=MathSqrt(error/(N-2));
//--- 
   return (true);
  }

Veja também

Teste de estratégias de negociação, TesterHideIndicators, Trabalhando com os resultados da otimização, TesterStatistics, OnTesterInit, OnTesterDeinit, OnTesterPass, MQL_TESTER, MQL_OPTIMIZATION, FileOpen, FileWrite, FileLoad, FileSave