Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots (Parte III)
Nikolay Kositsin | 6 enero, 2016
Introducción
Un lector del artículo anterior me ha pedido que explique cómo se puede automatizar el proceso de backtesting con el objetivo de obtener los resultados de todas las optimizaciones a la vez. Por otra parte, no es recomendable hacer las pruebas manualmente, sino que este proceso se tiene que automatizar. Así que la idea es fantástica. Además, MQL4 ofrece todo lo necesario para esta implementación. Comenzaré pues este artículo intentado solucionar el problema expuesto.
Automatización del backtesting
Para solucionar esta tarea necesitamos lo siguiente:
1. Incluir este archivo en la cabecera del Asesor Experto:
//+==================================================================+ //| Función personalizada de backtesting | //+==================================================================+ #include <IsBackTestingTime.mqh>
Con esta directiva incluimos la función IsBackTestingTime() en el código del EA. Por cierto, no olvide poner el archivo IsBackTestingTime.mqh en la carpeta INCLUDE. Esta función:
bool IsBackTestingTime()
{
}
define el periodo de tiempo donde tiene lugar la optimización de backtesting. En estos periodos de tiempo la función devuelve siempre 'true', y en otros intervalos devuelve 'false'. También se añaden variables externas al código del EA con esta directiva:
//---- Declaración de variables externas para backtesting extern datetime Start_Time = D'2007.01.01'; // hora de inicio de la optimización extern int Opt_Period = 3; // periodo de optimización en meses; si es menor que cero, los parámetros son en días. extern int Test_Period = 2; // periodo de pruebas en meses extern int Period_Shift = 1; // paso del cambio del periodo de optimización en meses extern int Opt_Number = 0; // número de optimización
Espero que todos los que han leído mi artículo anterior entiendan bien el significado de estas variables, de modo que no hace falta explicarlo otra vez.
2. Antes de llamar a la función IsBackTestingTime(), el bloque de código de la función de inicio limita la operación del EA a determinados periodos de tiempo de acuerdo al número de optimización de backtesting.
//----+ Ejecución de las condiciones de backtesting if (!IsBackTestingTime()) return(0);
Esto se puede representar esquemáticamente de esta manera:
//+==================================================================+ //| Exp_BackTest.mq4 | //| Copyright © 2008, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2008, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //+==================================================================+ //| Función personalizada de backtesting | //+==================================================================+ #include <IsBackTestingTime.mqh> //---- PARÁMETROS DE ENTRADA DEL EA //---- VARIABLES GLOBALES DEL EA //+==================================================================+ //| FUNCIONES DEL EA DEFINIDAS POR EL USUARIO | //+==================================================================+ //+==================================================================+ //| Función de inicialización del EA | //+==================================================================+ int init() { //----+ +------------------------------------------------------------+ //---- CÓDIGO DE INICIALIZACIÓN DEL EA //----+ +------------------------------------------------------------+ //---- fin de la inicialización return(0); } //+==================================================================+ //| Función de iteración del Asesor Experto | //+==================================================================+ int start() { //----+ Ejecución de las condiciones de backtesting if (!IsBackTestingTime()) return(0); //----+ +---------------------------------------------------------+ //----+ CÓDIGO DEL ALGORITMO DEL EA //----+ +---------------------------------------------------------+ //----+ return(0); } //+------------------------------------------------------------------+
Si le interesa la solución detallada de este problema eche un vistazo al código de Exp_5_1.mq4, que es el código del EA del artículo anterior, Exp_5.mq4 se ha modificado para el backtesting. En realidad no hay mucha diferencia entre la optimización del EA en comparación con un Asesor Experto sencillo. Sin embargo, a excepción de Opt_Number, creo que las variables de backtesting no se deberían optimizar, aunque posiblemente usted opine de otra manera.
Es importante recordar que después de la optimización, durante las pruebas, obtenemos resultados que no están dentro del período de optimización, sino después del mismo, ¡más allá del límite derecho! No es buena idea optimizar el backtesting de una sola vez con un algoritmo genético; por el contrario, es mucho más interesante analizar profundamente cada optimización de backtesting por separado sin la variable de entrada Opt_Number.
Pero incluso en este caso, tal aproximación facilita la comprensión del comportamiento del EA. Recuerde que el valor de la variable externa Opt_Number puede variar desde cero hasta un determinado valor máximo que se define del siguiente modo: tomar el periodo total en que se llevan a cabo todas las optimizaciones de backtesting (en meses) y restar el periodo de optimización de backtesting en meses (Opt_Period) y el periodo de backtesting (Test_Period). Añada uno al valor obtenido. El resultado obtenido es el máximo de la variable Opt_Number, si Period_Shift vale uno.
Sistemas de trading basados en el cruce de dos medias móviles
Esta variante de sistema de trading es bastante popular. Ahora vamos a analizar el algoritmo subyacente de estas estrategias. Este es el algoritmo de entrada para las posiciones largas:
Para las posiciones cortas es así:
Se pueden utilizar dos medias móviles idénticas con parámetros diferentes a modo de indicadores. Se asume que el parámetro que define la promediación de la media MovA siempre es más pequeño que el de la media MovB. Así, en este sistema de trading MovA es la media rápida y MovB es la media lenta. Esta es la implementación de la variante basada en dos medias JMA:
//+==================================================================+ //| Exp_6.mq4 | //| Copyright © 2007, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2007, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //----+ +-------------------------------------------------------------------------------+ //---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE COMPRA extern bool Test_Up = true;//filtro de la dirección de cálculo extern int Timeframe_Up = 240; extern double Money_Management_Up = 0.1; extern int LengthA_Up = 4; // profundidad de suavizado de la media rápida extern int PhaseA_Up = 100; // parámetro que varía en el rango //-100 ... +100, influye en la calidad del proceso de transición de la media rápida extern int IPCA_Up = 0;/* Selección de precios donde se calcula el indicador por la media rápida (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int LengthB_Up = 4; // incremento de la profundidad de suavizado de la media lenta extern int PhaseB_Up = 100; // parámetro que varía en el rango //-100 ... +100, influye en la calidad del proceso de transición de la media lenta; extern int IPCB_Up = 0;/* Selección de precios donde se calcula el indicador por la media lenta (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int STOPLOSS_Up = 50; // stop loss extern int TAKEPROFIT_Up = 100; // take profit extern bool ClosePos_Up = true; // cierre de posición forzado permitido //----+ +-------------------------------------------------------------------------------+ //---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE VENTA extern bool Test_Dn = true;//filtro de la dirección de cálculo extern int Timeframe_Dn = 240; extern double Money_Management_Dn = 0.1; extern int LengthA_Dn = 4; // profundidad de suavizado de la media rápida extern int PhaseA_Dn = 100; // parámetro que varía en el rango // -100 ... +100, influye en la calidad del proceso de transición de la media rápida; extern int IPCA_Dn = 0;/* Selección de precios donde se calcula el indicador por la media rápida (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int LengthB_Dn = 4; // incremento de la profundidad de suavizado de la media lenta extern int PhaseB_Dn = 100; // parámetro que varía en el rango // -100 ... +100, influye en la calidad del proceso de transición de la media lenta; extern int IPCB_Dn = 0;/* Selección de precios donde se calcula el indicador por la media lenta(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int STOPLOSS_Dn = 50; // stop loss extern int TAKEPROFIT_Dn = 100; // take profit extern bool ClosePos_Dn = true; // cierre de posición forzado permitido //----+ +-------------------------------------------------------------------------------+ //---- Variables enteras para el cálculo mínimo de las barras int MinBar_Up, MinBar_Dn; //+==================================================================+ //| Funciones personalizadas del Asesor Experto | //+==================================================================+ #include <Lite_EXPERT1.mqh> //+==================================================================+ //| Función de inicialización del EA | //+==================================================================+ int init() { //---- Comprobación del valor de la variable Timeframe_Up if (Timeframe_Up != 1) if (Timeframe_Up != 5) if (Timeframe_Up != 15) if (Timeframe_Up != 30) if (Timeframe_Up != 60) if (Timeframe_Up != 240) if (Timeframe_Up != 1440) Print(StringConcatenate("El parámetro Timeframe_Up no puede ", "ser igual a ", Timeframe_Up, "!!!")); //---- Comprobación del valor de la variable Timeframe_Dn if (Timeframe_Dn != 1) if (Timeframe_Dn != 5) if (Timeframe_Dn != 15) if (Timeframe_Dn != 30) if (Timeframe_Dn != 60) if (Timeframe_Dn != 240) if (Timeframe_Dn != 1440) Print(StringConcatenate("El parámetro Timeframe_Dn no puede ", "ser igual a ", Timeframe_Dn, "!!!")); //---- Inicialización de variables MinBar_Up = 4 + 30; MinBar_Dn = 4 + 30; //---- fin de la inicialización return(0); } //+==================================================================+ //| Función de desinicialización del EA | //+==================================================================+ int deinit() { //----+ //---- Fin de la desinicialización del EA return(0); //----+ } //+==================================================================+ //| Función de iteración del Asesor Experto | //+==================================================================+ int start() { //----+ Declaración de variables locales int bar; double MovA[2], MovB[2]; //----+ Declaración de variables estáticas static int LastBars_Up, LastBars_Dn; static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop; //----++ CÓDIGO DE LAS POSICIONES LARGAS if (Test_Up) { int IBARS_Up = iBars(NULL, Timeframe_Up); if (IBARS_Up >= MinBar_Up) { if (LastBars_Up != IBARS_Up) { //----+ Inicialización de variables BUY_Sign = false; BUY_Stop = false; LastBars_Up = IBARS_Up; //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iCustom(NULL, Timeframe_Up, "JJMA", LengthA_Up, PhaseA_Up, 0, IPCA_Up, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iCustom(NULL, Timeframe_Up, "JJMA", LengthA_Up + LengthB_Up, PhaseB_Up, 0, IPCB_Up, 0, bar); //----+ DEFINICIÓN DE SEÑALES DE TRADING if ( MovA[1] < MovB[1]) if ( MovA[0] > MovB[0]) BUY_Sign = true; if ( MovA[0] > MovB[0]) BUY_Stop = true; } //----+ EJECUCIÓN DE SEÑALES if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up)) return(-1); if (ClosePos_Up) if (!CloseOrder1(BUY_Stop, 1)) return(-1); } } //----++ CÓDIGO DE LAS POSICIONES CORTAS if (Test_Dn) { int IBARS_Dn = iBars(NULL, Timeframe_Dn); if (IBARS_Dn >= MinBar_Dn) { if (LastBars_Dn != IBARS_Dn) { //----+ Inicialización de variables SELL_Sign = false; SELL_Stop = false; LastBars_Dn = IBARS_Dn; //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iCustom(NULL, Timeframe_Dn, "JJMA", LengthA_Dn, PhaseA_Dn, 0, IPCA_Dn, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iCustom(NULL, Timeframe_Dn, "JJMA", LengthA_Dn + LengthB_Dn, PhaseB_Dn, 0, IPCB_Dn, 0, bar); //----+ DEFINICIÓN DE SEÑALES DE TRADING if ( MovA[1] > MovB[1]) if ( MovA[0] < MovB[0]) SELL_Sign = true; if ( MovA[0] < MovB[0]) SELL_Stop = true; } //----+ EJECUCIÓN DE SEÑALES if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn)) return(-1); if (ClosePos_Dn) if (!CloseOrder1(SELL_Stop, 2)) return(-1); } } //----+ return(0); } //+------------------------------------------------------------------+
Sistemas de trading basados en el cruce de dos osciladores
Esta estrategia comercial se puede utilizar con medias móviles y con osciladores; en este caso último, como escribí en el artículo anterior, es mejor colocar órdenes pendientes en vez de entrar al mercado inmediatamente cuando se reciben las señales. A modo de ejemplo se puede utilizar un diagrama MACD.
El siguiente algoritmo coloca órdenes pendientes BuyLimit:
Y este es el algoritmo de las órdenes de tipo SellLimit:
El código de este EA se parece al del anterior://+==================================================================+ //| Exp_7.mq4 | //| Copyright © 2007, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2007, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //----+ +---------------------------------------------------------------------------+ //---- PARÁMETROS DE ENTRADA PARA LAS OPERACIONES DE COMPRA extern bool Test_Up = true; // filtro de dirección de cálculo extern int Timeframe_Up = 240; extern double Money_Management_Up = 0.1; extern int FST_period_Up = 12; // periodo de la media rápida extern int SLO_period_Up = 22; // incremento del periodo de la media lenta extern int SIGN_period_Up = 8; // periodo de la línea de señal extern int Price_Up = 0; // selección de precios con los que se calcula MACD extern int STOPLOSS_Up = 50; // stop loss extern int TAKEPROFIT_Up = 100; // take profit extern int PriceLevel_Up =40; // diferencia entre el precio actual y // el precio de lanzamiento de la orden pendiente extern bool ClosePos_Up = true; // cierre forzado de la posición permitido //----+ +---------------------------------------------------------------------------+ //---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE VENTA extern bool Test_Dn = true; // filtro de dirección de cálculo extern int Timeframe_Dn = 240; extern double Money_Management_Dn = 0.1; extern int FST_period_Dn = 12; // periodo de la media rápida extern int SLO_period_Dn = 22; // incremento del periodo de la media lenta extern int SIGN_period_Dn = 8; // periodo de la línea de señal extern int Price_Dn = 0; // selección de precios con los que se calcula MACD extern int STOPLOSS_Dn = 50; // stop loss extern int TAKEPROFIT_Dn = 100; // take profit extern int PriceLevel_Dn =40; // diferencia entre el precio actual y // el precio de lanzamiento de la orden pendiente extern bool ClosePos_Dn = true; // cierre forzado de la posición permitido //----+ +---------------------------------------------------------------------------+ //---- Variables enteras para el cálculo mínimo de las barras int MinBar_Up, MinBar_Dn; //+==================================================================+ //| Funciones personalizadas del Asesor Experto | //+==================================================================+ #include <Lite_EXPERT1.mqh> //+==================================================================+ //| Función de inicialización del Asesor Experto | //+==================================================================+ int init() { //---- Comprobación del valor de la variable Timeframe_Up if (Timeframe_Up != 1) if (Timeframe_Up != 5) if (Timeframe_Up != 15) if (Timeframe_Up != 30) if (Timeframe_Up != 60) if (Timeframe_Up != 240) if (Timeframe_Up != 1440) Print(StringConcatenate("El parámetro Timeframe_Up no puede ", "ser igual a ", Timeframe_Up, "!!!")); //---- Comprobación del valor de la variable Timeframe_Dn if (Timeframe_Dn != 1) if (Timeframe_Dn != 5) if (Timeframe_Dn != 15) if (Timeframe_Dn != 30) if (Timeframe_Dn != 60) if (Timeframe_Dn != 240) if (Timeframe_Dn != 1440) Print(StringConcatenate("El parámetro Timeframe_Dn no puede ", "ser igual a ", Timeframe_Dn, "!!!")); //---- Inicialización de variables MinBar_Up = 4 + FST_period_Up + SLO_period_Up + SIGN_period_Up; MinBar_Dn = 4 + FST_period_Dn + SLO_period_Dn + SIGN_period_Dn; //---- fin de la inicialización return(0); } //+==================================================================+ //| función de desinicialización del experto | //+==================================================================+ int deinit() { //----+ //---- Fin de desinicialización del EA return(0); //----+ } //+==================================================================+ //| Función de iteración del Asesor Experto | //+==================================================================+ int start() { //----+ Declaración de variables locales int bar; double MovA[2], MovB[2]; //----+ Declaración de variables estáticas static int LastBars_Up, LastBars_Dn; static datetime StopTime_Up, StopTime_Dn; static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop; //----++ CÓDIGO DE LAS POSICIONES LARGAS if (Test_Up) { int IBARS_Up = iBars(NULL, Timeframe_Up); if (IBARS_Up >= MinBar_Up) { if (LastBars_Up != IBARS_Up) { //----+ Inicialización de variables BUY_Sign = false; BUY_Stop = false; LastBars_Up = IBARS_Up; StopTime_Up = iTime(NULL, Timeframe_Up, 0) + 60 * Timeframe_Up; //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iMACD(NULL, Timeframe_Up, FST_period_Up, FST_period_Up + SLO_period_Up, SIGN_period_Up, Price_Up, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iMACD(NULL, Timeframe_Up, FST_period_Up, FST_period_Up + SLO_period_Up, SIGN_period_Up, Price_Up, 1, bar); //----+ DEFINICIÓN DE SEÑALES DE TRADING if ( MovA[1] < MovB[1]) if ( MovA[0] > MovB[0]) BUY_Sign = true; if ( MovA[0] > MovB[0]) BUY_Stop = true; } //----+ EJECUCIÓN DE SEÑALES if (!OpenBuyLimitOrder1(BUY_Sign, 1, Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up, PriceLevel_Up, StopTime_Up)) return(-1); if (ClosePos_Up) if (!CloseOrder1(BUY_Stop, 1)) return(-1); } } //----++ CÓDIGO DE LAS POSICIONES CORTAS if (Test_Dn) { int IBARS_Dn = iBars(NULL, Timeframe_Dn); if (IBARS_Dn >= MinBar_Dn) { if (LastBars_Dn != IBARS_Dn) { //----+ Inicialización de variables SELL_Sign = false; SELL_Stop = false; LastBars_Dn = IBARS_Dn; StopTime_Dn = iTime(NULL, Timeframe_Dn, 0) + 60 * Timeframe_Dn; //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iMACD(NULL, Timeframe_Dn, FST_period_Dn, FST_period_Dn + SLO_period_Dn, SIGN_period_Dn, Price_Dn, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iMACD(NULL, Timeframe_Dn, FST_period_Dn, FST_period_Dn + SLO_period_Dn, SIGN_period_Dn, Price_Dn, 1, bar); //----+ DEFINICIÓN DE SEÑALES DE TRADING if ( MovA[1] > MovB[1]) if ( MovA[0] < MovB[0]) SELL_Sign = true; if ( MovA[0] < MovB[0]) SELL_Stop = true; } //----+ EJECUCIÓN DE SEÑALES if (!OpenSellLimitOrder1(SELL_Sign, 2, Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn, PriceLevel_Dn, StopTime_Dn)) return(-1); if (ClosePos_Dn) if (!CloseOrder1(SELL_Stop, 2)) return(-1); } } //----+ return(0); } //+------------------------------------------------------------------+
Conclusión
Llegados aquí ya podemos terminar este artículo. Hemos implementado una nueva variante de sistema de trading en forma de Asesor Experto combinando indicadores. Espero que este artículo sirva de ayuda a los programadores principiantes de EA. Con lo expuesto aquí usted podrá mejorar sus habilidades, y le ayudará a tomar ideas para convertir algoritmos correctamente formalizados en Asesores Expertos completamente funcionales.