miércoles, 24 de abril de 2013

¿Cúantos Sistemas de Trading Existen?

-->
En una entrada anterior de este blog vimos cómo mediante el uso de gramáticas se podían generar automáticamente sistemas de trading. Evidentemente la siguiente pregunta está clara, de entre todos estos posibles sistemas, ¿cómo buscamos aquellos que son rentables? Existen muchas alternativas para la búsqueda, desde el uso de algoritmos genéticos (de ahí lo de gramáticas evolutivas), hasta las nubes de partículas, pasando por la amplia colección de técnicas de optimización que existen (y de las que algún día hablaremos en este blog). Sin embargo, en esta entrada de blog vamos a plantearnos otra opción, que aunque en un principio parece una locura, quizás no lo sea tanto. La pregunta es: ¿podríamos evaluar TODOS los sistemas de trading existentes?

Imaginemos que queremos trabajar con sistemas de trading basados en la combinación de indicadores técnicos. En el paquete TTR de R tenemos una colección de más de 30 indicadores. ¿De cuantas maneras posibles se podrían combinar? Empecemos el caso más básico, consistente en comparar un indicador técnico con un número entero; si es superior a dicho entero, nos ponemos en corto, y si es inferior, nos ponemos en largo:

SI (“indicador” op_bol “entero”) largo SINO corto

Por ejemplo,

SI (RSI(14) < 10) largo SINO corto

Evidentemente, la mayoría de los sistemas así desarrollados no tendrían ningún sentido, y darían pérdidas, pero si queremos buscar sistemas nuevos que no hayan sido antes explorados, no deberíamos descartar ninguna idea a priori.

¿Cuantos sistemas existen basados en este formato? Pues si contamos con 33 posibles indicadores técnicos, con un conjunto de 30 posibles enteros a evaluar para cada indicador (de 0 a 9, de 10 a 90 con incrementos de 10, y de 100 a 900 con incrementos de 100), y dos operadores boleanos ('>' y '<') hacen un total de 59.400 sistemas. Ahora bien, ¿cuanto se tarda en evaluar cada sistema? Según la metodología que utilizamos en Entropycs (pruebas multiperiodo y multimercado) tardamos 30 minutos en evaluar cada uno, por lo que evaluarlos todos nos llevaría algo más de 3 años. Evidentemente parece que la idea no es del todo viable. Pero existe mucho margen para la optimización: mejoras en el propio código R de evaluación, el uso de procesadores multinúcleo, el uso de clusters de PCs, o la paralelización mediante tarjetas gráficas (GPUs). En Entropycs estamos convencidos de que con relativamente poco esfuerzo podríamos evaluar un sistema en menos de 10 segundos, lo que reduciría el tiempo de evaluación de todos los sistemas a tan sólo una semana, que es algo más que asumible.

Para el caso de combinaciones de 2 indicadores mediante la fórmula:

<indicador> <op_rel> <indicador>

Nos llevaría a los 2 millones de sistemas, que a 10 segundos, tardaríamos casi un año en su evaluación. Y a más indicadores, el número de sistemas posibles se dispara. Para los sistemas de la forma:

<indicador> <op_rel> <indicador> <op_bol> <indicador> <op_rel> <indicador>

nos vamos a los 7 billones de sistemas (billones de los europeos). Y para los sistemas de tipo:

<indicador> <op_art> <indicador> <op_rel> <indicador> <op_bol> <indicador> <op_art> <indicador> <op_rel> <indicador>

ya nos vamos a 31*10^18 sistemas. Imposible de evaluar, ¿no?

Bueno, quizás no tanto. Pensemos en el caso del ajedrez, donde existen 2*10^116 partidas posibles, y los programas de ajedrez no lo hacen tan mal (de hecho, hace años que ningún humano es capaz de ganarle al mejor de los programas). La supercomputadora de ajedrez Deep Blue era capaz de evaluar 200 millones de jugadas por segundo (y de aquello hace 15 años). Utilizando estos números, sería cuestión de horas evaluar los sistemas con 4 indicadores, y los de cinco empezaría a dejar de ser una utopía (del orden de 4 años).

Y, sobre todo, recordemos lo que dice la sabiduría popular del trading: “si con 5 indicadores tu sistema no es rentable, nunca lo será por muchos más indicadores que añadas”.

jueves, 18 de abril de 2013

Buscando Huecos en los Datos

-->
En una serie de entradas de este blog vimos cómo cargar los datos históricos de forex en una base de datos relacional, y cómo disponer los datos en este formato tenía ciertas ventajas a la hora de proceder a su análisis. En esta ocasión vamos a ver otra de las ventajas de las bases de datos, en concreto, cómo nos pueden ayudar a la hora de buscar huecos en los datos históricos.

Todos los ficheros de datos históricos tienen huecos. A veces, incluso, nos podemos encontrar con saltos que comprenden varios días, y que pueden llegar a resultar muy peligrosos, porque pueden distorsionar de forma importante nuestro análisis. Por tanto, es importante conocer la calidad de los datos que estamos utilizando en nuestros back tests.

Una vez tengamos los datos en la base de datos en el formato en el que habíamos propuesto en las entradas anteriores de nuestro blog, podemos proceder a su análisis.

Si queremos conocer el número de huecos de una determinada longitud que existen en el histórico de datos, escribiremos:

SELECT count(*),
    ((SELECT MIN(e2.unix) FROM eurusd e2 
        WHERE e2.unix > e1.unix) - e1.unix - 60) / 60 as minutes
FROM eurusd e1 WHERE minutes = 1

donde 'minutes' es la longitud en minutos del hueco que estamos analizando. También podríamos ver concretamente en qué fecha y hora se producen dichos huecos, para ello escribiríamos:

SELECT DATETIME(e1.date, '+1 minutes') AS start_date,
     DATETIME((SELECT MIN(e2.date) FROM eurusd e2 WHERE e2.date > e1.date), '-1 minutes') AS end_date,
     ((SELECT MIN(e2.unix) FROM eurusd e2 WHERE e2.unix > e1.unix) - e1.unix - 60) / 60 as minutes
FROM eurusd e1 WHERE minutes = 1

o si lo que queremos es sacar una tabla que nos indique para cada longitud de hueco el número de ellos que hay, escribiríamos:

SELECT count(*),
    ((SELECT MIN(e2.unix) FROM eurusd e2 WHERE e2.unix > e1.unix) - e1.unix - 60) / 60 as minutes
FROM eurusd e1 GROUP BY minutes

Finalmente, a modo de ejemplo voy a mostrar los resultados de un análisis comparativo realizado sobre dos históricos de datos, uno de ellos descargado desde la página web de Forex Tester, y el segundo utilizando el Centro de Historiales de la herramienta MetaTrader y a través del broker XTB.

El siguiente gráfico muestra para cada longitud de hueco desde 1 a 30 minutos el número de huecos encontrados:


Como se puede observar, el número de huecos de longitudes 1, 2, 3 y 4 es tremendamente grande, del orden de 3 huecos al día para XTB, y de 4 para Forex Tester. Este hecho debería ser tenido en cuenta por todos aquellos que utilizan sistemas automáticos que trabajan sobre barras de un minuto, sobre todo si se basan en optimizaciones y backtests de los mismos.

martes, 16 de abril de 2013

Evolución Gramatical, Sistemas de Trading y R (2/2)

-->
En una entrada anterior de este blog vimos lo que era una gramática, y cómo se podían utilizar las gramáticas para crear automáticamente sistemas de trading. En esta entrada vamos a ver cómo se puede implementar todo esto con R. Aunque el código es lo suficientemente genérico para poder utilizar cualquier gramática, vamos a seguir con nuestro ejemplo de gramática para la generación de números enteros, para no complicar demasiado la cosa.

Lo primero que tenemos es que definir nuestra gramática. Para ello utilizamos una lista de R con las reglas de producción. Cada elemento de la lista es una regla, y dentro de cada regla, las distintas opciones van como elementos en un vector. Seguimos el convenio de encerrar los símbolos no terminales entre '<' y '>', y dentro de una misma regla de producción separamos los diferentes tokens mediante espacios (si no lo hiciésemos así, tendríamos que recurrir a un analizador léxico, y eso lo complicaría todo). A continuación declaramos el símbolo inicial, y finalmente dentro de un bucle while() aplicamos las reglas de producción iterativamente a los símbolos no terminales hasta que todos los símbolos que queden en la cadena sean terminales.

En este caso hemos utilizado la función sample() para seleccionar aleatoriamente qué regla de producción tenemos que aplicar. En un entorno de producción real deberíamos reemplazar esta regla por algún método de búsqueda inteligente, tipo algoritmo genético o enjambre de partículas. Pero este es tema para una futura entrada del blog.

# Definición de la gramática
nt <- list(
    entero = c("<entero> <digito>", "<digito>"),
    digito = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
)

# Símbolo inicial
programa <- c("<entero>")

fin <- FALSE

while (!fin) {

    fin <- TRUE
    nprograma <- c()

    for(i in 1:length(programa)) {

        token <- programa[i]

        if( grepl("<.+>", token) ) {

            # Se trata de un símbolo no terminal, hay que parsearlo

            # Quitamos los marcadores < y >
            token <- substr(token, 2, nchar(token)-1)

            # Seleccionamos la regla adecuada
            regla <- nt[names(nt)==token][[1]]

            # Seleccionamos aleatoriamente una producción
            prod <- sample(regla, 1)

            # Parseamos la producción a aplicar y la añadimos
            nprograma <- c(nprograma, unlist(strsplit(prod, " ")))

            # Tenemos que hacer otra iteración
            fin <- FALSE

        } else {

            # Se trata de un símbolo terminal, lo añadimos tal cual
            nprograma <- c(nprograma, token)

        }

    }

    programa <- nprograma

}

# Programa final
sistema <- paste(programa, collapse="")

print(sistema)

jueves, 11 de abril de 2013

Evolución Gramatical, Sistemas de Trading y R (1/2)

-->
En una entrada reciente de este blog describí un sistema para la generación automática de sistemas de trading. Este sistema giraba en torno al concepto de “Evolución Gramatical”, optimizadores avanzados, y el lenguaje de análisis estadístico R. Degraciadamente R no dispone de ningún paquete específicamente diseñado para la optimización de sistemas basado en evoluciones gramaticales. Sin embargo, implementar los fundamentos de este sistema no es complicado. En esta serie de entradas vamos a revisar brevemente qué es eso de la evolución gramatical, cómo puede ser implementada en R, y qué tiene que ver con los sistemas de trading.

Las gramáticas son una herramienta muy conocida en el mundo de la ingeniería informática, y más concretamente, en el área de compiladores. Las gramáticas nos permiten definir de manera rigurosa los lenguajes de programación (como por ejemplo C++ o java), y por tanto, nos permite determinar si un determinado código fuente está correctamente escrito según el estándar del lenguaje. Además, las gramáticas también son útiles a la hora de compilar los programas, es decir, de transformar el código fuente de un programa en código máquina entendido por el ordenador.

Sin embargo, en el caso que nos ocupa, la generación de estrategias de trading, le vamos a dar la vuelta a la tortilla. Es decir, no utilizamos las gramáticas para decidir si una estrategia es (léxica y sintácticamente) correcta, sino para generar automáticamente el código de dicha estrategia. Podríamos, por ejemplo, definir una gramática que nos permita generar estrategias basadas en el cruce de medias móviles, y dejar que sea el propio ordenador el que “pruebe” distintas formas de combinar las medias móviles en una estrategia. Por ejemplo, el sistema que utilizamos en Entropycs generaría sistemas como los siguientes:

* SMA(simbolo,6)>SMA(simbolo,4+1) 
  | SMA(simbolo,92)>SMA(simbolo,90)

* SMA(simbolo,10)>SMA(simbolo,30)
  & SMA(simbolo,2)>SMA(simbolo,36)

* SMA(simbolo,116)>SMA(simbolo,99)

Evidentemente la mayoría de los sistemas así creados no tienen mucho sentido, pero si pensamos que el ordenador genera y evalúa varios centenares de sistemas por segundo, podemos hacernos una idea del potencial de este método.

Para describir las gramáticas se utiliza la denominada la notación Backus-Naur (BNF). Técnicamente la gramática se compone de un conjunto de símbolos terminales, un conjunto de símbolos no terminales, unas reglas de producción, y un símbolo inicial. Aunque suene muy complejo, la idea es muy sencilla. Veamos un ejemplo. Supongamos que queremos definir una gramática que genere números enteros de cualquier longitud. Un número se compone de uno o más dígitos, y los dígitos son los números de 0 a 9. Por tanto, la gramática sería

<número> := <número><dígito> | <dígito>
<dígito> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Los símbolos no terminales son <número> y <dígito>; los símbolos terminales sería de 0 al 9, y el símbolo inicial sería <número>. Las reglas de producción son las vistas más arriba y transforman un símbolo no terminal, a la izquierda del :=, por cualquiera de los símbolos (terminales o no) de la derecha, que vienen separados por '|'. Notese que en la generación de sistemas de trading seleccionaríamos al azar (o según nuestro algoritmo de búsqueda) el símbolo de la derecha a utilizar.

Por ejemplo, una posible evolución de la gramática sería:

Paso 1: <número>
Paso 2: <número><dígito>
Paso 3: <número><dígito>5
Paso 4: <dígito>35
Paso 5: 135

Y al ser todos los símbolos ya terminales, acabaría el proceso. Dejo propuesto al lector como ejercicio crear una gramática que sume dos números enteros.

¿Y cómo se hace esto en R? Pues eso es el tema del que tratará nuestra siguiente entrada de blog.

domingo, 7 de abril de 2013

Modificador de Relevancia Estadística

-->
Uno de los principales inconvenientes con los que nos encontramos a la hora de evaluar a priori la rentabilidad de un sistema de trading es el de la relevancia estadística de los resultados. De sobra es conocido que para que se pueda realizar un análisis estadístico sobre un conjunto de datos es necesario que dicho conjunto tenga un tamaño mínimo. Es difícil decir exactamente que se entiende por “tamaño mínimo”, pero parece que el valor más aceptado es el de 30 muestras (o el de 5 por celda en el caso de trabajar con tablas de contingencia). Si lo que estamos trabajando es con un sistema de seguimiento de tendencia basado en el cruce de dos medias móviles, y nuestro backtest se realiza sobre barras diarias en un periodo de 6 años, es posible que no lleguemos a ese número mínimo de 30 operaciones (entrada + salida), sobre todo si las medias móviles se calculan sobre periodos de tiempo muy largos.

En estos casos lo que se hace es evaluar a mano el número de operaciones realizadas, y si según nuestro criterio personal decidimos que es muy bajo, se descarta el análisis por no ser relevante. Pero si lo que estamos utilizando es una plataforma de optimización automática de sistemas, la cosa se complica un poco. Porque, ¿qué hacemos en este caso? ¿simplemente descartamos aquellas iteraciones que produzcan menos de 30 entradas? ¿no deberían ser tenidas en cuenta también aquellas con 29 entradas? ¿y 28? ¿y 27? ...

Para solucionar este problema propongo utilizar una especie de modificador de relevancia estadística, que pondere nuestro indicador de bondad de la estrategia según la relevancia estadística de los resultados. Este modificador debería poder ser aplicado indistintamente al indicador de bondad que habitualmente utilicemos, ya sea beneficio neto, drawdown máximo, ratio de Sharpe, SQN, etc. Desconozco si en la literatura estadística, o sobre trading cuantitativo, se ha propuesto alguna solución similar a la aquí planteada. Si es así, le rogaría a los lectores escriban un comentario a esta entrada de blog con la correspondiente referencia.

A priori, lo natural sería utilizar como modificador de relevancia alguna variación del error estándar utilizado en los propios análisis estadísticos. A saber, multiplicar nuestro indicador por 1-1/sqtr(n) (si lo que andamos buscando es maximizar el indicador). El problema de esta solución es que con muy pocas operaciones, ya se considera que el indicador es bastante confiable. Por ejemplo, con 5 operaciones ya tendríamos una ponderación del 55% sobre el indicador. Véase la siguiente figura:

Un modificador más adecuado podría estar basado en una tangente hiperbólica, o en la función logística. Yo me inclino más por la función logística, y en concreto, por la función 1/(1+exp((-n/5)+6)). El valor 6 viene del hecho de que la función logística sólo es interesante en el intervalo [-6,6], ya que para valores mayores de 6 es prácticamente 1, y para valores menores de 6 es prácticamente 0. El valor de 5 se escoge para centrar la función en el valor 30 (5*6) del que antes habíamos hablado. Así el indicador es progresivamente más relevante de 20 a 40 operaciones, para valores menores de 20 se degrada muy rápidamente, y para valores mayores de 40 el modificador apenas si tiene impacto. Justo lo que buscamos. Véase la figura:


Evidentemente, comentarios, opiniones, y modificadores alternativos, son bienvenidos.