Redes Neuronales en Metatrader

En los últimos meses parece que estamos asistiendo a un «revival» de las redes neuronales aplicadas al trading, sobre con la aparición del paquete gratuito Fann2MQL para Metatrader que permite utilizar redes neuronales dentro de los Expert Advisors. Para remate, el interés se ha avivado al conocerse que los sistemas utilizados por el ganador de la edición 2007 del Automated Trading Championship se basaban en redes neuronales. Personalmente debo reconocer que soy bastante escéptico (aunque también algo profano ;)) sobre estos temas, si bien estuve experimentando con Neuroshell allá por el año 2001 y los resultados no fueron demasiado interesantes: en el mejor de los casos, la capacidad predictiva de las redes neuronales entrenadas no superaba el 40%. No obstante, posiblemente este tipo de algoritmos haya evolucionado y mejorado en los últimos años por lo que quizás sea un buen momento para retomar la investigación.

¿Qué es una Red Neuronal?
Según la Wikipedia, las redes neuronales consisten en una simulación de las propiedades observadas en los sistemas neuronales biológicos a través de modelos matemáticos recreados mediante mecanismos artificiales. El objetivo es conseguir que una máquinas produzca respuestas similares a las que es capaz de dar el cerebro, las cuales se caracterizan por su generalización y su robustez.

Una red neuronal se compone de unidades llamadas neuronas. Cada neurona recibe una serie de entradas a través de interconexiones y emite una salida. Esta salida viene dada por tres funciones:

  • Una función de propagación (también conocida como función de excitación), que por lo general consiste en el sumatorio de cada entrada multiplicada por el peso de su interconexión (valor neto). Si el peso es positivo, la conexión se denomina excitatoria; si es negativo, se denomina inhibitoria.
  • Una función de activación, que modifica a la anterior. Puede no existir, siendo en este caso la salida la misma función de propagación.
  • Una función de transferencia, que se aplica al valor devuelto por la función de activación. Se utiliza para acotar la salida de la neurona y generalmente viene dada por la interpretación que queramos darle a dichas salidas. Algunas de las más utilizadas son la función sigmoidea (para obtener valores en el intervalo [0,1]) y la tangente hiperbólica (para obtener valores en el intervalo [-1,1]).

A la hora de implementar una red neuronal como parte de un programa o sistema informático, se pueden distinguir 3 fases básicas:

  • Diseño: en esta fase se elige el tipo de red neuronal a usar (la arquitectura o topología), el número de neuronas que la compondrán, etc.
  • Entrenamiento: en esta fase se le presentan a la red neuronal una serie de datos de entrada y datos de salida (resultados), para que a partir de ellos pueda aprender.
  • Uso: se le suministran las entradas pertinentes a la red, y esta genera las salidas en función de lo que ha aprendido en la fase de entrenamiento.

Posiblemente un ejemplo de su utilización permita comprender mejor su funcionamiento: supongamos que vamos a aplicar una red neuronal al diagnóstico de cáncer a través de imágenes. En primer lugar deberemos entrenar el sistema mostrándole imágenes de tejidos cancerígenos y sanos, clasificados correctamente, así como otras variables (edad del paciente, procedencia anatómica del tejido, etc.). Si el entrenamiento se realiza correctamente, al término del mismo el sistema deberá ser capaz de analizar imágenes de tejidos no clasificados y determinar de qué tipo son (sano/cancerígeno) con cierto grado fiabilidad. Lo que hacemos en definitiva mediante el proceso de entrenamiento es enseñar a la red neuronal qué salidas deseamos que nos dé en función de los datos que hemos introducido previamente; en este proceso la red neuronal “aprende” a detectar patrones, pautas, o modelos ocultos en los datos de entrada.

Las características de las redes neuronales las hacen bastante apropiadas para utilizarlas en aplicaciones en las que no se dispone a priori de un modelo identificable que pueda ser programado, pero se dispone de un conjunto básico de ejemplos de entrada (previamente clasificados o no). Por ello son habitualmente utilizadas en problemas de clasificación y reconocimiento de patrones tales como reconocimiento de textos, reconocimiento del habla, simulación de centrales de producción de energía, detección de explosivos o identificación de blancos de radares; y por supuesto también en el campo que nos ocupa, el trading en los mercados financieros.

Y llegó FANN
No hace mucho nos llegó la noticia a través del Foro (gracias YsEkU ;)) de la aparición de una librería de redes neuronales para Metatrader denominada Fann2MQL. Y lo mejor de todo es que es gratuita! El uso típico de esta librería es el de crear redes neuronales no muy complejas, entrenarlas y ejecutarlas, teniendo la posibilidad de almacenar los resultados obtenidos. También podemos incorporarlas dentro de nuestros propios Expert Advisors (EA) e indicadores o incluso para tratar de predecir el comportamiento del precio, si bien el autor de FANN ya advierte de que los resultados y calidad de las predicciones obtenidas, así como las posibilidades de lograr beneficios con ellas, son realmente dudosos.

Pero para poder comprender la utilidad y las posibilidades de esta librería, lo mejor es que veamos algunos ejemplos prácticos. Para poder seguirlos se recomienda instalar previamente dicha librería.

1. Filtrando señales generadas por el MACD dentro de un Expert Advisor
La primera aplicación que veremos es la de un sencillo filtro basado en redes neuronales para la señales generadas por una estrategia simple basada en el MACD (pueden descargar el EA completo haciendo click aquí).

Diseccionamos algunas partes del código. Lo primero es declarar las variables globales necesarias para que Fann2MQL funcione dentro del EA. En particular, la variable ANN_PATH define en qué carpeta almacenaremos los resultados:

// Include Neural Network package
#include <Fann2MQL.mqh>

// Global defines
#define ANN_PATH "C:\ANN\"

// EA Name
#define NAME "NeuroMACD"


//---- input parameters
extern double Lots=0.1;extern double StopLoss=180.0;
extern double TakeProfit=270.0;
extern int FastMA=18;
extern int SlowMA=36;
extern int SignalMA=21;
extern double Delta=-0.6;
extern int AnnsNumber=16;
extern int AnnInputs=30;
extern bool NeuroFilter=true;
extern bool SaveAnn=false;
extern int DebugLevel=2;
extern double MinimalBalance=100;
extern bool Parallel=true;

// Global variables

// Path to anns folder
string AnnPath;


// Trade magic number
int MagicNumber=65536;


// AnnsArray[ann#] - Array of anns
int AnnsArray[];


// All anns loded properly status
bool AnnsLoaded=true;


// AnnOutputs[ann#] - Array of ann returned returned
double AnnOutputs[];


// InputVector[] - Array of ann input data
double InputVector[];


// Long position ticket
int LongTicket=-1;


// Short position ticket
int ShortTicket=-1;


// Remembered long and short network inputs
double LongInput[];
double ShortInput[];

Seguidamente en la función init () introducimos diferentes parámetros. En particular AnnInputs determina el número de entradas que recibirá la red neuronal. En este caso serán 3 que se corresponden con los parámetros SlowMA, FastMA y SignalMA del MACD:

int init()

{
int i,ann;

if(!is_ok_period(PERIOD_M5))

{

debug(0,"Wrong period!");

return(-1);

}

AnnInputs=(AnnInputs/3)*3; // Make it integer divisible by 3

if(AnnInputs<3)

{

debug(0,"AnnInputs too low!");

}

// Compute MagicNumber and AnnPath

MagicNumber+=(SlowMA+256*FastMA+65536*SignalMA);

AnnPath=StringConcatenate(ANN_PATH,NAME,"-",MagicNumber);

// Initialize anns

ArrayResize(AnnsArray,AnnsNumber);

for(i=0;i<AnnsNumber;i++)

{

if(i%2==0)

{

ann=ann_load(AnnPath+"."+i+"-long.net");

}

else {

ann=ann_load(AnnPath+"."+i+"-short.net");

}

if(ann<0)

AnnsLoaded=false;

AnnsArray[i]=ann;

}

ArrayResize(AnnOutputs,AnnsNumber);

ArrayResize(InputVector,AnnInputs);

ArrayResize(LongInput,AnnInputs);

ArrayResize(ShortInput,AnnInputs);


// Initialize Intel TBB threads

f2M_parallel_init();

return(0);

}

A continuación inicializamos las librerías de redes neuronales. Para ello, salvo que carguemos datos ya existentes con f2M_create_from_file, la red neuronal se configura con la función f2M_create_standard. En particular, la estructura de esta función suponiendo que existen K capas de entrada y una capa de salida es la siguiente:

f2M_create_standard (Nº de Capas, Nº de Neuronas Capa1, Nº de Neuronas Capa2, … , Nº de Neuronas CapaK, Nº de Neuronas CapaSalida)

En nuestro ejemplo, la red neuronal constará de 4 capas (1 capa de entrada, 2 capas ocultas y 1 de salida) con AnnInput neuronas en la capa de entrada y en la primera capa oculta, AnnInput/2+1 neuronas en la segunda capa oculta y 1 neurona en la capa de salida. La función de transferencia aplicada en cada capa será de tipo sigmoidea tal y como se define en f2M_set_act_function_hidden(). Finalmente inicializamos los pesos de las conexiones dentro de la red con f2m_randomize_weights() utilizando un rango inicial entre -0.4 y 0.4.

int ann_load(string path)
{
int ann=-1;

/* Load the ANN */
ann=f2M_create_from_file(path);
if(ann!=-1)

{
debug(1,"ANN: '"+path+"' loaded successfully with handler "+ann);
}

if(ann==-1)
{
/* Create ANN */
ann=
f2M_create_standard(4,AnnInputs,AnnInputs,AnnInputs/2+1,1);

f2M_set_act_function_hidden(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
f2M_set_act_function_output(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
f2M_randomize_weights(ann,-0.4,0.4);

debug(1,"ANN: '"+path+"' created successfully with handler "+ann);
}

if(ann==-1)

{
debug(0,"INITIALIZING NETWORK!");
}

return(ann);
}

Previamente al comienzo de las operaciones, debemos normalizar los valores de entrada, lo que se realiza con el siguiente código:

void ann_prepare_input()

{
int i;

for(i=0;i<=AnnInputs-1;i=i+3)

{

InputVector[i]=10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_MAIN,i*3);

InputVector[i+1]=10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_SIGNAL,i*3);

InputVector[i+2]=InputVector[i-2]-InputVector[i-1];

}

}

Veamos ahora la función start (). Dentro de este apartado cabe destacar que:

  • Si la variable NeuroFilter no está activada, no se produce procesado de las señales del MACD y las órdenes son enviadas al mercado sin filtrar.
  • Una vez cerrada una operación de compra o venta, la red neuronal es entrenada con el vector train_output[] el cual permite discriminar qué operaciones producen beneficios (+1) o con pérdidas (-1).
int start()
{   int i;

bool BuySignal=false;
bool SellSignal=false;

double train_output[1];

/* Is trade allowed? */
if(!trade_allowed())

{
return(-1);
}

/* Prepare and run neural networks */

ann_prepare_input();
run_anns();

/* Calculate last and previous MACD values.
Lag one bar as current bar is building up */

double MacdLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_MAIN,1);
double MacdPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_MAIN,2);
double SignalLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_SIGNAL,1);
double SignalPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,
PRICE_CLOSE,MODE_SIGNAL,2);

/* BUY signal */
if(MacdLast>SignalLast && MacdPrev<SignalPrev)

{
BuySignal=true;
}
/* SELL signal */
if(MacdLast<SignalLast && MacdPrev>SignalPrev)

{
SellSignal=true;
}

/* No Long position */
if(LongTicket==-1)

{
/* BUY signal */
if(BuySignal)
{
/* If NeuroFilter is set use ann wise to decide :) */
if(!NeuroFilter || ann_wise_long()>Delta)

{
LongTicket=
OrderSend(Symbol(),OP_BUY,Lots,Ask,3,

Bid-StopLoss*Point,
Ask+TakeProfit*Point,
NAME+"-"+"L ",MagicNumber,0,Blue);

}
/* Remember network input */
for(i=0;i<AnnInputs;i++)

{
LongInput[i]=InputVector[i];
}

}
} else {
/* Maintain long position */
OrderSelect(LongTicket,SELECT_BY_TICKET);

if(OrderCloseTime()==0)
{
// Order is opened
if(SellSignal && OrderProfit()>0)

{
OrderClose(LongTicket,Lots,Bid,3);
}

}
if(OrderCloseTime()!=0)
{
// Order is closed

LongTicket=-1;
if(OrderProfit()>=0)

{
train_output[0]=1;
} else {

train_output[0]=-1;
}
for(i=0;i<AnnsNumber;i+=2)

{
ann_train(AnnsArray[i],LongInput,train_output);
}

}
}

/* No short position */
if(ShortTicket==-1)

{
if(SellSignal)
{
/* If NeuroFilter is set use ann wise to decide ;) */
if(!NeuroFilter || ann_wise_short()>Delta)

{
ShortTicket=
OrderSend(Symbol(),OP_SELL,Lots,Bid,3,

Ask+StopLoss*Point,
Bid-TakeProfit*Point,NAME+"-"+"S ",

MagicNumber,0,Red);
}
/* Remember network input */
for(i=0;i<AnnInputs;i++)

{
ShortInput[i]=InputVector[i];
}

}
} else {
/* Maintain short position */
OrderSelect(ShortTicket,SELECT_BY_TICKET);

if(OrderCloseTime()==0)
{
// Order is opened
if(BuySignal && OrderProfit()>0)

{
OrderClose(LongTicket,Lots,Bid,3);
}

}
if(OrderCloseTime()!=0)
{
// Order is closed

ShortTicket=-1;
if(OrderProfit()>=0)

{
train_output[0]=1;
} else {

train_output[0]=-1;
}
for(i=1;i<AnnsNumber;i+=2)

{
ann_train(AnnsArray[i],ShortInput,train_output);
}

}
}

return(0);
}
//+------------------------------------------------------------------+

Veamos ahora cómo trabajar con este código. Lo primero es entrenar a la red neuronal. Para ello, primero generaremos un conjunto amplio de operaciones con el que entrenarla. Para ello utilizaremos el EURUSD en barras de 5 minutos durante el periodo comprendido entre el 31/12/2007 y el 01/01/2009. Previamente a la realización del backtest desactivamos las variables NeuroFilter y SaveAnn y asignamos el valor 0 a AnnsNumber, con el fin de evitar que la red neuronal actúe por ahora. Los resultados obtenidos son los siguientes:

 

Seguidamente activamos la variable SaveAnn y asignamos el valor 30 a AnnsNumber y ejecutamos nuevamente el backtest. Los resultados obtenidos serán los mismos pero el proceso será mucho más lento debido al procesamiento de la red neuronal. Antes de proceder con este backtest no debemos olvidarnos de crear antes la carpeta especificada en el código para almacenar los resultados, la cual viene definida en la variable ANN_PATH (en nuestro ejemplo C:ANN)

Una vez finalizado el proceso, ha llegado el momento de ver cómo se comporta. Las primeras pruebas las realizaremos sobre los datos utilizados para el entrenamiento. Para ello activamos la variable NeuroFilter y desactivamos SaveAnn y realizamos el backtest. Los resultados obtenidos se muestran a continuación:

 

Tal y como cabía esperar, el beneficio obtenido es ligeramente mayor al igual que el profit factor. Sin embargo esto no nos dice nada acerca de cómo funcionará con datos que no estaban incluidos en la muestra utilizada para el entrenamiento. Para ello, vamos a realizar una prueba externa tomando datos del periodo comprendido entre el 01/01/2009 y el 22/03/2009. Ahora los resultados son los siguientes:

 

Si bien no hemos logrado unos beneficios espectaculares, si lo comparamos con los resultados obtenidos en el mismo periodo con el EA sin filtrar (mostrados en la siguiente imagen), podemos ver como las redes neuronales han convertido un sistema perdedor en uno ganador, al menos en ese periodo. Asimismo el comportamiento de la curva de beneficios es mucho menos suave y estable en el caso del EA sin filtrar.

 

2. Enseñando a la red neuronal a reconocer patrones
Otro ejemplo de uso muy habitual de las redes neuronales es el de «aprender» patrones sencillos. Supongamos que deseamos enseñar a la red el siguiente patrón:

Si A< B y B < C entonces la respuesta esperada es 1
Si A< B y B > C entonces la respuesta esperada es 0
Si A> B y B > C entonces la respuesta esperada es 0
Si A> B y B < C entonces la respuesta esperada es 1

Para aplicar este patrón al trading podemos imaginar por ejemplo que A, B y C son los precios de cierre de tres velas, siendo C la más antigua y A la más reciente. El valor 1 se correspondería con un mercado alcista y el valor 0 con un mercado bajista.

Si analizamos el código utilizado para enseñar a nuestra red neuronal este patrón (pueden descargarlo haciendo click aquí) podemos ver que se trata de una red neuronal que consta de 4 capas. La capa de entrada consta de 3 neuronas, la primera capa oculta consta de 8 neuronas, la segunda capa oculta tiene 5 neuronas y la capa de salida tiene 1. La función de transferencia seleccionada es nuevamente la sigmoidea.

En la siguiente imagen se muestra un ejemplo de los resultados obtenidos utilizando el código anterior. Por supuesto, dicho código podría utilizarse para detectar patrones de mercado reemplazando los números por cierres de mercado, valores de medias móviles o cualquier otro input que se nos ocurra.

 

Conclusión
Las aplicaciones mostradas en este artículo con la librería FANN no son más que la punta del iceberg del potencial de las redes neuronales. Si bien no destacan por su capacidad predictiva (y menos en un campo tan complejo como el de las series temporales de cotizaciones), es en la ejecución de tareas de clasificación y, por ende, de filtrado en lo que realmente brillan con luz propia. Esto unido a su elevada flexibilidad, al no imponer estructuras a priori, permiten utilizarlas para filtrar señales de trading, mejorar el proceso de optimización de variables de un sistema, construir escenarios de riesgo en base a diferentes comportamientos de nuestra curva de beneficios, etc. En definitiva, todo aquello que la imaginación nos permita.

Un saludo,
X-Trader

COMPARTIR EN: