R para Traders III

Seguimos con la serie sobre programación de sistemas de trading en R. En esta ocasión veremos cómo crear una estrategia algo más compleja con Quanstrat y realizar una optimización de algunos de sus parámetros.

Una Estrategia con Stop Loss y Trailing Stop
Retomando el ejemplo del artículo anterior vamos a complicarlo un poco creando una estrategia basada en bandas de Bollinger con stop de pérdidas y trailing stop incorporados. Para ello, usaremos los mismos valores del Ibex 35 (Inditex, Santander, Telefónica, BBVA e Iberdrola) en gráfico diario. Así pues, cargamos paquetes (si no sabéis cómo instalarlos, podéis verlo en el artículo anterior) y descargamos datos con el siguiente código:

require(quantstrat)
require(PerformanceAnalytics)
require(IKTrading)
require(lattice)

startDate <- '2010-01-01' 
endDate <- '2016-12-31'  
Sys.setenv(TZ="UTC")
symbols <- c("ITX.MC", "SAN.MC", "TEF.MC", "BBVA.MC", "IBE.MC")

getSymbols(symbols, from=startDate, to=endDate, index.class="POSIXct")


Declaramos capital inicial y divisa de la cuenta y los símbolos:

initDate <- '2010-01-01'
initEq <- 100000
currency("EUR")
stock(symbols, currency="EUR", multiplier=1)

Introducimos aquí una útil función, denominada osFixedDollar, con la que introducimos un algoritmo de gestión monetaria basado en Fixed Dollar con el que determinaremos el tamaño de las posiciones en función del capital disponible:

osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...)
{
  pos <- getPosQty(portfolio, symbol, timestamp)
  if( isTRUE(all.equal(pos,0)) )
  {
    ClosePrice <- as.numeric(Cl(mktdata[timestamp,]))
    orderqty <- sign(orderqty)*round(tradeSize/ClosePrice,-2)
  } else {
    orderqty <- 0
  }
  return(orderqty)
}

Lógicamente modificando un poco el código anterior podemos crear y utilizar nuestros propios algoritmos de gestión monetaria.


Inicializamos estrategia, portfolio y cuenta:

rm.strat("bb.stop")
initPortf(name="bb.stop", symbols, initDate=initDate, currency = "EUR")
initAcct(name="bb.stop", portfolios="bb.stop", initDate=initDate, initEq=initEq, currency = "EUR")
initOrders(portfolio="bb.stop", initDate=initDate)


A continuación declaramos estrategia e inicializamos las bandas de Bollinger y sus parámetros:

strategy("bbands2", store=TRUE)

add.indicator("bbands2", name = "BBands", arguments = list(HLC = quote(HLC(mktdata)), maType='SMA'), label='bbInd')

Añadimos ahora las señales, que serán las mismas que vimos en el artículo anterior. No obstante recordamos su funcionamiento: la primera de ellas nos indica que se produce un máximo (High) mayor que (gt) la banda superior de Bollinger (up), mientras que la segunda nos indica que se produce un cierre (Close) por debajo de (lt) la banda superior de Bollinger, tal que:

add.signal("bbands2", name="sigCrossover", arguments=list(columns=c("High","up"),relationship="gt"), label="H.gt.UpperBand")

add.signal("bbands2", name="sigCrossover", arguments=list(columns=c("Low","dn"),relationship="lt"), label="L.lt.LowerBand")

 

Ahora creamos las reglas de compra y venta que como podéis ver han variado ligeramente: por un lado, la primera regla abrirá una posición de compra de tamaño basado en osFixedDollar (aunque hay que especificar un valor en orderqty pero no afecta realmente) a mercado cada vez que se produzca un máximo por encima de la banda superior de Bollinger. Por su parte, la segunda regla nos indica que si se produce un cierre por debajo de la banda superior, se cerrarán todas las posiciones. Pero además hemos incorporado un stop de pérdidas del 3% y un trailing stop del 7%:

add.rule("bbands2", name='ruleSignal', arguments=list(sigcol="H.gt.UpperBand",sigval=TRUE, orderqty=+100, ordertype='market',    orderside='long', osFUN='osFixedDollar', orderset='ocolong'),type='enter', label='LongEntry')

add.rule("bbands2", name='ruleSignal', arguments=list(sigcol="L.lt.LowerBand",sigval=TRUE, orderqty= 'all', ordertype='market',  orderside='long', orderset='ocolong'), type='exit', label='LongExit')

stopLossPercent <- 0.03

add.rule("bbands2",name='ruleSignal', arguments = list(sigcol="H.gt.UpperBand", sigval=TRUE, replace=FALSE, orderside='long', ordertype='stoplimit', tmult=TRUE, threshold=quote( stopLossPercent ), orderqty='all', orderset='ocolong'), type='chain', parent="LongEntry", label='StopLossLong')

trailingStopPercent <- 0.07

add.rule("bbands2", name = 'ruleSignal', arguments=list(sigcol="H.gt.UpperBand" , sigval=TRUE, replace=FALSE, orderside='long', ordertype='stoptrailing', tmult=TRUE, threshold=quote(trailingStopPercent), orderqty='all', orderset='ocolong'), type='chain', parent="LongEntry", label='StopLossTrailing')

Con todo esto ya podemos proceder a ejecutar la estrategia:

enable.rule("bbands2",type="chain",label="StopLoss")
tradeSize <- 100000
out<-applyStrategy("bbands2" , portfolios="bb.stop", parameters=list(sd=2,n=20))

Hacemos un update del portfolio y vemos algunos resultados de las operaciones:

updatePortf("bb.stop")
updateAcct("bb.stop")
updateEndEq("bb.stop")

tStats <- tradeStats(Portfolios = "bb.stop", use="trades", inclZeroDays=FALSE)
tStats[,4:ncol(tStats)] <- round(tStats[,4:ncol(tStats)], 2)
print(data.frame(t(tStats[,-c(1,2)])))

Obtendremos los siguientes valores:

 

Optimización de Parámetros
¿Qué tal si optimizamos los valores del stop loss y el trailing stop? Con el siguiente código vamos a ver cómo hacerlo creando un heatmap de los parámetros optimizados.

Continuando con el ejemplo anterior, lo primero es definir los parámetros a optimizar y los rangos en que se van a mover usando la función add.distribution, tal que

stopLossPercentRange <- seq(0.01,0.10,by=0.01)

add.distribution("bbands2", paramset.label = "STOPOPT", component.type = "chain", component.label = "StopLossLong", variable = list( threshold = stopLossPercentRange ),  label = "StopLossLongDist")

trailingPercentRange <- seq(0.01,0.10,by=0.01)

add.distribution("bbands2", paramset.label = "STOPOPT", component.type = "chain", component.label = "StopLossTrailing", variable = list( threshold = trailingPercentRange ), label = "StopLossTrailingDist")

add.distribution.constraint("bbands2", paramset.label = 'STOPOPT', distribution.label.1 = 'StopLossLongDist', distribution.label.2 = 'StopLossTrailingDist', operator = '<', label = 'StopCon')


Con esta última línea de código (la que utiliza la función add.distribution.constraint) obligamos además a que los valores del stop loss sean menores que los del trailing stop.

Para realizar la optimización debemos crear una nueva estrategia para poder almacenar los resultados:

rm.strat("bb.opt")
initPortf(name="bb.opt", symbols, initDate=initDate, currency = "EUR")
initAcct(name="bb.opt", portfolios="bb.opt", initDate=initDate, initEq=initEq, currency = "EUR")
initOrders(portfolio="bb.opt", initDate=initDate)

Asimismo conviene instalar el paquete doParallel con el fin de acelerar la optimización de los parámetros y aprovechar toda la potencia de nuestro procesador:

install.packages("doParallel", repos="http://R-Forge.R-project.org")

library(parallel)
detectCores()

if( Sys.info()['sysname'] == "Windows" )
{
  library(doParallel)
  registerDoSEQ()
} else {
  library(doMC)
  registerDoMC(cores=detectCores())
}

Ahora simplemente basta con correr la estrategia pero esta vez usando la función apply.paramset en lugar de applyStrategy para iniciar la optimización de los parámetros:

results <- apply.paramset("bbands2", paramset.label = "STOPOPT", portfolio="bb.opt", account="bb.opt", nsamples=0)

El proceso de optimización tardará unos minutos y en pantalla podremos ir viendo los trades generados para cada activo con cada combinación de stop loss y trailing stop.

Si bien podemos ver los resultados de la optimización en texto sin más que teclear results, una visualización en forma de heatmap puede ser bastante útil, algo que podemos hacer usando el siguiente código:

z <- tapply(X=results$tradeStats$Profit.To.Max.Draw, INDEX=list(results$tradeStats$StopLossTrailingDist,results$tradeStats$StopLossLongDist), FUN=median)
x <- as.numeric(rownames(z))
y <- as.numeric(colnames(z))

filled.contour(x=x,y=y,z=z,color = heat.colors, xlab="Trailing Stop",ylab="Stop Loss")
title("Stop Loss vs Trailing Stop")

El resultado que obtendremos será el siguiente:

Como podemos ver, un desastre de optimización :D (ni una sola combinación nos da beneficios)

 

En la próxima entrega finalizaremos la serie sobre R examinando algunos trucos útiles en R para hacer análisis estadísticos de datos financieros.

 


Saludos,
X-Trader


Artículos Relacionados


MQL4 para Novatos IV

MQL4 para Novatos IV

Continuamos con la serie sobre el lenguaje MQL. En esta ocasión se abordan las principales funciones de indicadores técnicos que trae incorporadas de serie.

Reto para Traders

Reto para Traders

La iniciativa BBVA Open Challenges trata de abrir los retos actuales de la banca y las finanzas a todo el ecosistema emprendedor. Dentro de esta iniciativa este año tenemos una interesante novedad:...

Lenguajes de Programación para HFT

Lenguajes de Programación para HFT

Mucho se ha hablado y especulado sobre el High Frequency Trading pero muy pocos conocen realmente en qué lenguajes se programan los algoritmos utilizados en este tipo de trading. Como veremos en...

MQL4 para Novatos I

MQL4 para Novatos I

Con este artículo, iniciamos una serie dedicada a aquellos traders que, aún no sabiendo nada de programación, desean aprender a programar en MQL4, el lenguaje de desarrollo implementado en...

Filtro de Kalman Aplicado al Trading de Pares

Filtro de Kalman Aplicado al Trading de Pares

En la última kedada presenté una aplicación del filtro de Kalman al trading de pares. Para los que no pudisteis asistir, aquí tenéis un resumen de mi ponencia junto con el código en R.

R para Traders I

R para Traders I

En el trading cada vez se requiere el uso de herramientas más sofisticadas y potentes para el análisis de datos. Es aquí donde entra R, un entorno de programación GNU que nos facilitará enormemente...