![Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable](https://c.mql5.com/i/articles/overlay.png)
Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable
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.
- 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:
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.
En su caso, mi estimado Lector, el el criterio de la llegada del momento de reoptimización puede ser absolutamente diferente.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.
- Realizamos las operaciones comerciales.
- 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!).
-
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:
- 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);
- 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;
- 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):
- 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.
- 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
![Crear Paneles de Control Activos en MQL5 para Trading](https://c.mql5.com/2/0/panel__2.png)
![La estrategia "Todo o Nada" en Forex](https://c.mql5.com/2/0/allVSzero.png)
![Conexión del Asesor Experto con ICQ en MQL5](https://c.mql5.com/2/0/icq.png)
![Crear y Publicar Informes de Trading y Notificaciones SMS](https://c.mql5.com/2/0/trade_reports_SMS_MQL5.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso