miércoles, 27 de febrero de 2013

Trabajar con Datos de Forex con R y SQLite (2/5)


En la primera entrada de esta serie vimos como crear una tabla para almacenar los datos históricos del cambio euro-dólar. En esta ocasión vamos a empezar a trabajar con estos datos.

Trabajar con los datos

Lo primero que vamos a hacer es darle algo de formato a la salida que nos proporciona el intérprete de sqlite para que sea algo más legible, para ello escribimos:

.echo on
.mode column
.headers on
.nullvalue NULL

Básicamente, lo que le hemos dicho a SQLite es que queremos ver los datos formateados por columnas, y con una cabecera de tabla.

Para mostrar todos los elementos contenidos en la base de datos (que recomiendo no hacer porque llevaría un rato visualizarlos, ya que son muchos) se haría:

SELECT * FROM eurusd;

Para contar el número de barras almacenadas en la base de datos escribimos:

SELECT count(*) FROM eurusd;

que por cierto, debe coincidir con el número de líneas del fichero CSV desde el que hemos importado los datos, y que se pueden contar con la orden Unix:

wc -l EURUSD.csv

Para insertar una nueva barra se haría con:

INSERT INTO eurusd (date, unix, open, high, low, close) VALUES (
'2011-11-01 00:01:00', 0, '1.1212', '1.2323', '1.3434', '1.4545'
);

Que a su vez se puede borrar con:

DELETE FROM eurusd WHERE date = '2011-11-01 00:01:00';

Finalmente, podemos consultar cualquier barra con una orden como:

SELECT * FROM eurusd WHERE date = "2001-01-05 02:26";

Consultas avanzadas

La verdad es que las consultas que hemos hecho hasta ahora no son especialmente útiles. Pero en esta sección vamos a empezar a ver la potencia de almacenar los datos Forex en una base de datos relacional.

Por ejemplo si queremos ver el máximo y el mínimo alcanzado en cada mes escribimos:

SELECT MAX(high) AS High, MIN(low) as Low, 
  STRFTIME("%Y-%m", date) as Month 
  FROM eurusd
  WHERE date > '2010-01-01' AND date < '2011-01-01'
  GROUP BY STRFTIME("%Y-%m", date);

Si queremos ver el máximo y mínimo alcanzado cada día:

SELECT MAX(high) AS High, MIN(low) as Low,
  STRFTIME("%Y-%m-%d", date) as Date
  FROM eurusd
  WHERE date > '2010-01-01' AND date < '2011-01-01'
  GROUP BY STRFTIME("%Y-%m-%d", date);

Si queremos ver el máximo y mínimo alcanzado durante un periodo cualquiera de tiempo, por ejemplo 40 minutos, hacemos:

SELECT high, low FROM eurusd WHERE date >= "2001-01-05 02:00" AND date <= "2001-01-05 02:40";

Y finalmente, si queremos ver el precio de apertura en barras diarias, haríamos:

SELECT open, date FROM eurusd WHERE date IN
  (SELECT MIN(date)
    FROM eurusd
    WHERE date > '2010-01-01' AND date < '2011-01-01'
    GROUP BY STRFTIME("%Y-%m-%d", date));

Y esto es todo para esta entrega. En la siguiente entrada de este blog veremos cómo realizar algunas consultas más avanzadas.

viernes, 22 de febrero de 2013

Trabajar con Datos de Forex con R y SQLite (1/5)


El entorno R es extremadamente rápido analizando datos siempre y cuando estos quepan en la memoria de nuestro ordenador, pero si esto no es así, el rendimiento de R se puede degradar hasta niveles inaceptables. Existen múltiples soluciones a este problema (que en algún momento analizaremos en este blog), pero para el caso del análisis de datos Forex la que a mi particularmente más me convence es utilizar una base de datos externa, como por ejemplo SQLite, en la que almacenamos la totalidad de los datos, y a continuación, desde R, los vamos consultando según los vayamos necesitando para nuestro análisis.

Iniciamos aquí una serie de entregas en la que vamos a aprender cómo se pueden analizar grandes volúmenes de datos desde R con la ayuda de SQLite. Esto nos será de gran ayuda a la hora de analizar y optimizar estrategias de trading.

SQLite es un sistema gestor de bases de datos relacionales muy popular y ampliamente utilizado (de hecho, Bloomberg es uno de sus patrocinadores). SQLite es una librería que se distribuye de manera gratuita, y cuyo código fuente está disponible para su integración en otros programas (y que evidentemente, está integrada en la plataforma Entropycs). SQLite no necesita de ningún tipo de configuración o de mantenimiento, y actualmente está siendo utilizada para gestionar bases de datos de varios gigabytes (suficiente para trabajar con barras de 1 minuto, aunque quizás se quede algo corta para trabajar con datos tick a tick).

Creando una Tabla de Datos

Lo primero que vamos a hacer es crear una tabla que permita almacenar los datos históricos, en barras de un minuto, para el símbolo EURUSD. Esta tabla almacena la fecha y hora de cada barra en formato texto (campo date), y los valores que se han dado en esa barra (apertura, máximo, mínimo y cierre) como números con decimales. También contiene el capo unix, que es la misma fecha que contiene el campo date, pero en formato tiempo de unix (que cuenta el número de segundos que han transcurrido desde el 1 de Enero de 1970). El campo unix, desde un punto de vista formal, es redundante y debería ser eliminado. Sin embargo, y como veremos más adelante, nos va a venir muy bien para mejorar el rendimiento de nuestras consultas.

Utilizando el editor de comandos de sqlite creamos un fichero de datos para el símbolo EURUSD:

sqlite3 eurusd.db

y a continuación creamos la tabla con:

CREATE TABLE eurusd (
    date TEXT NOT NULL PRIMARY KEY,
    unix INTEGER NOT NULL,
    open REAL NOT NULL,
    high REAL NOT NULL,
    low REAL NOT NULL,
    close REAL NOT NULL
);

Nuevamente, desde un punto de vista formal hubiera sido más correcto crear una única tabla para almacenar los datos de todos los símbolos, pero por cuestiones de rendimiento, hemos optado por crear un único fichero que contiene una única tabla para cada uno de los símbolos.

Importar un fichero de datos

Para los datos históricos vamos a utilizar los datos proporcionados por Forex Tester. Estos datos tienen una calidad más que suficiente para nuestros análisis. De hecho, tienen una calidad mucho mayor que los datos proporcionados por la mayoría de los brokers ;-)

Si abrimos el fichero (una vez descomprimido) vemos que el formato en el que vienen los datos de las barras es el siguiente:

<TICKER>,<DTYYYYMMDD>,<TIME>,<OPEN>,<HIGH>,<LOW>,<CLOSE>,<VOL>
EURUSD,20010102,230100,0.9507,0.9507,0.9507,0.9507,4
EURUSD,20010102,230200,0.9506,0.9506,0.9505,0.9505,4
EURUSD,20010102,230300,0.9505,0.9507,0.9505,0.9506,4
...

Este formato no es el que SQLite espera, por lo que hay que hacer un pre-procesamiento del mismo. Aquí nos encontramos con el problema de que necesitamos un editor de textos que sea lo suficientemente potente como para poder cargar este fichero de datos en memoria, y que sea lo suficientemente versátil para permitir hacer cambios complejos en el mismo. En el mundo Linux tenemos el editor vi que cumple ambas funciones (existe una versión para Windows de este editor de textos llamada Vim). Alternativamente, aquellos que tengan conocimientos de programación pueden escribir un pequeño script en Perl, Python, TCL o similar, que haga la tarea.

Así que editamos el fichero con vi, borramos la primera línea y escribimos el siguiente galimatías:

:1,$s/\w\{6},\(\d\{4}\)\(\d\{2}\)\(\d\{2}\),\(\d\{2}\)\(\d\{2}\)\(\d\{2}\),\(.*\),\d$/\1-\2-\3 \4:\5,0,\7/

Grabamos el resultado como EURUSD.cvs, y comprobamos que el fichero tiene ahora el formato deseado (el indicado por la tabla que hemos creado):

2001-01-02 23:01,0,0.9507,0.9507,0.9507,0.9507
2001-01-02 23:02,0,0.9506,0.9506,0.9505,0.9505
2001-01-02 23:03,0,0.9505,0.9507,0.9505,0.9506

A continuación abrimos SQLite y escribimos:

.mode csv
.import EURUSD.csv eurusd

Esto nos importa la totalidad de los datos de las barras en nuestra tabla. Tan sólo nos quedaría corregir el campo de tiempo en formato unix, y para ello escribimos:

UPDATE eurusd SET unix = CAST(STRFTIME('%s', date) AS INTEGER);

Y con esto ya tendríamos completamente cargados los datos en la base de datos.

miércoles, 20 de febrero de 2013

Idea de Sistema: Seguimiento de la Tendencia

Recientemente asistí a unas charlas introductorias sobre trading organizadas por un conocido broker americano. Durante el evento, varios “especialistas” en trading nos daban su opinión sobre cómo operar con éxito. En concreto, uno de estos especialistas nos decía que hacer trading es muy fácil, tan sólo tenemos que ver la evolución de un símbolo, si está alcista nos ponemos en largo, y si está bajista nos ponemos en corto. Fácil, ¿no? El problema es cómo determinar cuando un símbolo está alcista, y cuando ha dejado de estarlo. Pero esto es algo que no nos contaron.


Al hilo de aquello abrí mi consola R y programé un estrategia simple, a ver que pasaba (nota: a partir de ahora me referiré únicamente al mercado Forex, aunque ideas similares podría aplicarse a otros mercados). Si el close de una barra diaria es mayor que el open, podemos decir que el día ha sido alcista. Y si al día siguiente vuelve a suceder lo mismo, ¿podemos decir que estamos ante una tendencia alcista? ¿O son necesarios tres, cuatro, o cinco días consecutivos alcistas? Vamos a ver que dice R.

Primero nos descargamos la información del cambio EURUSD de los últimos 10 años en barras diarias, tal y como hemos explicado en entradas anteriores de este blog. A continuación calculamos un vector de tendencias, que contendrá el valor 1 si el día ha sido alcista, y 0 si ha sido bajista:

tendencia <- as.integer(eurusd$Close > eurusd$Open)

Vamos a escribir una función llamada predicetendencias que recibe como argumento la tendencia diaria de un símbolo, y el número de días consecutivos que se tienen que dar para que una tendencia sea alcista o bajista, y devuelva cómo de efectiva es esta predicción (por efectiva entendemos la tasa de error en las predicciones):

predicetendencias <- function(x, k) {

    n <- length(x)
    pred <- rep(NA, n)

    for (i in (k+1):n) {
        if (all(x[(i-k):(i-1)] == 1)) pred[i] <- 1 else
            if (all(x[(i-k):(i-1)] == 0)) pred[i] <- 0
                else pred[i] <- NA
    }

    return(mean(abs(pred-x),na.rm=T))

}

Finalmente, evaluamos la estrategia para grupos desde 1 hasta 10 días consecutivos:

resul <- NULL
for( i in 1:10) {
     resul <- c(resul, predicetendencias(tendencia, i))
}

Visualizamos los resultados

plot(c(1:10), resul, type="lines")


Y vemos que la capacidad de predicción de este sistema es prácticamente nula (recuérdese que estamos hablando de tasa de error, por lo que a valores mayores, peor resultado).

¿Y si ... lo vemos al revés? Es decir, podríamos decir que si ha habido muchos días consecutivos en la misma tendencia, es posible que estemos al final de la misma, y que se deba producir un cambio de tendencia. En este caso tenemos que:




Bueno, parece que la capacidad de predicción ha mejorado algo. Nótese que en el caso de tendencias de 10 días consecutivos no se trata de que la predicción del sistema sea perfecta, sino más bien que tenemos pocas de esas tendencias (en concreto, sólo hay 3), y que hemos tenido mucha suerte en la predicción. Evidentemente, para poder poner este sistema en producción habría que trabajarlo mucho más, y hacer un análisis más exhaustivo. Pero es un comienzo.

Como ejercicio le dejo al lector propuesto el sistema Lluvia (le llamo lluvia porque en el libro en el que leí la idea hablaba de un sistema pare predecir si un determinado día llovería o no). El sistema lluvia se basa en contar el número de días alcistas entre los k días pasados, y si supera a k/2 esperamos (con probabilidad mayor a 0.5) otro día alcista, y si no, lo esperamos bajista. El código correspondiente es:

lluvia <- function(x, k) {

    n <- length(x)
    k2 <- k/2
    pred <- rep(NA, n)

    for (i in 1:(n-k)) {
        if (sum(x[i:(i+(k-1))]) >= k2) pred[i+k] <- 1 else pred[i+k] <- 0
    }

    return(mean(abs(pred-x),na.rm=T))

}

Habría que jugar con el parámetro k y quizás aumentando la proporción k/2. Ya me contaréis que tal.

Nota: Por petición popular, a partir de hoy incrementaré la frecuencia de las entradas de este blog, pasando de una semanal a dos (lunes y miércoles). Aprovecho la ocasión para agradeceros a todos el ánimo recibido para continuar con este proyecto de formación sobre R y trading.

miércoles, 13 de febrero de 2013

Optimización Lineal de un Sistema con R


Una de las tareas más importantes en el desarrollo de un sistema de trading de éxito es la optimización del mismo. Una vez desarrollado un sistema de trading, y elegido el símbolo donde lo vamos a utilizar, la optimización consiste en encontrar aquellos parámetros que maximicen el rendimiento del sistema. Para ello lo que se hace es probar el sistema con diferentes combinaciones de parámetros, sobre un conjunto de datos históricos.

Optimizar un sistema es una tarea muy complicada, que requiere tener en cuenta multitud de factores para no caer en una sobre-optimización (una sobre-optimización se da cuando un sistema ha sido optimizado inadecuadamente, lo que le lleva a describir casi a la perfección el pasado, pero tiene una capacidad nula para predecir el futuro). Además, por otro lado, existen decenas de técnicas de optimización, cada una con sus ventajas e inconvenientes.

Aunque de todos estos temas tendremos tiempo de hablar en este blog, en esta ocasión vamos a empezar por lo más básico: como optimizar un sencillo sistema de trading mediante una optimización lineal. Para ello, vamos a utilizar un sistema basado en el cruce de dos medias móviles, del que ya hemos hablado en este blog.

Lo primero que vamos a hacer es empaquetar nuestro sistema en una función, de tal manera que pueda ser invocado con distintos valores de medias móviles, que es una condición necesaria para poder hacer la optimización. La función se llamará CDMM (de Cruce de Dos Medias Móviles), y recibirá tres parámetros: “símbolo” conteniendo el histórico del símbolo sobre el que queremos realizar la optimización, “mmc” que contiene el valor actual de la media móvil corta, y “mml” con el valor de la media móvil larga:

CDMM <- function(simbolo, mmc, mml) {

# Calculamos las medias móviles
vmmc <- SMA(simbolo, mmc)
vmml <- SMA(simbolo, mml)

# Calculamos la señal (cruce de dos medias móviles)
sen <- ifelse(vmmc >= vmml, 1, -1)

# Calcula el retorno
ret <- ROC(simbolo)*sen

return(ret)

}

A continuación descargamos los valores del símbolo como hacemos habitualmente, y definimos una tabla (data frame en la jerga de R) que contendrá tes columnas: “corta” para los valores sobre los que queremos iterar para la media móvil corta (5, 7, 9, ..., 43), “larga” igual para la media móvil larga (60, 65, 70, ..., 155), y “retorno” que contendrá el retorno relativo para cada combinación de media móvil corta y larga (para entender mejor cómo se construye el data frame recomiendo al lector que ejecute paso a paso las sentencias anteriores e imprima los resultados):

require(quantmod)
require(PerformanceAnalytics)
getSymbols(“EUR/USD”, src=”oanda”)

corta <- rep(seq(from=5, to=43, by=2), 20)
larga <- rep(seq(from=60, to=155, by=5), each=20)
retorno <- rep(NA, 400)
result <- data.frame(corta, larga, retorno)

Finalmente ejecutamos un bucle que nos compruebe el retorno obtenido por cada combinación de medias móviles (siguiendo la paradigma funcional de R debería haber utilizado una función tipo sapply() en vez de un bucle, pero por claridad he optado por el bucle):

for( i in 1:nrow(result)) {

row <- result[i,]
ret <- CDMM(EURUSD, row$corta, row$larga)
ret_cum <- Return.cumulative(ret)

result[i,]$retorno <- ret_cum

print(row$corta)
print(row$larga)
print(ret_cum)

}

Para ver los resultados podemos utilizar cualquiera de las funciones gráficas de R, por ejemplo:

persp(seq(from=5, to=43, by=2), seq(from=60, to=155, by=5), matrix(result$retorno, nrow=20), theta = 135, phi = 30, col = "green3", ltheta = -120, shade = 0.75)

Que nos mostraría el siguiente gráfico:
Nota: Me gustaría comentaros que hemos creado un grupo en LinkedIn para todos aquellos que tengáis interés en el análisis cuantitativo de estrategias de trading con R. Estáis invitados a uniros.

miércoles, 6 de febrero de 2013

Ejemplo de Sistema de Trading con R


En esta ocasión vamos a ver un ejemplo concreto de sistema de trading en R, cómo se programa, y cómo se evalúa su rendimiento. El sistema que vamos a desarrollar es muy simple. Se basa en el cruce de dos medias móviles simples: una media móvil corta y una media móvil larga. Cuando la media móvil corta está por encima de la media móvil larga nos ponemos en largo, y cuando está por debajo, nos ponemos en corto.

Aquellas personas que hayan programado en otros lenguajes de trading, tales como mql4, encontrarán la forma de trabajar en R un tanto extraña. En la mayoría de los lenguajes para el desarrollo de estrategias de trading, lo que tenemos es un entorno (como por ejemplo MetaTrader 4 para mql4) que invoca nuestro sistema cada vez que hay un tick nuevo, o cada vez que se cierra una barra. Cuando esto ocurre, nuestro código lo que suele hacer es calcular los correspondiente indicadores técnicos, y decidir qué posiciones se cierran y qué posiciones nuevas se abren. Y es el propio entorno que rodea al lenguaje el que lleva la cuenta de las pérdidas y ganancias, así como del balance. En R es distinto. En R lo que tenemos son series temporales, y vectores numéricos, y lo que hacemos es manipularlos, y proceder al análisis de los resultados. Veamos cómo.

Lo primero que tenemos que hacer es importar aquellos paquetes R que necesitamos: quanmod para descargar los datos que necesitamos, TTR para calcular los indicadores técnicos, y PerformaceAnalytics para evaluar los resultados.

> require(TTR)
> require(quantmod)
> require(PerformanceAnalytics)

A continuación vamos a descargar los datos del cambio EUR/USD desde el servidor de Oanda. La función getSymbols del paquete quantmod descarga las últimas barras diarias que existen en el servidor (aproximadamente unas 500). En el caso de hacer análisis de estrategias reales para su uso en producción tendríamos que buscar una fuente de datos más completa, por ejemplo la que nos proporciona nuestro broker en barras de 1 minuto, e importarlas en R. De cómo hacer esto hablaremos en entradas posteriores de este blog.

> getSymbols(“EUR/USD”, src=”oanda”)

La función getSymbols nos ha creado automáticamente la serie temporal EURUSD, que podemos visualizarla gráficamente como explicamos cuando hablamos del paquete quantmod.

Para el cálculo de las medias móviles simples utilizamos la función SMA() del paquete TTR:

> mmc <- SMA(EURUSD, 5)
> mml <- SMA(EURUSD, 60)

Ahora necesitamos comparar ambas medias móviles para saber cuando la media móvil corta está por encima o por debajo de la media móvil larga, para ello nos creamos un nuevo vector que tendrá el valor 1 cuando esté por encima, y el valor -1 cuando esté por debajo:

> sen <- ifelse(mmc >= mml, 1, -1)

La función ROC (rate of change) nos indica la tasa de variación de una serie temporal, en nuestro caso lo que nos indicaría es si una barra ha cerrado por encima o por debajo de la barra anterior, en valores relativos. Vamos a calcular un vector con todas las tasas de cambio:

> ch <- ROC(EURUSD)

Y ahora viene lo interesante: si estamos en largo (valor 1 en el vector sen) y la tasa de cambio es positiva, ganamos dinero, pero si es negativa, perdemos. De igual manera, si estamos en corto (valor -1) y la tasa de cambio es positiva, perdemos dinero, pero si es negativa, ganamos. Es decir, para saber la tasa de beneficio que obtendremos con nuestro sistema lo que hay que hacer es multiplicar el vector de señales con el vector de tasa de cambio:

> ret <- ch * sen

Y ya tenemos todo lo que necesitamos. Vamos a ver qué tal ha funcionado nuestro sistema (los datos corresponden a una media móvil corta de 5, y una media móvil larga de 60). Primero vemos el retorno total acumulado con:

> Return.cumulative(ret)
EUR.USD
Cumulative Return 0.05120814

y cual es el máximo drawdown de nuestro sistema:

> maxDrawdown(ret)
[1] 0.116249

también podemos ver algunos indicadores más avanzados, como el ratio de Sharpe:

> SharpeRatio(ret)
EUR.USD
StdDev Sharpe: (Rf=0%, p=95%) 0.02546219
VaR Sharpe: (Rf=0%, p=95%) 0.01571873
ES Sharpe: (Rf=0%, p=95%) 0.01165527

Y para finalizar, vamos a ver gráficamente cómo se ha comportado el sistema con:

> charts.PerformanceSummary(ret)


Este gráfico se compone de tres partes. En la parte superior tenemos el retorno relativo acumulado. En el gráfico medio tenemos el retorno relativo desglosado por días. Y en el gráfico inferior tenemos el máximo drawdown que se ha producido.