OnTester

Se llama en los expertos al suceder el evento Tester para el procesamiento de las acciones necesarias al finalizar la simulación.

double  OnTester(void);

Valor retornado

Valor del criterio de optimización personalizado para analizar los resultados de la simulación.  

Observación

La función OnTester() puede ser usada solo en expertos en la simulación, y ha sido pensada en primer lugar para calcular un cierto valor usado como criterio "Custom max" al optimizar los parámetros de entrada.

Al realizar la optimización genética, la clasificación de los resultados dentro de los límites de una generación se realiza con carácter descendente. Esto significa que se considerarán los mejores resultados desde el punto de vista del criterio de optimización aquellos que tengan un mayor valor. Los mejores criterios desde el punto de vista de la clasificación, se ubican al final y como consecuencia son descartados y no se usan en la siguiente generación.

De esta forma, con la ayuda de la función OnTester() es posible no solo crear y guardar informes propios de los resultados de la simulación, sino también gestionar el transcurso de la optimización para buscar los mejores parámetros de la estrategia comercial.

Ejemplo de cálculo del criterio de optimización personalizado. La idea consiste en calcular la regresión lineal del gráfico de balance, y es descrita en el artículo Optimizamos la estrategia del gráfico de balance y comparamos los resultados con el criterio "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 "Ejemplo de asesor con el manejador OnTester()"
#property description "En calidad de criterio de optimización personalizado "
#property description "se retorna el coeficiente de regresión lineal del gráfico de balance,"
#property description "dividido entre el error medio cuadrático de la desviación"
//--- incluimos la clase de operaciones comerciales
#include <Trade\Trade.mqh>
//--- parámetros de entrada del experto
input double Lots               = 0.1;     // Volumen
input int    Slippage           = 10;      // Deslizamiento permitido
input int    MovingPeriod       = 80;      // Periodo de la media móvil
input int    MovingShift        = 6;       // Desplazmiento de la media móvil
//--- variables globales
int    IndicatorHandle=0;  // manejador del indicador
bool   IsHedging=false;    // bandera de la cuenta
CTrade trade;              // para realizar transacciones comerciales
//--- 
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| Comprobando las condiciones de apertura de posición              |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- comerciamos solo al inicio de una nueva barra
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- volumen de ticks
   if(rt[1].tick_volume>1)
      return;
//--- obtenemos los valores de la media móvil
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- comprobamos la presencia de la señal
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- la vela se abierto por encima, y se ha cerrado por debajo de la media móvil
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_BUY;    // señal de compra
   else // la vela se ha abierto por debajo, y se ha cerrado por encima de la media móvil
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_SELL;// señal de venta
     }
//--- comprobaciones adicionales
   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);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Comprobando las condiciones de cierre de posición                |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- comerciamos solo al inicio de una nueva barra
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- obtenemos los valores de la media móvil
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- la posición ya había sido elegida anteriormente con la ayuda de PositionSelect()
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);
//--- la vela se ha abierto por encima, y se ha cerrado por debajo de la media móvil
   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
//--- la vela se ha abierto por debajo, y se ha cerrado por encima de la media móvil: cerramos la posición larga
   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- comprobaciones adicionales
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         trade.PositionClose(_Symbol,Slippage);
     }
//---
  }
//+------------------------------------------------------------------+
//| Elegimos la posición considerando el tipo de cuenta: Netting o Hedging
//+------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- eligiendo la posición para la cuenta de 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;
           }
        }
     }
//--- eligiendo la posición para la cuenta de Netting
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---проверка Magic number
     }
//--- resultado de la ejecución
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- establecemos el tipo de comercio: Netting o Hedging
   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- inicializamos el objeto para controlar correctamente las posiciones
   trade.SetExpertMagicNumber(EA_MAGIC);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetDeviationInPoints(Slippage);
//--- creamos el indicador Moving Average
   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(IndicatorHandle==INVALID_HANDLE)
     {
      printf("Error al crear el indicador iMA");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- si la posición ya está abierta, comprobamos la condición de cierre
   if(SelectPosition())
      CheckForClose();
// comprobamos la condición de cierre de la posición
   CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//--- valor del criterio de optimización personalizado (cuanto mayor sea, mejor)
   double ret=0.0;
//--- obtenemos los resultados de las transacciones en una matriz
   double array[];
   double trades_volume;
   GetTradeResultsToArray(array,trades_volume);
   int trades=ArraySize(array);
//--- si hay menos de 10 transacciones, la simulación no habrá dado buenos resultados
   if(trades<10)
      return (0);
//--- resultado medio por transacción
   double average_pl=0;
   for(int i=0;i<ArraySize(array);i++)
      average_pl+=array[i];
   average_pl/=trades;
//--- mostramos un mensaje para el modo de simulación única
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
      PrintFormat("%s: Transacciones=%d, Beneficio medio=%.2f",__FUNCTION__,trades,average_pl);
//--- calculamos los coeficientes de regresión lineal para el gráfico de beneficio
   double a,b,std_error;
   double chart[];
   if(!CalculateLinearRegression(array,chart,a,b))
      return (0);
//--- calculamos el error de desviación del gráfico con respecto a la línea de regresión
   if(!CalculateStdError(chart,a,b,std_error))
      return (0);
//--- calculamos la relación del beneficio de tendencia con respecto a la desviación media cuadrada
   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- retornamos el valor del criterio de optimización personalizado
   return(ret);
  }
//+------------------------------------------------------------------+
//| Recibir la matriz de beneficio/pérdidas de las transacciones     |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
  {
//--- solicitamos la historia comercial completa
   if(!HistorySelect(0,TimeCurrent()))
      return (false);
   uint total_deals=HistoryDealsTotal();
   volume=0;
//--- establecemos el tamaño inicial de la matriz con espacio de sobra, según el número de transacciones en la historia
   ArrayResize(pl_results,total_deals);
//--- contador de transacciones que registra el resultado comercial: beneficio o pérdidas
   int counter=0;
   ulong ticket_history_deal=0;
//--- iteramos por todas las transacciones
   for(uint i=0;i<total_deals;i++)
     {
      //--- elegimos una transacción 
      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);
         //--- nos interesan solo las operaciones comerciales        
         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
            continue;
         //--- solo las transacciones que registran beneficio/pérdidas
         if(deal_entry!=DEAL_ENTRY_IN)
           {
            //--- anotamos el resultado comercial en la matriz e incrementamos el contador de transacciones
            pl_results[counter]=deal_profit;
            volume+=deal_volume;
            counter++;
           }
        }
     }
//--- establecemos el tamaño definitivo de la matriz
   ArrayResize(pl_results,counter);
   return (true);
  }
//+------------------------------------------------------------------+
//| Calcula una regresión lineal del tipo y=a*x+b                    |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double  &change[],double &chartline[],
                               double  &a_coef,double  &b_coef)
  {
//--- comprobamos que los datos sean suficientes
   if(ArraySize(change)<3)
      return (false);
//--- creamos una matriz del gráfico con acumulación
   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];
//--- ahora calculamos los coeficientes de regresión
   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 el error medio cuadrático de la desviación para las a y b establecidas    
//+------------------------------------------------------------------+
bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)
  {
//--- suma de cuadrados del error
   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);
  }

Ver también

Simulación de estrategias comerciales, TesterHideIndicators, Trabajo con los resultados de la optimización, TesterStatistics, OnTesterInit, OnTesterDeinit, OnTesterPass, MQL_TESTER, MQL_OPTIMIZATION, FileOpen, FileWrite, FileLoad, FileSave