Francisco Guijarro
Universidad Politécnica de Valencia
Creative Commons Attribution 4.0 International License (CC BY 4.0)
En esta tema introduciremos algunos elementos adicionales de programación, como las estructuras condicionales y las estructuras iterativas, que nos permitirán programar scripts de mayor complejidad que los vistos hasta ahora.
Veremos cómo el uso de este tipo de estructuras permiten modelizar situaciones más complejas, así como poder ejecutar de forma repetida una serie de comandos haciendo más eficiente el código.
También se presentarán las funciones, que permiten encapsular un bloque de código bajo una nombre determinado. Las funciones será útiles cuando nuestros scripts necesiten repetir bloques de códigos con pequeñas modificaciones, evitando tener que repetir partes del código a lo largo del script.
Hasta ahora hemos ejecutado diferentes comandos en forma secuencial. Cada una de las funciones se han ejecutado en un orden determinado. Sin embargo, todos los lenguajes de programación tienen la opción de ejecutar comandos en base a condiciones, o repetir un mismo bloque de código varias veces mediante un proceso iterativo.
Una estructura condicional es una pieza de código que permite ejecutar uno o varios comandos siempre que se cumpla una condición.
Por ejemplo, supongamos que tenemos la siguiente pieza de código:
dia <- "martes"
if(dia == "martes") {
print("Hoy es martes")
}
## [1] "Hoy es martes"
El comando if comprueba si se cumple la condición dia == "martes". En caso afirmativo, imprime en pantalla el texto Hoy es martes.
Supongamos que el valor de la variable dia fuera otro:
dia <- "miércoles"
if(dia == "martes") {
print("Hoy es martes")
}
if(dia != "martes") {
print("Hoy no es martes")
}
## [1] "Hoy no es martes"
La anterior secuencia se podría haber escrito a través de la estructura if ... else:
dia <- "miércoles"
if(dia == "martes") {
print("Hoy es martes")
} else {
print("Hoy no es martes")
}
## [1] "Hoy no es martes"
Una estructura iterativa permite ejecutar uno o más comandos de forma repetitiva siempre que se siga cumpliendo una condición.
Supongamos que tenemos una variable semana que recoje los días de la semana ordenados de lunes a domingo, y queremos recorrer su estructura hasta llegar al sábado. Una opción poco recomendable sería:
semana <- c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")
if(semana[1] == "sábado") {
print("¡Por fin es sábado! El elemento 1 del vector semana")
} else {
if(semana[2] == "sábado") {
print("¡Por fin es sábado! El elemento 2 del vector semana")
} else {
if(semana[3] == "sábado") {
print("¡Por fin es sábado! El elemento 3 del vector semana")
} else {
if (semana[4] == "sábado") {
print("¡Por fin es sábado! El elemento 4 del vector semana")
} else {
if (semana[5] == "sábado") {
print("¡Por fin es sábado! El elemento 5 del vector semana")
} else {
if (semana[6] == "sábado") {
print("¡Por fin es sábado! El elemento 6 del vector semana")
} else {
print("¡Por fin es sábado! El elemento 7 del vector semana")
}
}
}
}
}
}
## [1] "¡Por fin es sábado! El elemento 6 del vector semana"
En lugar de utilizar un código tan largo como el anterior, podemos hacer uso del comando while que ejecuta un bloque de código mientras se cumpla la condición que pasamos como parámetro al propio while:
contador <- 1
while(semana[contador] != "sábado") {
contador <- contador + 1
}
paste0("¡Por fin es sábado! El elemento ", contador, " del vector semana")
## [1] "¡Por fin es sábado! El elemento 6 del vector semana"
Otra forma de expresar un bucle es a través del comando for. En este caso, los comandos se repiten de forma iterativa a través de un contador:
for(contador in 1:7) {
print(paste("Hoy es", semana[contador]))
}
## [1] "Hoy es lunes"
## [1] "Hoy es martes"
## [1] "Hoy es miércoles"
## [1] "Hoy es jueves"
## [1] "Hoy es viernes"
## [1] "Hoy es sábado"
## [1] "Hoy es domingo"
Esto último también se podría haber obtenido de otra forma alternativa:
for(dia in semana) {
print(paste("Hoy es", dia))
}
## [1] "Hoy es lunes"
## [1] "Hoy es martes"
## [1] "Hoy es miércoles"
## [1] "Hoy es jueves"
## [1] "Hoy es viernes"
## [1] "Hoy es sábado"
## [1] "Hoy es domingo"
Ejercicio
1Calcula el factorial de 10 utilizando a) un bucle
for, y b) un buclewhile. Puedes comprobar si el resultado es correcto con la funciónfactorial(10).
factorial <- 1
for (i in 2:10) {
factorial <- factorial * i
}
paste("El factorial de 10 es", factorial)
## [1] "El factorial de 10 es 3628800"
factorial <- 1
i <- 2
while (i <= 10) {
factorial <- factorial * i
i <- i+1
}
paste("El factorial de 10 es", factorial)
## [1] "El factorial de 10 es 3628800"
factorial(10)
## [1] 3628800
Ejercicio
2Calcula los números primos que existen entre 2 y 100; es decir, cuáles son sólo divisibles por 1 y por sí mismos. Puedes utilizar la función
%%para calcular el resto de una división. Por ejemplo,10 %% 2da como resultado0; pero10 %% 3da como resultado1. Deberías obtener algo similar a esto:
## [1] "El número 2 es primo"
## [1] "El número 3 es primo"
## [1] "El número 5 es primo"
## [1] "El número 7 es primo"
## [1] "El número 11 es primo"
## [1] "El número 13 es primo"
## [1] "El número 17 es primo"
## [1] "El número 19 es primo"
## [1] "El número 23 es primo"
## [1] "El número 29 es primo"
## [1] "El número 31 es primo"
## [1] "El número 37 es primo"
## [1] "El número 41 es primo"
## [1] "El número 43 es primo"
## [1] "El número 47 es primo"
## [1] "El número 53 es primo"
## [1] "El número 59 es primo"
## [1] "El número 61 es primo"
## [1] "El número 67 es primo"
## [1] "El número 71 es primo"
## [1] "El número 73 es primo"
## [1] "El número 79 es primo"
## [1] "El número 83 es primo"
## [1] "El número 89 es primo"
## [1] "El número 97 es primo"
Una función permite encapsular un bloque de código, dándole una denominación determinada. Esto permite que podamos ejecutar ese bloque de código cada vez que llamemos a la función.
Por ejemplo, supongamos que queremos crear una función que nos informe sobre la fecha actual:
dime_fecha_actual <- function() {
fecha <- Sys.Date()
print(fecha)
}
dime_fecha_actual()
## [1] "2020-10-18"
Observa la diferencia entre dime_fecha_actual() y dime_fecha_actual:
dime_fecha_actual
## function() {
## fecha <- Sys.Date()
## print(fecha)
## }
Esto es, para que la función ejecute su bloque de código, al nombre de la función debe seguirle los paréntesis (). Habitualmente entre paréntesis pasaremos los parámetros de la función, aunque en el caso anterior lo hemos dejado en blanco (sin parámetros).
Pongamos ahora el caso de una función a la que pasamos un único parámetro (más adelante veremos que podemos pasar tantos parámetros como queramos, separándolos mediante una coma):
imprime_nombre <- function(nombre) {
print(nombre)
}
imprime_nombre("Jaime")
## [1] "Jaime"
imprime_nombre("Valentina")
## [1] "Valentina"
Precisamente la opción de pasar uno a más parámetros a las funciones va a facilitar la codificación de muchos scripts.
Ejercicio
3Escribe una función que calcule el factorial de un número. Posteriormente, llama a la función para que calcule el factorial de 10.
factorial <- function(numero) {
resultado <- 1
for (i in 2:numero) {
resultado <- resultado * i
}
return(resultado)
}
factorial(10)
## [1] 3628800
A diferencia de imprime_nombre, la función factorial no imprime nada, sino que devuelve un valor: el resultado del factorial. Esto lo hacemos a través del comando return. Veremos que igual que podemos pasar más de un parámetro a una función, también podemos devolver más de una variable con una función.
Ejercicio
4Obtener el factorial de 1 con la función anterior. Corrije la función para que devuelva un valor correcto.
factorial(1)
## [1] 2
Vemos como la función anterior calcula incorrectamente el factorial de 1. De ahí que se precise su modificación.
El resultado de la nueva función debería arrojar valores como los siguientes:
factorial(10)
## [1] 3628800
factorial(1)
## [1] 1
factorial(0)
## [1] NA
factorial(-5)
## [1] NA
En este apartado descargaremos las cotizaciones bursátiles de diferentes títulos, los representaremos gráficamente, e implementaremos una sencilla (y poco rentable) estrategia de inversión.
Esto nos ayudará a ejercitar los nuevos comandos aprendidos en el tema, así como familiarizarnos con el tratamiento de datos y terminología bursátiles.
Respecto de la terminología, conviene saber el significado de los siguientes términos:
Ticker bursátil de un título.
Precios OHLC.
Precios ajustados.
Posiciones largas y posiciones cortas.
Tendencia.
Indicadores técnicos.
Rentabilidad absoluta y rentabilidad relativa.
Riesgo, Drawdown, Máximo drawdown.
En primer lugar vamos a descargar las cotizaciones diarias de Apple, cuyo ticker bursátil es AAPL:
library(quantmod)
getSymbols("AAPL", from = '2010-01-01', to = "2020-10-15")
## [1] "AAPL"
head(AAPL)
## AAPL.Open AAPL.High AAPL.Low AAPL.Close AAPL.Volume AAPL.Adjusted
## 2010-01-04 7.622500 7.660714 7.585000 7.643214 493729600 6.604801
## 2010-01-05 7.664286 7.699643 7.616071 7.656428 601904800 6.616219
## 2010-01-06 7.656428 7.686786 7.526786 7.534643 552160000 6.510980
## 2010-01-07 7.562500 7.571429 7.466072 7.520714 477131200 6.498945
## 2010-01-08 7.510714 7.571429 7.466429 7.570714 447610800 6.542150
## 2010-01-11 7.600000 7.607143 7.444643 7.503929 462229600 6.484439
La información de los títulos viene en formato OHLC (Open, High, Low, Close).
Podemos representar gráficamente toda la serie o solo un subconjunto de la misma:
chart_Series(AAPL)
chart_Series(AAPL["2020-01/2020-10"])
También podemos realizar diferentes representaciones de las cotizaciones:
chartSeries(AAPL, type = "line", subset = "2020-09/2020-10",
theme = chartTheme("white"), TA = NULL)
chartSeries(AAPL, type = "bars", subset = "2020-09/2020-10",
theme = chartTheme("white"), TA = NULL)
chartSeries(AAPL, type = "candlesticks", subset = "2020-09/2020-10",
theme = chartTheme("white"), TA = NULL)
A continuación vamos a plantear una sencilla estrategia de inversión.
Tendremos una señal de compra (buy) cuando durante dos sesiones consecutivas el precio de cierre sea superior al precio de cierre del día anterior.
Tendremos una señal de venta (sell) cuando durante dos sesiones consecutivas el precio de cierre sea inferior al precio de cierre del día anterior.
Una señal de entrada al mercado (compra o venta) sólo se cancelará cuando tengamos una señal en dirección contraria.
Sólo podemos tener una posición abierta: o estamos comprados (buy), o estamos vendidos (sell). Pero no podemos tener dos o más posiciones abiertas, en la misma o distinta dirección.
Para poder visualizar en detalle la estrategia vamos a trabajar únicamente con los datos de 2020.
Los siguientes pasos resumen la operativa que vamos a llevar a cabo:
library(dplyr)
datos <- AAPL["2020"]
datos_estrategia <- datos %>%
as.data.frame() %>%
select(AAPL.Adjusted) %>%
mutate(variacion = AAPL.Adjusted - lag(AAPL.Adjusted))
datos_estrategia[1:12, ]
## AAPL.Adjusted variacion
## 1 74.57304 NA
## 2 73.84803 -0.725006
## 3 74.43647 0.588440
## 4 74.08639 -0.350075
## 5 75.27816 1.191765
## 6 76.87714 1.598976
## 7 77.05093 0.173790
## 8 78.69707 1.646149
## 9 77.63441 -1.062668
## 10 77.30170 -0.332703
## 11 78.27002 0.968316
## 12 79.13655 0.866531
Tomamos únicamente los datos de AAPL del año 2020.
Creamos el objeto datos_estrategia a partir del objeto datos, y lo convertimos en un dataframe (el formato original es xts, un formato propio para series temporales).
Nos quedamos con la columna AAPL.Adjusted, o de cierre ajustado, y creamos la columna variacion que nos indica cuánto subió o bajó la cotización. La columna variacion recoge las diferencias en el precio ajustado de una sesión y la anterior.
Por ejemplo, el primer día el precio ajustado de cierre es 74.573036, mientras que el segundo día el precio de cierre ajustado es de 73.84803. Esto implica una variación negativa, por lo que registramos un descenso en la cotización de -0.725006 para el día 2. Esto es, el segundo día la cotización ha bajado 0.725006.
De la misma forma se calculan el resto de elementos de la columna variacion. Vemos cómo tenemos varios días con subidas consecutivas, y un periodo de dos días consecutivas con pérdidas en la cotización.
Siguiendo la estrategia planteada anteriormente, calculamos la nueva columna signal:
datos_estrategia$signal <- 0
for (t in 3:nrow(datos_estrategia)) {
if(datos_estrategia$variacion[t] > 0 & datos_estrategia$variacion[t-1] > 0) {
datos_estrategia$signal[t] <- 1
}
if(datos_estrategia$variacion[t] < 0 & datos_estrategia$variacion[t-1] < 0) {
datos_estrategia$signal[t] <- -1
}
}
datos_estrategia[1:12, ]
## AAPL.Adjusted variacion signal
## 1 74.57304 NA 0
## 2 73.84803 -0.725006 0
## 3 74.43647 0.588440 0
## 4 74.08639 -0.350075 0
## 5 75.27816 1.191765 0
## 6 76.87714 1.598976 1
## 7 77.05093 0.173790 1
## 8 78.69707 1.646149 1
## 9 77.63441 -1.062668 0
## 10 77.30170 -0.332703 -1
## 11 78.27002 0.968316 0
## 12 79.13655 0.866531 1
Por defecto, la columna signal tendrá valor 0; esto es, neutral, sin señal de compra ni de venta. Veamos qué señal tenemos para cada uno de los primeros días de la estrategia:
Los días 1 a 4 se alternan días de subida y bajada en la cotización. No se encuentran dos días consecutivos con el mismo signo en la variación. Por lo tanto, ni compramos ni vendemos. Nos quedamos fuera de la operativa.
Los días 5 y 6 registramos subidas consecutivas en la cotización. Esto da la primera señal de compra, que reflejamos con una señal de compra en la columna signal: asignamos un valor 1 al finalizar el sexto día:
datos_estrategia[4:6, ]
## AAPL.Adjusted variacion signal
## 4 74.08639 -0.350075 0
## 5 75.27816 1.191765 0
## 6 76.87714 1.598976 1
datos_estrategia[7:9, ]
## AAPL.Adjusted variacion signal
## 7 77.05093 0.173790 1
## 8 78.69707 1.646149 1
## 9 77.63441 -1.062668 0
datos_estrategia[7:10, ]
## AAPL.Adjusted variacion signal
## 7 77.05093 0.173790 1
## 8 78.69707 1.646149 1
## 9 77.63441 -1.062668 0
## 10 77.30170 -0.332703 -1
datos_estrategia[7:11, ]
## AAPL.Adjusted variacion signal
## 7 77.05093 0.173790 1
## 8 78.69707 1.646149 1
## 9 77.63441 -1.062668 0
## 10 77.30170 -0.332703 -1
## 11 78.27002 0.968316 0
Y así continuaríamos con el proceso de cálculo de la columna signal.
Una vez contamos con información sobre las señales de compra y venta para cada uno de los días, vamos a crear una columna donde pongamos la posicion que tenemos en el mercaddo a raíz de esas señales.
Comenzaremos estando fuera del mercado, y nuestra posición se va actualizando conforme nos llegan las señales.
Observa como, en el caso de no tener señal de compra ni de venta, copiamos la posición del día anterior.
datos_estrategia$posicion <- "fuera"
for(t in 3:nrow(datos_estrategia)) {
if(datos_estrategia$signal[t] == 1) {
datos_estrategia$posicion[t] <- "buy"
} else {
if(datos_estrategia$signal[t] == -1) {
datos_estrategia$posicion[t] <- "sell"
} else {
datos_estrategia$posicion[t] <- datos_estrategia$posicion[t-1]
}
}
}
datos_estrategia[1:12, ]
## AAPL.Adjusted variacion signal posicion
## 1 74.57304 NA 0 fuera
## 2 73.84803 -0.725006 0 fuera
## 3 74.43647 0.588440 0 fuera
## 4 74.08639 -0.350075 0 fuera
## 5 75.27816 1.191765 0 fuera
## 6 76.87714 1.598976 1 buy
## 7 77.05093 0.173790 1 buy
## 8 78.69707 1.646149 1 buy
## 9 77.63441 -1.062668 0 buy
## 10 77.30170 -0.332703 -1 sell
## 11 78.27002 0.968316 0 sell
## 12 79.13655 0.866531 1 buy
El primer día para entar largos (buy) es el 6º. Nos mantenemos en posición buy hasta recibir una señal en dirección contraria (sell), cosa que ocurre el día 10.
Seguimos cortos (sell) hasta recibir una señal de compra que varíe nuestra posición: el día 12.
Una vez que ya sabemos qué días debemos comprar y cuáles vender, podemos calcular el beneficio diario de nuestras posiciones abiertas:
datos_estrategia$beneficio <- 0
for(t in 3:(nrow(datos_estrategia)-1)) {
if(datos_estrategia$posicion[t] == "buy") {
datos_estrategia$beneficio[t+1] = datos_estrategia$variacion[t+1] +
datos_estrategia$beneficio[t]
} else {
if(datos_estrategia$posicion[t] == "sell") {
datos_estrategia$beneficio[t+1] = -datos_estrategia$variacion[t+1] +
datos_estrategia$beneficio[t]
}
}
}
datos_estrategia[1:15, ]
## AAPL.Adjusted variacion signal posicion beneficio
## 1 74.57304 NA 0 fuera 0.000000
## 2 73.84803 -0.725006 0 fuera 0.000000
## 3 74.43647 0.588440 0 fuera 0.000000
## 4 74.08639 -0.350075 0 fuera 0.000000
## 5 75.27816 1.191765 0 fuera 0.000000
## 6 76.87714 1.598976 1 buy 0.000000
## 7 77.05093 0.173790 1 buy 0.173790
## 8 78.69707 1.646149 1 buy 1.819939
## 9 77.63441 -1.062668 0 buy 0.757271
## 10 77.30170 -0.332703 -1 sell 0.424568
## 11 78.27002 0.968316 0 sell -0.543748
## 12 79.13655 0.866531 1 buy -1.410279
## 13 78.60025 -0.536301 0 buy -1.946580
## 14 78.88082 0.280571 0 buy -1.666009
## 15 79.26070 0.379875 1 buy -1.286134
Vemos que para cada día llevamos un registro de nuestro beneficio “flotante”. Esto es, el valor actualizado de nuestra posición, incluyendo los beneficios/pérdidas de nuestras operaciones pasadas, así como el estado actual de la posición abierta.
Podemos representar la cuenta de resultados diaria:
library(ggplot2)
ggplot(datos_estrategia, aes(y = beneficio, x = index(datos_estrategia))) +
geom_line() +
labs(y = "Beneficio", x = "Tiempo")
Ejercicio
5Aplicar la estrategia de inversión sobre todos los datos descargados de AAPL. ¿Resulta la estrategia más rentable que la propia inversión en el título?
## AAPL.Adjusted variacion signal posicion beneficio
## 1 6.604801 NA 0 fuera 0.000000
## 2 6.616219 0.011418 0 fuera 0.000000
## 3 6.510980 -0.105239 0 fuera 0.000000
## 4 6.498945 -0.012035 -1 sell 0.000000
## 5 6.542150 0.043205 0 sell -0.043205
## 6 6.484439 -0.057711 0 sell 0.014506
## 7 6.410679 -0.073760 -1 sell 0.088266
## 8 6.501104 0.090425 0 sell -0.002159
## 9 6.463451 -0.037653 0 sell 0.035494
## 10 6.355436 -0.108015 -1 sell 0.143509
## 11 6.636590 0.281154 0 sell -0.137645
## 12 6.534435 -0.102155 0 sell -0.035490
## 13 6.421480 -0.112955 -1 sell 0.077465
## 14 6.102983 -0.318497 -1 sell 0.395962
## 15 6.267169 0.164186 0 sell 0.231776
## 16 6.355743 0.088574 1 buy 0.143202
## 17 6.415617 0.059874 1 buy 0.203076
## 18 6.150510 -0.265107 0 buy -0.062031
## 19 5.927377 -0.223133 -1 sell -0.285164
## 20 6.009779 0.082402 0 sell -0.367566
Ejercicio
6¿Cuántos días hemos estado largos, cuántos cortos, y cuántos fuera del mercado? Puede parecer llamativo que para un título tan alcista, la estrategia de continuación de tendencia no arroje muchos más días largos que cortos.
##
## buy fuera sell
## 1444 3 1268
Ejercicio
7Una vez conocida la distribución de nuestra posición, calcula el número de operaciones largas frente al número de operaciones cortas. ¿Te sorprende el resultado o es lógico?
## cambio_posicion
## buy sell
## 214 214
Ejercicio
8Repetir la estrategia pero tomando señales cuando se repita la tendencia durante 3 días consecutivos (en lugar de 2).
## AAPL.Adjusted variacion signal posicion beneficio
## 1 6.604801 NA 0 fuera 0.000000
## 2 6.616219 0.011418 0 fuera 0.000000
## 3 6.510980 -0.105239 0 fuera 0.000000
## 4 6.498945 -0.012035 0 fuera 0.000000
## 5 6.542150 0.043205 0 fuera 0.000000
## 6 6.484439 -0.057711 0 fuera 0.000000
## 7 6.410679 -0.073760 0 fuera 0.000000
## 8 6.501104 0.090425 0 fuera 0.000000
## 9 6.463451 -0.037653 0 fuera 0.000000
## 10 6.355436 -0.108015 0 fuera 0.000000
## 11 6.636590 0.281154 0 fuera 0.000000
## 12 6.534435 -0.102155 0 fuera 0.000000
## 13 6.421480 -0.112955 0 fuera 0.000000
## 14 6.102983 -0.318497 -1 sell 0.000000
## 15 6.267169 0.164186 0 sell -0.164186
## 16 6.355743 0.088574 0 sell -0.252760
## 17 6.415617 0.059874 1 buy -0.312634
## 18 6.150510 -0.265107 0 buy -0.577741
## 19 5.927377 -0.223133 0 buy -0.800874
## 20 6.009779 0.082402 0 buy -0.718472
## 21 6.044655 0.034876 0 buy -0.683596
## 22 6.148659 0.104004 1 buy -0.579592
## 23 5.927071 -0.221588 0 buy -0.801180
## 24 6.032310 0.105239 0 buy -0.695941
## 25 5.990953 -0.041357 0 buy -0.737298
Ejercicio
9Repetir la estrategia sobre los dos días de tendencia pero únicamente abriendo largos. Esto es, omitimos las señales de venta y sólo lanzamos largos: si recibimos señal de largo, compramos; si recibimos señal de corto, cerramos la posición de largos (pero no abrimos un corto). Calcula el número de operaciones (largos) realizados.
## AAPL.Adjusted variacion signal posicion beneficio
## 1 6.604801 NA 0 fuera 0.000000
## 2 6.616219 0.011418 0 fuera 0.000000
## 3 6.510980 -0.105239 0 fuera 0.000000
## 4 6.498945 -0.012035 -1 fuera 0.000000
## 5 6.542150 0.043205 0 fuera 0.000000
## 6 6.484439 -0.057711 0 fuera 0.000000
## 7 6.410679 -0.073760 -1 fuera 0.000000
## 8 6.501104 0.090425 0 fuera 0.000000
## 9 6.463451 -0.037653 0 fuera 0.000000
## 10 6.355436 -0.108015 -1 fuera 0.000000
## 11 6.636590 0.281154 0 fuera 0.000000
## 12 6.534435 -0.102155 0 fuera 0.000000
## 13 6.421480 -0.112955 -1 fuera 0.000000
## 14 6.102983 -0.318497 -1 fuera 0.000000
## 15 6.267169 0.164186 0 fuera 0.000000
## 16 6.355743 0.088574 1 buy 0.000000
## 17 6.415617 0.059874 1 buy 0.059874
## 18 6.150510 -0.265107 0 buy -0.205233
## 19 5.927377 -0.223133 -1 fuera -0.428366
## 20 6.009779 0.082402 0 fuera -0.428366
El beneficio total de la estrategia es $83.18.
El número de operaciones asciende a 214. Todo largos, claro.
Volvamos al plantemiento original de la estrategia. Vamos a crear una función que permite calcular el beneficio de la estrategia tomando como parámetros:
La serie de precios sobre la que aplicar la estrategia.
El número necesario de periodos consecutivos al alza/baja para lanzar una señal.
Si se toman largos y cortos, sólo largos, o solo cortos. Si no se toma nada, entonces la estrategia tendrá necesariamente beneficio 0.
Observa 3 novedades en la siguiente implementación:
Algunos parámetros vienen con valores por defecto, viz. n_periodos, largos y cortos.
El nuevo comando all.
La función devuelve una lista de variables (en lugar de un único valor).
estrategia_tendencia <- function(datos, n_periodos = 2, largos = TRUE, cortos = TRUE) {
require(dplyr)
datos_estrategia <-
data.frame(adjusted = as.numeric(datos[, ncol(datos)])) %>%
mutate(variacion = adjusted - lag(adjusted))
# Calculamos la señal
datos_estrategia$signal <- 0
for (t in (n_periodos+1):nrow(datos_estrategia)) {
if(all(datos_estrategia$variacion[(t-n_periodos+1):t] > 0)) {
datos_estrategia$signal[t] <- 1
}
if(all(datos_estrategia$variacion[(t-n_periodos+1):t] < 0)) {
datos_estrategia$signal[t] <- -1
}
}
# Calculamos las posiciones
datos_estrategia$posicion <- "fuera"
for(t in (n_periodos+1):nrow(datos_estrategia)) {
if(datos_estrategia$signal[t] == 1) {
datos_estrategia$posicion[t] <- "buy"
} else {
if(datos_estrategia$signal[t] == -1) {
datos_estrategia$posicion[t] <- "sell"
} else {
datos_estrategia$posicion[t] <- datos_estrategia$posicion[t-1]
}
}
}
# Calculamos el beneficio
datos_estrategia$beneficio <- 0
for(t in (n_periodos+1):(nrow(datos_estrategia)-1)) {
if(datos_estrategia$posicion[t] == "buy" & largos == TRUE) {
datos_estrategia$beneficio[t+1] = datos_estrategia$variacion[t+1] +
datos_estrategia$beneficio[t]
}
if(datos_estrategia$posicion[t] == "buy" & largos == FALSE) {
datos_estrategia$beneficio[t+1] = datos_estrategia$beneficio[t]
}
if(datos_estrategia$posicion[t] == "sell" & cortos == TRUE) {
datos_estrategia$beneficio[t+1] = -datos_estrategia$variacion[t+1] +
datos_estrategia$beneficio[t]
}
if(datos_estrategia$posicion[t] == "sell" & cortos == FALSE) {
datos_estrategia$beneficio[t+1] = datos_estrategia$beneficio[t]
}
}
# Devolvemos la serie con el beneficio diario, la rentabilidad absoluta y la rentabilidad relativa
return(list(serie_beneficio = datos_estrategia$beneficio,
rent_absoluta = datos_estrategia$beneficio[nrow(datos_estrategia)],
rent_relativa = datos_estrategia$beneficio[nrow(datos_estrategia)] /
datos_estrategia$adjusted[1]))
}
library(scales)
resultado_1 <- estrategia_tendencia(AAPL, n_periodos = 2)
str(resultado_1)
## List of 3
## $ serie_beneficio: num [1:2715] 0 0 0 0 -0.0432 ...
## $ rent_absoluta : num 51.7
## $ rent_relativa : num 7.82
dollar(resultado_1$rent_absoluta)
## [1] "$51.67"
resultado_2 <- estrategia_tendencia(AAPL, n_periodos = 3)
dollar(resultado_2$rent_absoluta)
## [1] "$40.04"
resultado_3 <- estrategia_tendencia(AAPL, n_periodos = 2, cortos = FALSE)
dollar(resultado_3$rent_absoluta)
## [1] "$83.18"
La anterior función ha permitido calcular el beneficio de la estrategia para diferentes valores del parámetro n_periodos.
Puede resultar interesante calcular el beneficio para otras posibles combinaciones de n_periodos, largos y cortos.
matriz_beneficio <- expand.grid(n_periodos = 1:5,
largos = c(TRUE, FALSE),
cortos = c(TRUE, FALSE))
matriz_beneficio$rent_absoluta <- matriz_beneficio$rent_relativa <- 0
matriz_beneficio
## n_periodos largos cortos rent_relativa rent_absoluta
## 1 1 TRUE TRUE 0 0
## 2 2 TRUE TRUE 0 0
## 3 3 TRUE TRUE 0 0
## 4 4 TRUE TRUE 0 0
## 5 5 TRUE TRUE 0 0
## 6 1 FALSE TRUE 0 0
## 7 2 FALSE TRUE 0 0
## 8 3 FALSE TRUE 0 0
## 9 4 FALSE TRUE 0 0
## 10 5 FALSE TRUE 0 0
## 11 1 TRUE FALSE 0 0
## 12 2 TRUE FALSE 0 0
## 13 3 TRUE FALSE 0 0
## 14 4 TRUE FALSE 0 0
## 15 5 TRUE FALSE 0 0
## 16 1 FALSE FALSE 0 0
## 17 2 FALSE FALSE 0 0
## 18 3 FALSE FALSE 0 0
## 19 4 FALSE FALSE 0 0
## 20 5 FALSE FALSE 0 0
Ejercicio
10Completa los campos
rent_absolutayrent_relativautilizando la funciónestrategia_tendencia, utilizando un bucleforowhile. ¿Por qué las últimas filas dematriz_beneficioaparecen con ceros?
matriz_beneficio
## n_periodos largos cortos rent_relativa rent_absoluta
## 1 1 TRUE TRUE -5.335212 -35.23801
## 2 2 TRUE TRUE 7.822646 51.66702
## 3 3 TRUE TRUE 6.061900 40.03764
## 4 4 TRUE TRUE 11.654840 76.97790
## 5 5 TRUE TRUE 11.168879 73.76823
## 6 1 FALSE TRUE -11.341129 -74.90590
## 7 2 FALSE TRUE -4.771078 -31.51202
## 8 3 FALSE TRUE -5.681426 -37.52469
## 9 4 FALSE TRUE -2.858696 -18.88112
## 10 5 FALSE TRUE -3.068897 -20.26946
## 11 1 TRUE FALSE 6.005917 39.66789
## 12 2 TRUE FALSE 12.593724 83.17904
## 13 3 TRUE FALSE 11.743326 77.56233
## 14 4 TRUE FALSE 14.513535 95.85901
## 15 5 TRUE FALSE 14.237776 94.03768
## 16 1 FALSE FALSE 0.000000 0.00000
## 17 2 FALSE FALSE 0.000000 0.00000
## 18 3 FALSE FALSE 0.000000 0.00000
## 19 4 FALSE FALSE 0.000000 0.00000
## 20 5 FALSE FALSE 0.000000 0.00000
Ejercicio
11Obtén un modelo de regresión que explique la variable
rent_absolutadel anterior ejercicio a partir de las variablesn_periodos,largosycortos. Atendiendo a los resultados, ¿recomiendas hacer sólo largos, sólo cortos, o ambos? ¿Es mejor basar la tendencia en varios días o reducirlo a unos pocos?
##
## Call:
## lm(formula = rent_absoluta ~ n_periodos + largos + cortos, data = matriz_beneficio)
##
## Residuals:
## Min 1Q Median 3Q Max
## -52.348 -9.308 -0.249 13.443 24.332
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -36.499 12.362 -2.952 0.009364 **
## n_periodos 12.166 3.192 3.811 0.001535 **
## largosTRUE 78.061 9.028 8.646 2e-07 ***
## cortosTRUE -36.619 9.028 -4.056 0.000917 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 20.19 on 16 degrees of freedom
## Multiple R-squared: 0.8686, Adjusted R-squared: 0.8439
## F-statistic: 35.25 on 3 and 16 DF, p-value: 2.793e-07
En el anterior epígrafe hemos comparado la rentabilidad, tanto absoluta como relativa, de diferentes combinaciones de los parámetros.
En la presente sección calcularemos una popular medida de riesgo en el análisis de estrategias de inversión: el máximo drawdown (MD), que mide la profundidad de la mayor caída en el beneficio de una estrategia. Para ello debe calcularse previamente el drawdown (D) para caulquier instante de tiempo t.
El drawdown para un instante de tiempo t se define como la diferencia entre el beneficio (flotante) registrado en el instante t y el máximo beneficio registrado hasta el instante t:
\[D(t) = \max_{s \in \{0,t \}} \left[beneficio(s) - beneficio(t)\right ]\]
El máximo drawdown de la estrategia se obtiene como el mayor drawdown registrado por la estrategia durante todo el periodo de tiempo analizado (T):
\[MD = max_{t \in \{0,T \}} D(t)\] En la siguiente figura se recoge un ejemplo gráfico sobre el cálculo del máximo drawdown:
Ejemplo de máximo drawdown. Figura tomada de https://finanzaszone.com/evaluando-estrategias-de-trading-ii-drawdown-y-maximo-drawdown/
Ejercicio
12Modificar la función
estrategia_tendenciapara que devuelva dos parámetros adicionales: eldrawdownen cada instante de tiempo (los datos son diarios) y elmax_drawdownde la estrategia. Aquí van algunos ejemplos del resultado que debería obtenerse:
resultado_4 <- estrategia_tendencia(AAPL, n_periodos = 2)
dollar(resultado_4$max_drawdown)
## [1] "$30.41"
resultado_4 %>%
as.data.frame() %>%
select(serie_beneficio, drawdown) %>%
slice(1:20)
## serie_beneficio drawdown
## 1 0.000000 0.000000
## 2 0.000000 0.000000
## 3 0.000000 0.000000
## 4 0.000000 0.000000
## 5 -0.043205 0.043205
## 6 0.014506 0.000000
## 7 0.088266 0.000000
## 8 -0.002159 0.090425
## 9 0.035494 0.052772
## 10 0.143509 0.000000
## 11 -0.137645 0.281154
## 12 -0.035490 0.178999
## 13 0.077465 0.066044
## 14 0.395962 0.000000
## 15 0.231776 0.164186
## 16 0.143202 0.252760
## 17 0.203076 0.192886
## 18 -0.062031 0.457993
## 19 -0.285164 0.681126
## 20 -0.367566 0.763528
Comparemos la primera parte de la figura del drawdown frente al beneficio:
plot(resultado_4$drawdown[1:100], type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_4$drawdown[1:100], resultado_4$serie_beneficio[1:100]),
max(resultado_4$drawdown[1:100], resultado_4$serie_beneficio[1:100])))
lines(resultado_4$serie_beneficio[1:100], col = "grey50")
Y ahora la serie completa:
plot(resultado_4$drawdown, type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_4$max_drawdown, resultado_4$serie_beneficio),
max(resultado_4$max_drawdown, resultado_4$serie_beneficio)))
lines(resultado_4$serie_beneficio, col = "grey50")
resultado_5 <- estrategia_tendencia(AAPL, n_periodos = 3, cortos = FALSE)
dollar(resultado_5$max_drawdown)
## [1] "$29.28"
resultado_5 %>%
as.data.frame() %>%
select(serie_beneficio, drawdown) %>%
slice(1:20)
## serie_beneficio drawdown
## 1 0.000000 0.000000
## 2 0.000000 0.000000
## 3 0.000000 0.000000
## 4 0.000000 0.000000
## 5 0.000000 0.000000
## 6 0.000000 0.000000
## 7 0.000000 0.000000
## 8 0.000000 0.000000
## 9 0.000000 0.000000
## 10 0.000000 0.000000
## 11 0.000000 0.000000
## 12 0.000000 0.000000
## 13 0.000000 0.000000
## 14 0.000000 0.000000
## 15 0.000000 0.000000
## 16 0.000000 0.000000
## 17 0.000000 0.000000
## 18 -0.265107 0.265107
## 19 -0.488240 0.488240
## 20 -0.405838 0.405838
plot(resultado_5$drawdown[1:100], type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_5$drawdown[1:100], resultado_5$serie_beneficio[1:100]),
max(resultado_5$drawdown[1:100], resultado_5$serie_beneficio[1:100])))
lines(resultado_5$serie_beneficio[1:100], col = "grey50")
En el análisis bursátil existen dos enfoques para enfrentarse a las decisiones de inversión: el análisi fundamental y el análisis técnico.
El análisis fundamental guía sus decisiones utilizando la información económico-financiera de las empresas, de forma que se invierte en aquellas empresas cuyas cuentas se encuentran más saneadas y con mejor proyección de futuro.
En el análisis técnico las decisiones de inversión se llevan a cabo atendiendo únicamente a la cotización de la acción y a su comportamiento en el pasado. Para ello se dispone de gran cantidad de indicadores técnicos, que no son otra cosa que fórmulas aplicadas sobre las cotizaciones de los títulos (y en ocasiones considerando también el volumen).
En esta sección vamos a emplear el enfoque del análisis técnico, implementando alguna estrategia a partir de diferentes indicadores técnicos.
Utilizaremos la librería quantmod para obtener diferentes indicadores técnicos.
La media móvil se obtiene promediando los últimos valores de la cotización, por lo que se utiliza como un indicador retrasado de la tendencia de la cotización:
merge(AAPL$AAPL.Adjusted[1:20], SMA(AAPL$AAPL.Adjusted[1:20], n = 10))
## AAPL.Adjusted SMA
## 2010-01-04 6.604801 NA
## 2010-01-05 6.616219 NA
## 2010-01-06 6.510980 NA
## 2010-01-07 6.498945 NA
## 2010-01-08 6.542150 NA
## 2010-01-11 6.484439 NA
## 2010-01-12 6.410679 NA
## 2010-01-13 6.501104 NA
## 2010-01-14 6.463451 NA
## 2010-01-15 6.355436 6.498820
## 2010-01-19 6.636590 6.501999
## 2010-01-20 6.534435 6.493821
## 2010-01-21 6.421480 6.484871
## 2010-01-22 6.102983 6.445275
## 2010-01-25 6.267169 6.417777
## 2010-01-26 6.355743 6.404907
## 2010-01-27 6.415617 6.405401
## 2010-01-28 6.150510 6.370341
## 2010-01-29 5.927377 6.316734
## 2010-02-01 6.009779 6.282168
chartSeries(AAPL, type = "line", subset = "2020-04::2020-06",
TA = "addSMA(n = 20)", theme = chartTheme("white"))
Como puedes observar en el anterior gráfico, la media móvil puede utilizarse fácilmente para establecer otra estrategia bursátil de continuación de la tendencia:
Señal de largos cuando el precio cruza al alza la media móvil.
Señal de cortos cuando el precio cruza a la baja la media móvil.
Ejercicio
13Implementar a través de una función la estrategia de inversión basada en la media móvil sobre los datos completos de AAPL. La función debe tomar los mismos parámetros que la función
estrategia_tendencia). En este caso, el parámetro número de periodosn_periodoshara referencia al número de periodos con que se calcula la media móvil.
resultado_6 <- estrategia_SMA(AAPL)
dollar(resultado_6$max_drawdown)
## [1] "$26.75"
resultado_6 %>%
as.data.frame() %>%
select(serie_beneficio, drawdown) %>%
slice(1:20)
## serie_beneficio drawdown
## 1 0.000000 0.000000
## 2 0.000000 0.000000
## 3 0.000000 0.000000
## 4 0.000000 0.000000
## 5 0.000000 0.000000
## 6 0.000000 0.000000
## 7 0.000000 0.000000
## 8 0.000000 0.000000
## 9 0.000000 0.000000
## 10 0.000000 0.000000
## 11 -0.281154 0.281154
## 12 -0.383309 0.383309
## 13 -0.496264 0.496264
## 14 -0.177767 0.177767
## 15 -0.341953 0.341953
## 16 -0.430527 0.430527
## 17 -0.490401 0.490401
## 18 -0.755508 0.755508
## 19 -0.532375 0.532375
## 20 -0.614777 0.614777
Comparemos la primera parte de la figura del drawdown frente al beneficio:
plot(resultado_6$drawdown[1:100], type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_6$drawdown[1:100], resultado_6$serie_beneficio[1:100]),
max(resultado_6$drawdown[1:100], resultado_6$serie_beneficio[1:100])))
lines(resultado_6$serie_beneficio[1:100], col = "grey50")
Y ahora la serie completa:
plot(resultado_6$drawdown, type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_6$max_drawdown, resultado_6$serie_beneficio),
max(resultado_6$max_drawdown, resultado_6$serie_beneficio)))
lines(resultado_6$serie_beneficio, col = "grey50")
resultado_7 <- estrategia_SMA(AAPL, n_periodos = 20)
resultado_7 %>%
as.data.frame() %>%
select(serie_beneficio, drawdown) %>%
slice(1:30)
## serie_beneficio drawdown
## 1 0.000000 0.000000
## 2 0.000000 0.000000
## 3 0.000000 0.000000
## 4 0.000000 0.000000
## 5 0.000000 0.000000
## 6 0.000000 0.000000
## 7 0.000000 0.000000
## 8 0.000000 0.000000
## 9 0.000000 0.000000
## 10 0.000000 0.000000
## 11 0.000000 0.000000
## 12 0.000000 0.000000
## 13 0.000000 0.000000
## 14 0.000000 0.000000
## 15 0.000000 0.000000
## 16 0.000000 0.000000
## 17 0.000000 0.000000
## 18 0.000000 0.000000
## 19 0.000000 0.000000
## 20 0.000000 0.000000
## 21 -0.034876 0.034876
## 22 -0.138880 0.138880
## 23 0.082708 0.000000
## 24 -0.022531 0.105239
## 25 0.018826 0.063882
## 26 -0.045060 0.127768
## 27 -0.012035 0.094743
## 28 -0.121598 0.204306
## 29 -0.174370 0.257078
## 30 -0.267574 0.350282
plot(resultado_7$drawdown, type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_7$max_drawdown, resultado_7$serie_beneficio),
max(resultado_7$max_drawdown, resultado_7$serie_beneficio)))
lines(resultado_7$serie_beneficio, col = "grey50")
resultado_8 <- estrategia_SMA(AAPL, n_periodos = 50, cortos = FALSE)
plot(resultado_8$drawdown, type = "l", col = "blue", lty = 3,
ylim = c(min(resultado_8$max_drawdown, resultado_8$serie_beneficio),
max(resultado_8$max_drawdown, resultado_8$serie_beneficio)))
lines(resultado_8$serie_beneficio, col = "grey50")
Ejercicio
14Obtén en una matriz (
matriz_beneficio) los resultados de aplicar la estrategia sobre el siguiente grid de valores. Mediante una regresión, calcula la incidencia de los parámetros en la rentabilidad absoluta de la estrategia. Haz una segunda regresión eliminando del análisis los casos dondelargosycortossonFALSE. ¿Se mantienen unos resultados similares?
matriz_beneficio <- expand.grid(n_periodos = c(10, 20, 30, 40, 50),
largos = c(TRUE, FALSE),
cortos = c(TRUE, FALSE))
matriz_beneficio$rent_absoluta <- matriz_beneficio$rent_relativa <- 0
## n_periodos largos cortos rent_relativa rent_absoluta
## 1 10 TRUE TRUE 7.336529 48.456312
## 2 20 TRUE TRUE 14.589063 96.357857
## 3 30 TRUE TRUE 14.209305 93.849635
## 4 40 TRUE TRUE 6.772099 44.728368
## 5 50 TRUE TRUE 4.962183 32.774229
## 6 10 FALSE TRUE -5.025000 -33.189127
## 7 20 FALSE TRUE -1.424900 -9.411183
## 8 30 FALSE TRUE -1.594523 -10.531507
## 9 40 FALSE TRUE -5.300393 -35.008041
## 10 50 FALSE TRUE -6.168904 -40.744386
## 11 10 TRUE FALSE 12.361529 81.645439
## 12 20 TRUE FALSE 16.013963 105.769040
## 13 30 TRUE FALSE 15.803828 104.381142
## 14 40 TRUE FALSE 12.072492 79.736409
## 15 50 TRUE FALSE 11.131087 73.518615
## 16 10 FALSE FALSE 0.000000 0.000000
## 17 20 FALSE FALSE 0.000000 0.000000
## 18 30 FALSE FALSE 0.000000 0.000000
## 19 40 FALSE FALSE 0.000000 0.000000
## 20 50 FALSE FALSE 0.000000 0.000000
##
## Call:
## lm(formula = rent_absoluta ~ n_periodos + largos + cortos, data = matriz_beneficio)
##
## Residuals:
## Min 1Q Median 3Q Max
## -23.076 -9.813 -4.616 12.314 30.616
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 12.449 10.379 1.199 0.24781
## n_periodos -0.415 0.268 -1.548 0.14106
## largosTRUE 89.010 7.580 11.743 2.81e-09 ***
## cortosTRUE -25.777 7.580 -3.401 0.00365 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 16.95 on 16 degrees of freedom
## Multiple R-squared: 0.9047, Adjusted R-squared: 0.8868
## F-statistic: 50.62 on 3 and 16 DF, p-value: 2.176e-08
##
## Call:
## lm(formula = rent_absoluta ~ n_periodos + largos + cortos, data = matriz_beneficio %>%
## filter(largos | cortos))
##
## Residuals:
## Min 1Q Median 3Q Max
## -25.843 -15.701 -3.741 13.236 30.616
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 16.5987 18.9014 0.878 0.3986
## n_periodos -0.5533 0.3638 -1.521 0.1565
## largosTRUE 89.0101 12.6009 7.064 2.09e-05 ***
## cortosTRUE -25.7768 12.6009 -2.046 0.0655 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 19.92 on 11 degrees of freedom
## Multiple R-squared: 0.8949, Adjusted R-squared: 0.8663
## F-statistic: 31.23 on 3 and 11 DF, p-value: 1.118e-05
Ejercicio
15Realiza el mismo ejercicio sobre otros valores. Pueden ser americanos o europeos.
Ejercicio
16La librería
quantmodincorpora muchos más indicadores técnicos. Prueba a implementar alguna estrategia que utilice uno o varios de estos indicadores.
%%
chart_Series
chartSeries
dollar
expand_grid
for
function
getSymbols
if .. else
merge
paste
paste0
return
slice
SMA
Sys.Date
while