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