English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable

Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable

MetaTrader 5Trading | 14 febrero 2014, 07:42
1 386 0
Roman Zamozhnyy
Roman Zamozhnyy

Idea

Después de crear un Asesor Experto (EA, por sus siglas en inglés), todos nosotros recurrimos a la ayuda del Probador de Estrategias incorporado para seleccionar los parámetros óptimos. Una vez encontrados, iniciamos el EA, y cuando nos parece que ocurren algunos cambios importantes en el trabajo del EA, lo paramos, volvemos a optimizarlo una y otra vez en el Probador. Y así constantemente.

¿Podemos asignar al EA la toma de decisión sobre la reoptimización y el mismo proceso de esta reoptimización sin parar, desde luego, el trabajo del EA?

Una de las opciones para resolver este problema fue propuesta por Quantum en su artículo "Sistemas de Trading Adoptables y su Uso en el Terminal de Cliente MetaTrader 5". En este artículo, junto con el sistema de trading real, él ha considerado el uso de varias (una cantidad ilimitada) estrategias virtuales de las cuales se hace la selección de la estrategia que ha traído el mayor beneficio para el momento actual. La decisión sobre el cambio de la estrategia comercial se toma después de superar un valor fijado de las barras.

Pues, yo propongo usar el código del algoritmo genético (GA, por sus siglas en inglés) descrito por joo en su artículo "Algoritmos genéticos: ¡Es fácil!". Vamos a ver la implementación de este EA (como ejemplo: el EA que fue propuesto para participar en el Automated Trading Championship 2011).


Manos a la obra

Pues bien, vamos a definir qué es lo que tiene que saber hacer el EA. Primero, por supuesto, tradear según la estrategias elegida. Segundo, tomar decisión sobre si ha llegado el momento de reoptimizarse (llevar a cabo nueva optimización de los parámetros de entrada). Y en tercer lugar, reoptimizarse utilizando GA. Al principio, vamos a ver la reoptimización simplísima: hay una estrategia y nosotros simplemente seleccionamos nuevos parámetros. Luego veremos si podemos (y si es posible, pues de qué manera), usando GA, elegir otra estrategia para las condiciones de mercado que han cambiado.

Además, para facilitar la simulación en la Función de Aptitud (fitness function), tomamos la decisión de tradear sólo en las barras formadas en un instrumento. Asimismo no habrá posiciones adicionales y cierres parciales. Los que prefieren utilizar stops y takes fijas, así como trailing stops, pueden leer el artículo "Algoritmo de generación de ticks en el téster de estrategias del terminal MetaTrader 5" con el fin de implementar el chequeo de la activación de las órdenes Stop Loss y Take Profit en la función de aptitud. Voy a entrar en los detalles de esta complicada frase:

En la función de aptitud yo simulo para sí mismo el modo de prueba que en el Probador se llama "Sólo precios de apertura". ¡PERO, OJO! Eso no significa que es la única posible simulación del proceso de prueba en la función de aptitud. Es posible que las personas más escrupulosas quieran implementar en la función de aptitud la prueba en el modo "Todos los ticks". Y, pues, para que no inventen la bicicleta, no inventen ellos mismos estos "todos los ticks", les recomiendo fijarse en el algoritmo ya hecho: de la empresa MetaQuotes. Es decir, después de leer este artículo, uno será capaz de simular en la función de aptitud el modo "Todos los ticks", lo que representa una condición necesaria para la simulación correcta de las Stops y Takes en la función de aptitud.

Antes de ponerse a lo más importante -la implementación de la estrategia- vamos a centrarnos un poco en los detalles técnicos y implementamos las funciones auxiliares que definen la llegada de nueva barra, la apertura y el cierre de la posición:

//+------------------------------------------------------------------+
//| Determinamos si ha llegado nueva barra                           |
//+------------------------------------------------------------------+
bool isNewBars()
  {
   CopyTime(s,tf,0,1,curBT);
   TimeToStruct(curBT[0],curT);
   if(tf==PERIOD_M1||
      tf==PERIOD_M2||
      tf==PERIOD_M3||
      tf==PERIOD_M4||
      tf==PERIOD_M5||
      tf==PERIOD_M6||
      tf==PERIOD_M10||
      tf==PERIOD_M12||
      tf==PERIOD_M15||
      tf==PERIOD_M20||
      tf==PERIOD_M30)
      if(curT.min!=prevT.min)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_H1||
      tf==PERIOD_H2||
      tf==PERIOD_H3||
      tf==PERIOD_H4||
      tf==PERIOD_H6||
      tf==PERIOD_H8||
      tf==PERIOD_M12)
      if(curT.hour!=prevT.hour)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_D1||
      tf==PERIOD_W1)
      if(curT.day!=prevT.day)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_MN1)
      if(curT.mon!=prevT.mon)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   return(false);
  }
//+------------------------------------------------------------------+
//|  ClosePosition                                                   |
//+------------------------------------------------------------------+
void ClosePosition()
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=PositionGetSymbol(0);
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; 
   else request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   while(PositionsTotal()>0)
     {
      request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
     }
  }
//+------------------------------------------------------------------+
//|  OpenPosition                                                    |
//+------------------------------------------------------------------+
void OpenPosition()
  {
   double vol;
   request.action=TRADE_ACTION_DEAL;
   request.symbol=s;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE)
       /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP);
   vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN));
   vol=MathMin(vol,GetPossibleLots()*0.95);
   if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2);
   request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
   while(PositionSelect(s)==false)
     {
      if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); 
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
   while(PositionGetDouble(POSITION_VOLUME)<vol)
     {
      request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
  }
//+------------------------------------------------------------------+

Si nos fijamos con detenimiento, en la función de apertura de la posición se puede encontrar tres cosas importantes: las variables s, optF y la llamada a la función GetPossibleLots():

  • s - instrumento con el que vamos a tradear es una de las variables optimizada por el GA,
  • optF - parte del depósito que va a utilizarse para el trading (otra variable más optimizada por el GA), 
  • función GetPossibleLots() - devuelve una parte del depósito con la que se puede tradear:
//+------------------------------------------------------------------+
//|  GetPossibleLots                                                 |
//+------------------------------------------------------------------+
double GetPossibleLots()
  {
   request.volume=1.0;
   if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
   else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
   OrderCheck(request,check);
   return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2));
  }

Rompiendo un poco el orden de nuestra narración, introducimos otras dos funciones más que son comunes para todos nuestros EAs y serán necesarias en el paso número Dos:

//+------------------------------------------------------------------+
//|  InitRelDD                                                       |
//+------------------------------------------------------------------+
void InitRelDD()
  {
   ulong DealTicket;
   double curBalance;
   prevBT[0]=D'2000.01.01 00:00:00';
   TimeToStruct(prevBT[0],prevT);
   curBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   maxBalance=curBalance;
   HistorySelect(D'2000.01.01 00:00:00',TimeCurrent());
   for(int i=HistoryDealsTotal();i>0;i--)
     {
      DealTicket=HistoryDealGetTicket(i);
      curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT);
      if(curBalance>maxBalance) maxBalance=curBalance;
     }
  }
//+------------------------------------------------------------------+
//|  GetRelDD                                                        |
//+------------------------------------------------------------------+
double GetRelDD()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance);
  }

¿Qué tenemos aquí? La primera función determina el valor máximo del balance de la cuenta, la segunda calcula la actual reducción relativa de la cuenta. Durante la descripción del paso número Dos vamos a ver con más detalles las particularidades de su uso.

Bueno, ahora vamos a centrarnos en los EAs. Puesto que todavía somos principiantes, no vamos a hacerle al EA seleccionar la estrategia e implementamos estrictamente dos EAs con las siguientes estrategias:
  • uno tradea usando intersecciones de las medias móviles (Cruz de Oro - compramos el instrumento, Cruz Muerta - vendemos);
  • el otro es una red neuronal simple que recibe en la entrada los cambios de precios en el rango [0..1] de las cinco anteriores sesiones de trading.

Algoritmicamente el trabajo del Asesor Experto Autoenseñable puede ser representado de la siguiente manera:

  1. Inicializamos las variables que utiliza el EA: definimos y inicializamos los búferes indicadores, o establecemos la topología de la red neuronal (número de capas/neuronas en una capa; en nuestro ejemplo tenemos una red neuronal simple con el mismo número de neuronas en todas las capas), indicamos el período de tiempo (tameframe) de trabajo. A continuación, nos espera seguramente el paso más importante: llamamos a la función de la Optimización Genética que a su vez se dirige a la función principal - Función de Aptitud (a continuación en el texto: FA).

    IMPORTANTE Para cada nueva estrategia de trading la FA es nueva, es decir, se sobrescribe. La FA para una media móvil es totalmente diferente que la FA para dos medias móviles, y se diferencia considerablemente de la FA de la red neuronal.

    El resultado de trabajo de la FA en mis EAs es el máximo del balance con la condición de que la reducción relativa no supere el valor crítico que se establece con la variable externa (en nuestros ejemplos es 0,5). En otras palabras, si el siguiente repaso del GA ha dado el balance de 100 000, pero la reducción relativa del balance es 0,6, entonces la FA=0,0. En su caso, mi estimado Lector, el resultado en la FA pueden ser criterios absolutamente diferentes.

    Recogemos los resultados del trabajo del Algoritmo Genético: para la intersección de las medias móviles éstos, naturalmente, van a ser los períodos de las medias, para la red neuronal - los pesos de sinapsis, para las ambas (y para los demás EAs míos) - el instrumento con el que vamos a tradear hasta la siguiente reoptimización, luego, ya conocido optF - la parte del depósito que nos gustaría utilizar para el trading. Usted puede añadir en su FA los parámetros de optimización a su propia discreción, por ejemplo, elegir el período de tiempo, etc.

    El último paso en la inicialización es averiguar el valor máximo del balance de la cuenta. ¿En qué consiste la importancia de este paso? Porque es el punto de partida para la toma de decisión sobre la reoptimización.

    IMPORTANTE Cómo se toma la decisión sobre la reoptimización: cuando la reducción relativa del BALANCE alcance un determinado valor crítico establecido como una variable externa (en nuestros ejemplos es 0,2), pues se hace la reoptimización. Para no obligar al EA realizar la reoptimización en cada barra al alcanzar la reducción crítica, después de cada reoptimización sustituimos el valor máximo del balance con el valor actual.

    En su caso, mi estimado Lector, el el criterio de la llegada del momento de reoptimización puede ser absolutamente diferente.

  2. Realizamos las operaciones comerciales.

  3. Después de cada posición cerrada, comprobamos si la reducción del balance ha alcanzado el valor crítico. Si ha alcanzado, llamamos al GA, recogemos los resultados de su trabajo (¡Pues, aquí tenemos nuestra reoptimización!).

  4. Y esperamos que nos llame el jefe del Forex rogando no llevemos el mundo a la quiebra, o bien (con más frecuencia): Stop Out, Margin Call, la ambulancia...

Mostraremos la implementación programada de lo expuesto más arriba para el EA usando las medias móviles (los códigos fuente también se adjuntan), para la red neuronal: todo está en los códigos fuente.

El código se distribuye bajo los términos y condiciones de GPL.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tf=Period();
//--- para la prueba barra a barra...
   prevBT[0]=D'2001.01.01';
//---... hace mucho tiempo
   TimeToStruct(prevBT[0],prevT);
//--- profundidad del historial (establecemos, porque la optimización se basa en los datos históricos)
   depth=10000;
//--- cuántas veces copiamos (establecemos, porque la optimización se basa en los datos históricos)
   count=2;
   ArrayResize(LongBuffer,count);
   ArrayResize(ShortBuffer,count);
   ArrayInitialize(LongBuffer,0);
   ArrayInitialize(ShortBuffer,0);
//--- llamamos a la función de la optimización genética de la red neuronal
   GA();
//--- obtenemos los parámetros optimizados de la red neuronal y otras variables
   GetTrainResults();
//--- obtenemos la reducción del balance
   InitRelDD();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBars()==true)
     {
      bool trig=false;
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(trig==true)
        {
         //--- si la reducción del balance ha superado el valor permitido:
         if(GetRelDD()>maxDD)
           {
            //--- llamamos a la función de la optimización genética de la red neuronal
            GA();
            //--- obtenemos los parámetros optimizados de la red neuronal y otras variables
            GetTrainResults();
            //--- ahora la lectura de la reducción la vamos a llevar del balance actual en vez del máximo del balance
            maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
           }
        }
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         request.type=ORDER_TYPE_SELL;
         OpenPosition();
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         request.type=ORDER_TYPE_BUY;
         OpenPosition();
        }
     };
  }
//+------------------------------------------------------------------+
//| Preparación y llamada al optimizador genético                    |
//+------------------------------------------------------------------+
void GA()
  {
//--- número de genes (es igual al número de las variables a optimizar, 
//--- hay que no olvidar especificarlas todas en FitnessFunction())
   GeneCount      =OptParamCount+2;    
//--- número de cromosomas en la colonia
   ChromosomeCount=GeneCount*11;
//--- mínimo del rango de búsqueda
   RangeMinimum   =0.0;
//--- máximo del rango de búsqueda
   RangeMaximum   =1.0;
//--- paso de búsqueda
   Precision      =0.0001;
//--- 1 es el mínimo, cualquier otro es el máximo
   OptimizeMethod =2;                                                 
   ArrayResize(Chromosome,GeneCount+1);
   ArrayInitialize(Chromosome,0);
//--- número de épocas sin mejora
   Epoch          =100;                                               
//--- ratio de replicación, mutación natural, mutación artificial, prestación de genes, 
//--- entrecruzamiento cromosómico, coeficiente del desplazamiento de los límites del intervalo, probabilidad de mutación de cada gen en %
   UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0);                                
  }
//+------------------------------------------------------------------+
//| Función de Aptitud para el optimizador genético                  | 
//| de la red neuronal:                                              | 
//| seleccionamos el par, optF, pesos de sinapsis;                   |
//| se puede optimizar lo que sea, pero hay que                      |
//| monitorear atentamente el número de genes                        |
//+------------------------------------------------------------------+
void FitnessFunction(int chromos)
  {
   int    b;
//--- ¿hay una posición abierta?
   bool   trig=false;
//--- dirección de la posición abierta
   string dir="";
//--- precio de apertura de la posición
   double OpenPrice=0;
//--- eslabón intermedio entre la colonia de genes y parámetros a optimizar
   int    z;
//--- balance actual
   double t=cap;
//--- balance máximo
   double maxt=t;
//--- reducción absoluta
   double aDD=0;
//--- reducción relativa
   double rDD=0.000001;
//--- la propia Función de Aptitud
   double ff=0;
//--- GA está seleccionando el par
   z=(int)MathRound(Colony[GeneCount-1][chromos]*12);
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
   MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
   
//--- GA está seleccionando F óptimo
   optF=Colony[GeneCount][chromos];                                   
   
   leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
   contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
   b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
   
//--- para la red neuronal que utiliza los datos históricos - desde donde empezamos a copiarlos
   for(from=b;from>=1;from--) 
     {
      CopyBuffer(MAshort,0,from,count,ShortBuffer);
      CopyBuffer(MAlong,0,from,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="SELL";
            trig=true;
           }
         else
           {
            if(dir=="BUY")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="SELL";
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="BUY";
            trig=true;
           }
         else
           {
            if(dir=="SELL")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="BUY";
               trig=true;
              }
           }
        }
     }
   if(rDD<=trainDD) ff=t; else ff=0.0;
   AmountStartsFF++;
   Colony[0][chromos]=ff;
  }

//+-------------------------------------------------------------------+
//| Obtenemos los parámetros optimizados                              |
//| de la red neuronal y otras variables                              |
//| siempre tiene que ser igual al número de genes                    |
//+-------------------------------------------------------------------+
void GetTrainResults()
  {
//--- eslabón intermedio entre la colonia de genes y parámetros a optimizar
   int z;                                                             
   MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   CopyBuffer(MAshort,0,from,count,ShortBuffer);
   CopyBuffer(MAlong,0,from,count,LongBuffer);
//--- recordamos el mejor par
   z=(int)MathRound(Chromosome[GeneCount-1]*12);                      
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
//--- recordamos el mejor F óptimo
   optF=Chromosome[GeneCount];                                        
  }
//+------------------------------------------------------------------+

Vamos a intentar comprender mejor el trabajo de la principal función del algoritmo :  Función de Aptitud.

Toda la idea del Asesor Experto Auto-optimizable se basa en la simulación del proceso del trading (como en el Probador estándar de MetaQuotes) dentro de un período de tiempo (supongamos, utilizando el historial de 10 000 barras) en la Función de Aptitud que recibe las variables a optimizar del Algoritmo Genético (función GA()). En caso del algoritmo basado en las intersecciones de las medias móviles las variables a optimizar son las siguientes:

  • instrumento (para el Forex se trata de un par de divisas); pues sí, es un típico Asesor Experto multidivisas, y el Algoritmo Genético selecciona el instrumento (puesto que el código ha sido cogido del EA propuesto para participar en el Campeonato, los pares que contiene corresponden a los pares de divisas del Campeonato; en caso general, aquí puede haber cualquier instrumento cotizado por el broker).
    Nota: es una lástima pero en modo de prueba el Asesor Experto no puede obtener la lista de los pares de la ventana Market Watch (muchas gracias a MetaQuotes, aquí lo hemos aclarado: ¡No se puede!). Por eso, si desea probar el Asesor Experto en el Probador para el Forex y para las acciones por separado, simplemente especifique la lista de sus instrumentos en la FA y en la función GetTrainResults().
  • parte del depósito que vamos a utilizar para el trading;
  • períodos de dos medias móviles.

¡En los ejemplos ofrecidos se muestra la variante del EA PARA LA PRUEBA!

El código para el TRADING REAL puede ser simplificado considerablemente utilizando la lista de los instrumentos de la ventana Market Watch.

Para eso en la FA y en la función GetTrainResults() con los comentarios "//--- GA está seleccionando el par" y "//--- recordamos el mejor par" simplemente ponemos:

//--- GA está seleccionando el par
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));
  s=SymbolName(z,true);

De esta manera, en el principio de la FA especificamos e inicializamos, en caso de necesidad, las variables para la simulación del trading a base de datos históricos. En la segunda fase recibimos diferentes valores de las variables a optimizar del Algoritmo Genético, por ejemplo en la línea "optF=Colony[GeneCount][chromos];" el GA pasa a la FA el valor de la parte del depósito.

A continuación, averiguaremos el número de barras disponibles en el historial, y a partir de la barra 10 000 o desde la primera barra disponible empezamos a simular el proceso de recepción de cotizaciones en modo "Sólo precios de apertura" y la toma de decisiones de trading:

  • Copiamos los valores de las medias móviles a los búferes;
  • Comprobamos si es la Cruz muerta;
  • Si es la Cruz muerta y no hay posiciones abiertas (if(trig==false)), abrimos la posición SELL de manera virtual (simplemente recordamos el precio de apertura y la dirección);
  • Si es la Cruz muerta y la posición BUY está abierta (if(dir==»BUY»)), cogemos el precio de apertura de la barra, y preste mucha atención a tres puntos muy importantes que van a continuación:
  1. Simulamos el cierre de la posición y el cambio del balance: al balance actual le sumamos el valor del balance actual que ha sido multiplicado por la parte del depósito permitido para el trading, multiplicado por la diferencia de los precios de apertura y de cierre, multiplicado por el precio del pip (aproximado);
  2. Comprobamos si el balance actual es el máximo durante todo el historial de la simulación del trading; si no lo es, calculamos la reducción del balance máximo en dinero;
  3. Convertimos la reducción en dinero previamente calculada en la reducción relativa del balance;
  • Abrimos la posición SELL de manera virtual (simplemente recordamos el precio de apertura y la dirección);
  • Realizamos las mismas comprobaciones y los cálculos para la Cruz de Oro.

Después de pasar todo el historial disponible y simular el trading virtual, calculamos el valor de la FA final: si la reducción relativa que hemos obtenido es inferior a la reducción establecida para la prueba, entonces la FA es igual al balance, de lo contrario la Fa es igual a 0. ¡El Algoritmo Genético persigue maximizar la Función de Aptitud!

Al fin y al cabo, pasando diferentes valores de los instrumentos, partes del depósito, períodos de las medias móviles, el Algoritmo Genético encontrará los valores que maximizan el balance con la reducción relativa mínima (el mínimo se establece por el usuario).


Conclusión

La conclusión breve es la siguiente: es fácil crear un Asesor Experto Autoenseñable, lo complicado es encontrar lo que hay que pasarle de entrada (lo más importante es la idea, la implementación es sólo la cuestión de técnica).

Adelantando la pregunta de los pesimistas: "¿Y eso funciona?", les diré: "Sí, funciona". A los optimistas les diré: "No es Santo Grial".

¿En qué consiste la principal diferencia entre el método propuesto y el de Quantum? De la mejor manera se puede describirlo si comparamos los Asesores Expertos en las medias móviles (MA, por sus siglas en inglés):

  1. La decisión sobre los períodos de las MAs en el Sistema de Trading Adaptable hay que tomarla antes de la compilación, indicar estrictamente en el código y hacer la selección sólo de este número limitado de variantes; en el Asesor Experto Optimizado Genéticamente no tomamos ninguna decisión sobre los períodos antes de la compilación, esta decisión se toma por el GA y el número de las variantes se limita sólo por el sentido común.
  2. En el Sistema de Trading Adaptable el trading virtual se realiza barra a barra; en el Asesor Experto Optimizado Genéticamente eso sucede raramente -y sólo cuando llegan las condiciones de reoptimización. Cuando el número de estrategias, parámetros, instrumentos se aumenta, el rendimiento del ordenador puede ser el factor limitador para el Sistema de Trading Adaptable.


Anexo

Si metemos la red neuronal en el Probador sin activar ninguna optimización basándose en los gráficos diarios desde 01.01.2010, obtendremos los siguiente:

Informe del Probador de Estrategias
MetaQuotes-Demo (Build 523)
Asesor Experto: ANNExample
Símbolo: EURUSD
Período: Daily (2010.01.01 - 2011.09.30)
Parámetros de entrada: trainDD=0.9
maxDD=0.1
Broker: Alpari NZ Limited
Divisa: USD
Depósito inicial: 10 000.00
Apalancamiento: 1:100
Resultados
Calidad del historial: 100%
Barras: 454 Ticks: 2554879
Beneficio neto: -9 094.49 Beneficio bruto: 29 401.09 Pérdidas brutas: -38 495.58
Rentabilidad: 0.76 Beneficio esperado: -20.53 Nivel del margen: 732.30%
Factor de recuperación -0.76 Ratio de Sharpe: -0.06 Resultado OnTester: 0
Reducción del balance:
Reducción absoluta del balance: 9 102.56 Reducción máxima del balance: 11 464.70 (92.74%) Reducción relativa del balance: 92.74% (11 464.70)
Reducción de equidad:
Reducción absoluta de equidad: 9 176.99 Reducción máxima de equidad: 11 904.00 (93.53%) Reducción relativa de equidad: 93.53% (11 904.00)
Total de trades: 443 Trades cortos (% de ganados): 7 (14.29%) Trades largos (% de ganados): 436 (53.44%)
Total de transacciones: 886 Trades rentables (% de todos): 234 (52.82%) Trades irrentables (% de todos): 209 (47.18%)
Trade más rentable: 1 095.57 Trades más irrentables: -1 438.85
Trade rentable medio: 125.65 Trades irrentables medio: -184.19
Máx. de ganancias consecutivas (beneficio): 8 (397.45) Máx. de pérdidas consecutivas (pérdidas): 8 (-1 431.44)
Max. beneficio consecutivo (núm. de ganancias) 1 095.57 (1) Max. pérdidas consecutivas (núm. de pérdidas) -3 433.21 (6)
Media de ganancias consecutivas: 2 Media de pérdidas consecutivas: 2

y aquí hay tres variantes de reoptimización para elegir:

una...

Tiempo Transacción Símbolo Tick Dirección Volumen Precio Orden Comisión Swap Beneficio Balance
2010.01.01 00:00 1 balance 0.00 0.00 10 000.00 10 000.00
2010.01.04 00:00 2 AUDUSD buy in 0.90 0.89977 2 0.00 0.00 0.00 10 000.00
2010.01.05 00:00 3 AUDUSD sell out 0.90 0.91188 3 0.00 5.67 1 089.90 11 095.57
2010.01.05 00:00 4 AUDUSD buy in 0.99 0.91220 4 0.00 0.00 0.00 11 095.57
2010.01.06 00:00 5 AUDUSD sell out 0.99 0.91157 5 0.00 6.24 -62.37 11 039.44
2010.01.06 00:00 6 AUDUSD buy in 0.99 0.91190 6 0.00 0.00 0.00 11 039.44
2010.01.07 00:00 7 AUDUSD sell out 0.99 0.91924 7 0.00 18.71 726.66 11 784.81


dos...

Tiempo Transacción Símbolo Tick Dirección Volumen Precio Orden Comisión Swap Beneficio Balance
2010.05.19 00:00 189 AUDUSD sell out 0.36 0.86110 189 0.00 2.27 -595.44 4 221.30
2010.05.19 00:00 190 EURAUD sell in 0.30 1.41280 190 0.00 0.00 0.00 4 221.30
2010.05.20 00:00 191 EURAUD buy out 0.30 1.46207 191 0.00 7.43 -1 273.26 2 955.47
2010.05.20 00:00 192 AUDUSD buy in 0.21 0.84983 192 0.00 0.00 0.00 2 955.47


tres

Tiempo Transacción Símbolo Tick Dirección Volumen Precio Orden Comisión Swap Beneficio Balance
2010.06.16 00:00 230 GBPCHF buy in 0.06 1.67872 230 0.00 0.00 0.00 2 128.80
2010.06.17 00:00 231 GBPCHF sell out 0.06 1.66547 231 0.00 0.13 -70.25 2 058.68
2010.06.17 00:00 232 GBPCHF buy in 0.06 1.66635 232 0.00 0.00 0.00 2 058.68
2010.06.18 00:00 233 GBPCHF sell out 0.06 1.64705 233 0.00 0.04 -104.14 1 954.58
2010.06.18 00:00 234 AUDUSD buy in 0.09 0.86741 234 0.00 0.00 0.00 1 954.58
2010.06.21 00:00 235 AUDUSD sell out 0.09 0.87184 235 0.00 0.57 39.87 1 995.02
2010.06.21 00:00 236 AUDUSD buy in 0.09 0.88105 236 0.00 0.00 0.00 1 995.02
2010.06.22 00:00 237 AUDUSD sell out 0.09 0.87606 237 0.00 0.57 -44.91 1 950.68
2010.06.22 00:00 238 AUDUSD buy in 0.09 0.87637 238 0.00 0.00 0.00 1 950.68
2010.06.23 00:00 239 AUDUSD sell out 0.09 0.87140 239 0.00 0.57 -44.73 1 906.52
2010.06.23 00:00 240 AUDUSD buy in 0.08 0.87197 240 0.00 0.00 0.00 1 906.52
2010.06.24 00:00 241 AUDUSD sell out 0.08 0.87385 241 0.00 1.51 15.04 1 923.07
2010.06.24 00:00 242 AUDUSD buy in 0.08 0.87413 242 0.00 0.00 0.00 1 923.07
2010.06.25 00:00 243 AUDUSD sell out 0.08 0.86632 243 0.00 0.50 -62.48 1 861.09
2010.06.25 00:00 244 AUDUSD buy in 0.08 0.86663 244 0.00 0.00 0.00 1 861.09
2010.06.28 00:00 245 AUDUSD sell out 0.08 0.87375 245 0.00 0.50 56.96 1 918.55
2010.06.28 00:00 246 AUDUSD buy in 0.08 0.87415 246 0.00 0.00 0.00 1 918.55
2010.06.29 00:00 247 AUDUSD sell out 0.08 0.87140 247 0.00 0.50 -22.00 1 897.05
2010.06.29 00:00 248 AUDUSD buy in 0.08 0.87173 248 0.00 0.00 0.00 1 897.05
2010.07.01 00:00 249 AUDUSD sell out 0.08 0.84053 249 0.00 2.01 -249.60 1 649.46
2010.07.01 00:00 250 EURGBP sell in 0.07 0.81841 250 0.00 0.00 0.00 1 649.46
2010.07.02 00:00 251 EURGBP buy out 0.07 0.82535 251 0.00 -0.04 -73.69 1 575.73
2010.07.02 00:00 252 EURGBP sell in 0.07 0.82498 252 0.00 0.00 0.00 1 575.73
2010.07.05 00:00 253 EURGBP buy out 0.07 0.82676 253 0.00 -0.04 -18.93 1 556.76
2010.07.05 00:00 254 EURGBP sell in 0.06 0.82604 254 0.00 0.00 0.00 1 556.76
2010.07.06 00:00 255 EURGBP buy out 0.06 0.82862 255 0.00 -0.04 -23.43 1 533.29


P.S. Como tarea de casa: seleccionar no sólo los parámetros de un sistema determinado, sino también seleccionar el mismo sistema que mejor corresponda al mercado en un momento dado (una pista - desde el banco de sistemas).


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/334

Archivos adjuntos |
anntrainlib.mqh (9.76 KB)
matrainlib.mqh (8.94 KB)
ugalib.mqh (33.26 KB)
annexample.mq5 (4.32 KB)
maexample.mq5 (4.22 KB)
musthavelib.mqh (8.14 KB)
Crear Paneles de Control Activos en MQL5 para Trading Crear Paneles de Control Activos en MQL5 para Trading
Este artículo trata el tema del desarrollo de paneles de control activos en MQL5. Los elementos de la interfaz se gestionan con el mecanismo de control de eventos. Además, existe una opción para una configuración flexible de propiedades de elementos de control. El panel de control activo permite trabajar con posiciones, así como configuración, modificaciones y eliminaciones de mercado y órdenes pendientes.
La estrategia "Todo o Nada" en Forex La estrategia "Todo o Nada" en Forex
El objetivo de este artículo es crear una estrategia comercial máximamente sencilla que implemente el principio de juego "Todo o Nada". No se plantea la tarea de crear un Asesor Experto rentable. El objetivo consiste en multiplicar el depósito inicial con la probabilidad máximamente posible. ¿Será posible usar Forex para conseguir grandes beneficios contra la probabilidad de perderlo todo, sin saber nada sobre el análisis técnico y sin usar ningunos indicadores?
Conexión del Asesor Experto con ICQ en MQL5 Conexión del Asesor Experto con ICQ en MQL5
Este artículo describe el método de intercambio de información entre el Asesor Experto y usuarios de ICQ, y presenta varios ejemplos. El material facilitado resultará interesante para aquellos que deseen recibir información de trading remotamente de un terminal de cliente, a través de un ICQ client en su teléfono móvil o PDA.
Crear y Publicar Informes de Trading y Notificaciones SMS Crear y Publicar Informes de Trading y Notificaciones SMS
Los traders no siempre tienen la capacidad ni las ganas de estar sentados frente al terminal de trading durante horas. Especialmente si el sistema de trading está más o menos formalizado y puede identificar automáticamente algunos de los estados de mercado. Este artículo describe cómo generar un informe de resultados de trading (usando un Asesor Experto, Indicador o Script) como archivo HTML y cómo subirlo por FTP a un servidor WWW. También explicaremos cómo enviar notificaciones de eventos de trading como mensajes SMS a teléfonos móviles.