Cómo evaluar los resultados de los Asesores Expertos

Slava | 28 enero, 2016


Primero de todo, vamos a explicar el procedimiento de las pruebas en unas cuantas líneas. Antes de comenzar con las pruebas, el subsistema carga el asesor experto, estableciendo los parámetros previamente definidos por el usuario, y llama a la función init(). A continuación el Probador recorre la secuencia generada y llama cada vez a la función start(). Al terminar la secuencia de pruebas, el Probador llama a la función deinit(). Entonces está disponible el historial de trading completo, generado durante las pruebas. La eficiencia del asesor experto se puede analizar en ese momento.

La función CalculateSummary proporciona los cálculos de los resultados de las pruebas, es decir, los datos del informe estándar del Probador de Estrategias.

void CalculateSummary(double initial_deposit)
  {
   int    sequence=0, profitseqs=0, lossseqs=0;
   double sequential=0.0, prevprofit=EMPTY_VALUE, drawdownpercent, drawdown;
   double maxpeak=initial_deposit, minpeak=initial_deposit, balance=initial_deposit;
   int    trades_total=HistoryTotal();
//---- inicializamos los resúmenes
   InitializeSummaries(initial_deposit);
//----
   for(int i=0; i<trades_total; i++)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      int type=OrderType();
      //---- depósito inicial no considerado
      if(i==0 && type==OP_BALANCE) continue;
      //---- calculamos el beneficio
      double profit=OrderProfit()+OrderCommission()+OrderSwap();
      balance+=profit;
      //---- comprobación de la reducción
      if(maxpeak<balance)
        {
         drawdown=maxpeak-minpeak;
         if(maxpeak!=0.0)
           {
            drawdownpercent=drawdown/maxpeak*100.0;
            if(RelDrawdownPercent<drawdownpercent)
              {
               RelDrawdownPercent=drawdownpercent;
               RelDrawdown=drawdown;
              }
           }
         if(MaxDrawdown<drawdown)
           {
            MaxDrawdown=drawdown;
            if(maxpeak!=0.0) MaxDrawdownPercent=MaxDrawdown/maxpeak*100.0;
            else MaxDrawdownPercent=100.0;
           }
         maxpeak=balance;
         minpeak=balance;
        }
      if(minpeak>balance) minpeak=balance;
      if(MaxLoss>balance) MaxLoss=balance;
      //---- solo órdenes de mercado
      if(type!=OP_BUY && type!=OP_SELL) continue;
      SummaryProfit+=profit;
      SummaryTrades++;
      if(type==OP_BUY) LongTrades++;
      else             ShortTrades++;
      //---- operaciones perdedoras
      if(profit<0)
        {
         LossTrades++;
         GrossLoss+=profit;
         if(MinProfit>profit) MinProfit=profit;
         //---- cambio de suerte
         if(prevprofit!=EMPTY_VALUE && prevprofit>=0)
           {
            if(ConProfitTrades1<sequence ||
               (ConProfitTrades1==sequence && ConProfit2<sequential))
              {
               ConProfitTrades1=sequence;
               ConProfit1=sequential;
              }
            if(ConProfit2<sequential ||
               (ConProfit2==sequential && ConProfitTrades1<sequence))
              {
               ConProfit2=sequential;
               ConProfitTrades2=sequence;
              }
            profitseqs++;
            AvgConWinners+=sequence;
            sequence=0;
            sequential=0.0;
           }
        }
      //---- operaciones ganadoras (profit>=0)
      else
        {
         ProfitTrades++;
         if(type==OP_BUY)  WinLongTrades++;
         if(type==OP_SELL) WinShortTrades++;
         GrossProfit+=profit;
         if(MaxProfit<profit) MaxProfit=profit;
         //---- cambio de suerte
         if(prevprofit!=EMPTY_VALUE && prevprofit<0)
           {
            if(ConLossTrades1<sequence ||
               (ConLossTrades1==sequence && ConLoss2>sequential))
              {
               ConLossTrades1=sequence;
               ConLoss1=sequential;
              }
            if(ConLoss2>sequential ||
               (ConLoss2==sequential && ConLossTrades1<sequence))
              {
               ConLoss2=sequential;
               ConLossTrades2=sequence;
              }
            lossseqs++;
            AvgConLosers+=sequence;
            sequence=0;
            sequential=0.0;
           }
        }
      sequence++;
      sequential+=profit;
      //----
      prevprofit=profit;
     }
//---- comprobación de reducción final
   drawdown=maxpeak-minpeak;
   if(maxpeak!=0.0)
     {
      drawdownpercent=drawdown/maxpeak*100.0;
      if(RelDrawdownPercent<drawdownpercent)
        {
         RelDrawdownPercent=drawdownpercent;
         RelDrawdown=drawdown;
        }
     }
   if(MaxDrawdown<drawdown)
     {
      MaxDrawdown=drawdown;
      if(maxpeak!=0) MaxDrawdownPercent=MaxDrawdown/maxpeak*100.0;
      else MaxDrawdownPercent=100.0;
     }
//---- consideramos la última transacción
   if(prevprofit!=EMPTY_VALUE)
     {
      profit=prevprofit;
      if(profit<0)
        {
         if(ConLossTrades1<sequence ||
            (ConLossTrades1==sequence && ConLoss2>sequential))
           {
            ConLossTrades1=sequence;
            ConLoss1=sequential;
           }
         if(ConLoss2>sequential ||
            (ConLoss2==sequential && ConLossTrades1<sequence))
           {
            ConLoss2=sequential;
            ConLossTrades2=sequence;
           }
         lossseqs++;
         AvgConLosers+=sequence;
        }
      else
        {
         if(ConProfitTrades1<sequence ||
            (ConProfitTrades1==sequence && ConProfit2<sequential))
           {
            ConProfitTrades1=sequence;
            ConProfit1=sequential;
           }
         if(ConProfit2<sequential ||
            (ConProfit2==sequential && ConProfitTrades1<sequence))
           {
            ConProfit2=sequential;
            ConProfitTrades2=sequence;
           }
         profitseqs++;
         AvgConWinners+=sequence;
        }
     }
//---- recopilación hecha
   double dnum, profitkoef=0.0, losskoef=0.0, avgprofit=0.0, avgloss=0.0;
//---- ganancias y pérdidas consecutivas medias
   dnum=AvgConWinners;
   if(profitseqs>0) AvgConWinners=dnum/profitseqs+0.5;
   dnum=AvgConLosers;
   if(lossseqs>0)   AvgConLosers=dnum/lossseqs+0.5;
//---- valores absolutos
   if(GrossLoss<0.0) GrossLoss*=-1.0;
   if(MinProfit<0.0) MinProfit*=-1.0;
   if(ConLoss1<0.0)  ConLoss1*=-1.0;
   if(ConLoss2<0.0)  ConLoss2*=-1.0;
//---- factor de beneficio
   if(GrossLoss>0.0) ProfitFactor=GrossProfit/GrossLoss;
//---- rentabilidad esperada
   if(ProfitTrades>0) avgprofit=GrossProfit/ProfitTrades;
   if(LossTrades>0)   avgloss  =GrossLoss/LossTrades;
   if(SummaryTrades>0)
     {
      profitkoef=1.0*ProfitTrades/SummaryTrades;
      losskoef=1.0*LossTrades/SummaryTrades;
      ExpectedPayoff=profitkoef*avgprofit-losskoef*avgloss;
     }
//---- disminución absoluta
   AbsoluteDrawdown=initial_deposit-MaxLoss;
  }
Hay que conocer el valor inicial del depósito para que los cálculos sean correctos. Para ello hay que llamar a la función AccountBalance() en la función init(). Esto calculará el valor del depósito justo cuando comiencen las pruebas.
void init()
  {
   ExtInitialDeposit=AccountBalance();
  }
El beneficio se calcula en la divisa del depósito, tanto en la función CalculateSummary como en el informe estándar. Los resultados de trading tales como la "Mayor operación de beneficios" o la "Pérdida consecutiva máxima" se calculan en función del beneficio, y también se miden en términos de dinero. Por lo tanto, recalcular el beneficio en puntos es sencillo.
...
      //---- solo órdenes de mercado
      if(type!=OP_BUY && type!=OP_SELL) continue;
      //---- calculamos el beneficio en puntos
      profit=(OrderClosePrice()-OrderOpenPrice())/MarketInfo(OrderSymbol(),MODE_POINT);
      SummaryProfit+=profit;
...
Los resultados obtenidos se escriben en el archivo del informe con la función WriteReport.
void WriteReport(string report_name)
  {
   int handle=FileOpen(report_name,FILE_CSV|FILE_WRITE,'\t');
   if(handle<1) return;
//----
   FileWrite(handle,"Depósito inicial          ",InitialDeposit);
   FileWrite(handle,"Beneficio neto total      ",SummaryProfit);
   FileWrite(handle,"Beneficio bruto           ",GrossProfit);
   FileWrite(handle,"Pérdida bruta             ",GrossLoss);
   if(GrossLoss>0.0)
      FileWrite(handle,"Factor de beneficio       ",ProfitFactor);
   FileWrite(handle,"Rentabilidad esperada     ",ExpectedPayoff);
   FileWrite(handle,"Disminución absoluta      ",AbsoluteDrawdown);
   FileWrite(handle,"Disminución máxima        ",
                     MaxDrawdown,
                     StringConcatenate("(",MaxDrawdownPercent,"%)"));
   FileWrite(handle,"Disminución relativa      ",
                     StringConcatenate(RelDrawdownPercent,"%"),
                     StringConcatenate("(",RelDrawdown,")"));
   FileWrite(handle,"Total de operaciones         ",SummaryTrades);
   if(ShortTrades>0)
      FileWrite(handle,"Posiciones cortas (ganado %)    ",
                        ShortTrades,
                        StringConcatenate("(",100.0*WinShortTrades/ShortTrades,"%)"));
   if(LongTrades>0)
      FileWrite(handle,"Posiciones largas (ganado %)     ",
                        LongTrades,
                        StringConcatenate("(",100.0*WinLongTrades/LongTrades,"%)"));
   if(ProfitTrades>0)
      FileWrite(handle,"Operaciones de beneficios (% del total)",
                        ProfitTrades,
                        StringConcatenate("(",100.0*ProfitTrades/SummaryTrades,"%)"));
   if(LossTrades>0)
      FileWrite(handle,"Operaciones de pérdidas (% del total)  ",
                        LossTrades,
                        StringConcatenate("(",100.0*LossTrades/SummaryTrades,"%)"));
   FileWrite(handle,"Mayor operación de beneficios      ",MaxProfit);
   FileWrite(handle,"Mayor operación de pérdidas        ",-MinProfit);
   if(ProfitTrades>0)
      FileWrite(handle,"Operación de beneficios media      ",GrossProfit/ProfitTrades);
   if(LossTrades>0)
      FileWrite(handle,"Operación de pérdidas media        ",-GrossLoss/LossTrades);
   FileWrite(handle,"Media de ganancias consecutivas  ",AvgConWinners);
   FileWrite(handle,"Media de pérdidas consecutivas",AvgConLosers);
   FileWrite(handle,"Ganancias consecutivas máximas (beneficios en dinero)",
                     ConProfitTrades1,
                     StringConcatenate("(",ConProfit1,")"));
   FileWrite(handle,"Pérdidas consecutivas máximas (pérdidas en dinero)",
                     ConLossTrades1,
                     StringConcatenate("(",-ConLoss1,")"));
   FileWrite(handle,"Máximo beneficio consecutivo (número de ganancias)",
                     ConProfit2,
                     StringConcatenate("(",ConProfitTrades2,")"));
   FileWrite(handle,"Máxima pérdida consecutiva (número de pérdidas)",
                     -ConLoss2,
                     StringConcatenate("(",ConLossTrades2,")"));
//----
   FileClose(handle);
  }
El siguiente ejemplo ilustra cómo generar un informe con estas funciones.
void deinit()
  {
   if(!IsOptimization())
     {
      if(!IsTesting()) ExtInitialDeposit=CalculateInitialDeposit();
      CalculateSummary(ExtInitialDeposit);
      WriteReport("MACD_Sample_Report.txt");
     }
  }
Como se observa, los informes se generan no solo después de realizar las pruebas, sino también en la desinicialización del asesor experto. Tal vez usted se pregunte cómo se puede conocer el tamaño del depósito inicial si el historial de la cuenta se descarga en el terminal sólo parcialmente, por ejemplo, con los datos de un solo mes. La función CalculateInitialDeposit ayuda a resolver este problema.
double CalculateInitialDeposit()
  {
   double initial_deposit=AccountBalance();
//----
   for(int i=HistoryTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      int type=OrderType();
      //---- depósito inicial no considerado
      if(i==0 && type==OP_BALANCE) break;
      if(type==OP_BUY || type==OP_SELL)
        {
         //---- calculamos el beneficio
         double profit=OrderProfit()+OrderCommission()+OrderSwap();
         //---- disminuimos el depósito
         initial_deposit-=profit;
        }
      if(type==OP_BALANCE || type==OP_CREDIT)
         initial_deposit-=OrderProfit();
     }
//----
   return(initial_deposit);
  }
Los informes se generan de este modo en el terminal cliente MetaTrader 4.


Se puede comparar con los datos calculados por el programa.
Depósito inicial             10000
Beneficio neto total            -13.16
Beneficio bruto                20363.32
Pérdida bruta                  20376.48
Factor de beneficio               0.99935416
Rentabilidad esperada             -0.01602923
Disminución absoluta           404.28
Disminución máxima            1306.36 (11.5677%)
Disminución relativa           11.5966%    (1289.78)
Total de operaciones                    821
Posiciones cortas(ganado %)      419 (24.821%)
Posiciones largas(ganado %)       402 (31.592%)
Operaciones de beneficios (% del total)  231 (28.1364%)
Operaciones de pérdidas (% del total)    590 (71.8636%)
Mayor operación de beneficios        678.08
Mayor operación de pérdidas          -250
Operación de beneficios media        88.15290043
Operación de pérdidas media          -34.53640678
Media de ganancias consecutivas    1
Media de pérdidas consecutivas  4
Ganancias consecutivas máximas (beneficios en dinero)  4   (355.58)
Pérdidas consecutivas máximas (pérdidas en dinero)  15  (-314.74)
Máximo beneficio consecutivo (número de ganancias)  679.4   (2)
Máxima pérdida consecutiva (número de pérdidas)  -617.16 (8)
Se recomienda poner el archivo SummaryReport.mq4 en el directorio experts\include, e insertarlo mediante la directiva #include. Dicho archivo se adjunta en este artículo.
#include <SummaryReport.mq4>
 
double ExtInitialDeposit;