Curso
En esta entrada te muestro cómo usar R para recopilar las acciones listadas en loyal3, obtener datos históricos de Yahoo y aplicar una estrategia sencilla de trading algorítmico. Por el camino, aprenderás algo de web scraping, a llamar a una API financiera y a usar un htmlwidget para crear un gráfico interactivo de series temporales.
Para este artículo, un "trading algo" se define como un conjunto de reglas que activan eventos de compra o venta, más que como un modelo predictivo o un pronóstico de series temporales. Es el tipo más simple de algoritmo de trading, pero si quieres profundizar en finanzas con R, te recomiendo el curso de DataCamp sobre modelado de una estrategia cuantitativa de trading en R.
Contexto
En 2015 empecé a invertir un poco en loyal3. Su servicio es atípico y un gran lugar para iniciarte en la inversión. En lugar de cobrar comisiones al inversor, loyal3 cobra a las empresas por aparecer en su plataforma. La premisa es que quienes disfrutan del servicio de una compañía también comprarán sus acciones y, al hacerlo, se convertirán en grandes embajadores de la marca. Además, puedes comprar fracciones de acciones. Así, puedes entrar en esa acción de Amazon de $800 por solo $10 y añadir otra fracción de $10 cada vez que te sobre algo de dinero a final de mes. Claro que hay costes de fricción porque tienes que operar en ventanas y tu cartera se limita a unas 70 acciones, pero loyal3 es una forma divertida y barata de explorar la inversión en renta variable. ¡Puedes poner dinero real en juego desde tan solo $10!
Para que conste, tengo las típicas cuentas de jubilación e inversión, pero me gusta la interfaz limpia de la app de loyal3 y la ausencia de comisiones. Acabo mirando mi cartera de loyal3 por diversión más a menudo que mis fondos, simplemente porque es fácil y entretenido ver el rendimiento de las acciones que he elegido directamente.
Configura tu espacio de trabajo
Para empezar, carga las librerías en tu entorno. Casi siempre uso rvest para web scraping hoy en día. Hay otros paquetes que funcionan, incluido RSelenium, pero me gusta lo sencillo que es ejecutar rvest.
El segundo paquete, pbapply, es opcional porque simplemente añade una barra de progreso a las funciones apply. Como podrías estar extrayendo cientos de páginas web, una barra de progreso ayuda a estimar el tiempo.
Después, TTR es un paquete que acabo de empezar a explorar. Se utiliza para construir "reglas de trading técnico". Aunque aquí aprenderás un algoritmo sencillo, TTR puede realizar cálculos más sofisticados y merece la pena aprenderlo.
La librería dygraphs es un wrapper de una librería JavaScript de gráficos rápida y open source. Es uno de los htmlwidgets que hace que los gráficos en R sean dinámicos y formen parte de un archivo HTML en lugar de una imagen estática. Por último, el paquete lubridate se usa para manejar fechas de forma sencilla.
library(rvest)
library(pbapply)
library(TTR)
library(dygraphs)
library(lubridate)
Recopilación de datos
Todas las acciones de loyal3 están listadas en una sola página. Antes de poder consultar precios diarios individuales para construir tu algoritmo de trading, necesitas recopilar todos los tickers disponibles. Lo primero es declarar stock.list como una URL. Luego usa read_html() para que tu sesión de R cree una sesión de Internet y recoja todo el HTML de la página como un conjunto de nodos XML. El CSS de la página tiene un ID llamado ".company-name". Úsalo como parámetro al llamar a html_nodes() para seleccionar solo los datos XML asociados a ese nodo. Por último, usa html_text() para obtener el texto con los nombres de las compañías.
stock.list<-'https://www.loyal3.com/stocks'
stocks<-read_html(stock.list)
stocks.names<-html_nodes(stocks,'.company-name')
stocks.names<-html_text(stocks.names)
Para revisar las acciones disponibles en loyal3, puedes imprimir el objeto stocks.names en la consola. Devuelve los nombres de las compañías como un vector de texto.
stocks.names
Para investigar los precios, primero necesitas el símbolo del ticker. En el sitio de loyal3, puedes hacer clic en la tarjeta de la empresa para abrir una página con el ticker y otra información.
Usando html_nodes() sobre stocks, extraes todos los nodos marcados con una etiqueta "a". En HTML, la etiqueta <a> define un hipervínculo que enlaza de una página a otra. Dentro de la etiqueta, el atributo "href" es la URL exacta. Así que html_attr() extraerá la URL de TODOS los enlaces de la página si le pasas "href".
Tras una inspección manual, vi que los enlaces 54 a 123 de la página corresponden a las páginas de empresa que necesito para extraer el ticker. La última línea usa paste0() para concatenar la URL base ‘http://www.loyal3.com` con las páginas específicas de cada empresa, como "/WALMART". Por ejemplo, http://www.loyal3.com/WALMART:
loyal.links<-html_nodes(stocks, "a")
loyal.links<-html_attr(loyal.links, "href")
stock.links<-paste0('http://www.loyal3.com',loyal.links[54:123])
En cada página de empresa hay una descripción, un precio de cierre reciente y el ticker. Todas las páginas están organizadas igual, así que la función personalizada get.ticker() sirve para extraer el símbolo.
Dentro de la página hay una tabla llamada "ticker-price". La función navega a la página de la empresa, identifica la tabla adecuada y extrae el texto con html_text(). Por último, usando sub() junto con la expresión regular ^([[:alpha:]]*).* y \\1 conservarás solo caracteres alfabéticos. El resultado es que se eliminan caracteres especiales, como $, y cualquier número, como el precio de cierre. A medida que la función lee cada una de las 70 páginas, solo recoge el ticker.
get.ticker<-function(url){
x<-read_html(url)
x<-html_node(x,'.ticker-price')
x<-html_text(x)
x<-sub("^([[:alpha:]]*).*", "\\1", x)
return(x)
}
Con tu función, usa pblapply() para aplicarla a cada uno de los stock.links que contienen la página de cada empresa. El objeto resultante, stock.tickers, es una lista de tickers en la que cada elemento corresponde a una empresa.
stock.tickers<-pblapply(stock.links,get.ticker)
Una forma de convertir una lista de elementos en un objeto plano es con do.call(). Aquí aplicas rbind para unir por filas cada elemento de la lista en un único vector. Por último, creas un data frame con el símbolo y el nombre de la compañía.
stock.ticks<-do.call(rbind,stock.tickers)
stock.ticks<-data.frame(symbol=stock.ticks,name=stocks.names)
Para ser coherente en tu análisis, quizá quieras limitar la cantidad de histórico que reúnes de cada acción. La función Sys.Data() almacenará una fecha como año, mes y día. Usar años como entero es una forma de restar una cantidad concreta de tiempo al objeto start.date.
start.date<-Sys.Date()
end.date<-Sys.Date()-years(3)
Para obtener los datos de Yahoo Finance, el objeto fecha debe transformarse a texto simple sin guiones. Usar la sustitución global gsub() sobre start.date y end.date cambiará la clase y, a la vez, eliminará los guiones. Dentro de gsub(), pasa el patrón de caracteres a buscar y luego el reemplazo. En este caso, el patrón de reemplazo es una cadena vacía entre comillas. El último parámetro es el objeto al que se aplica gsub().
start.date<-gsub('-','', start.date)
end.date<-gsub('-','', end.date)
La función getYahooData() del paquete TTR acepta un símbolo y una fecha de inicio y fin. Devuelve un data frame con información de serie temporal. Cada fila es una fecha y las columnas incluyen los precios de "Open", "High", "Low" y "Close" de la acción. Como consultas múltiples compañías, puedes usar lapply() o pblapply(). Pasa el vector de símbolos, luego la función getYahooData() y, después, la información de fechas. Los objetos date se reciclan cada vez que getYahooData() se aplica a un símbolo.
stocks.ts<-pblapply(stock.ticks$symbol,getYahooData,end.date, start.date)
Para facilitar la navegación de la lista devuelta, stocks.ts, puedes añadir nombres a los elementos. Usando names con el objeto stocks.ts, asigna como nombres el vector original $symbol.
names(stocks.ts)<-stock.ticks$symbol
Cuando trabajo con listas grandes, me gusta examinar el objeto de salida para asegurarme de que es lo esperado. Ahora que los elementos tienen nombre, puedes referenciarles directamente. En este ejemplo, consultas las 6 primeras filas de AMC Entertainment Holdings (AMC). Usar head() en la lista referenciando $AMC devolverá una parte de la serie temporal de esta acción:
head(stocks.ts$AMC)
Examinando los datos de las acciones
Cuando escucho a analistas financieros, a menudo se refieren a gráficos. A pesar del high-frequency trading y la gestión activa que hacen otros, muchos pequeños inversores siguen mirando gráficos para obtener pistas. El objeto de serie temporal puede mostrarse rápidamente con plot. Pasa la lista refiriéndote al elemento con nombre, como $AMC, y luego la columna que quieres mostrar, aquí $Close.
plot(stocks.ts$AMZN$Close)

El gráfico anterior es estático y poco interesante.
Vamos a usar una librería de JavaScript para crear un gráfico que puedas explorar. En este fragmento, verás el operador "%>%" o pipe. Es una forma muy cómoda de escribir código conciso. Envía un objeto a la siguiente función sin obligarte a reescribir el nombre del objeto como hiciste antes.
En este ejemplo, creas un dygraph con la acción de Twitter, $TWTR, y la columna a representar, $Close. Dentro de dygraph, main añade un título especificado entre comillas. Con "%>%" todo el objeto se encadena a la siguiente función, dyRangeSelector(). Puedes fijar un rango de fechas por defecto usando c() con una fecha de inicio y otra de fin en formato texto. El objeto HTML resultante es una serie temporal dinámica de la acción de Twitter con un deslizador de fechas abajo.
Recuerda: para cambiar la acción mostrada, cambia el ticker en la lista stocks.ts y el título del gráfico.
dygraph(stocks.ts$TWTR$Close, main = "TWTR Stock Price") %>%
dyRangeSelector(dateWindow = c("2013-12-18", "2016-12-30"))
Una estrategia sencilla: seguir la tendencia
Los high-frequency traders y los hedge funds usan modelos sofisticados y enfoques basados en reglas para ejecutar órdenes. Si quieres saber más, te sugiero visitar www.quantopian.com para enfoques avanzados. Para enfoques más simples, empieza con esta página en www.Investopedia.com.
En el código de abajo, vas a visualizar una estrategia de momentum simple. Básicamente, quieres calcular las medias móviles de 200 y 50 días del precio. Cualquier día en que la media de 50 días esté por encima de la de 200, comprarías o mantendrías tu posición. En los días en que la media de 200 supere a la de 50, venderías tus acciones. Esta estrategia se llama de seguimiento de tendencia. La relación positiva o negativa entre ambas medias temporales representa el momentum de la acción.
El paquete TTR proporciona SMA() para calcular la media móvil simple. En este fragmento, consultas los 6 primeros valores de las medias de 200 y 50 días de Twitter. SMA() funciona pasando la serie temporal de una acción y una columna específica como Close. Es un vector con los cierres de TWTR. El segundo parámetro es un entero que representa el número de observaciones de la media móvil. Sin usar head(), SMA() devolvería todos los valores.
head(SMA(stocks.ts$TWTR$Close, 200))
head(SMA(stocks.ts$TWTR$Close, 50))
Ahora que has visto la función de medias móviles en detalle, necesitas aplicarla a cada una de las 70 acciones. stocks.ts es una lista de 70 data frames con datos individuales. La cuarta columna de cada data frame contiene el precio de cierre que usaremos para las medias móviles.
La función personalizada mov.avgs() acepta un data frame de una acción para calcular las medias móviles. La primera línea selecciona los cierres porque indexa [,4] para crear stock.close. Después, la función usa ifelse para comprobar el número de filas. Concretamente, si nrow es menor que (2*260), la función creará un data frame de medias móviles con "NA".
Elegí este número porque hay unas 250 sesiones al año, así que comprueba que la serie tiene unos 2 años o más. A veces loyal3 accede a OPVs y, si la acción lleva poco en el mercado, no habrá suficientes datos para una media de 200 días. Sin embargo, si nrow es mayor que 2*260, la función creará un data frame con los datos originales y las medias de 200 y 50 días como columnas nuevas. Con colnames declaro los nombres de las columnas. La última parte usa complete.cases para comprobar valores en la columna de la media de 200 días. Cualquier fila sin valor se elimina del resultado final.
mov.avgs<-function(stock.df){
stock.close<-stock.df[,4]
ifelse((nrow(stock.df)<(2*260)),
x<-data.frame(stock.df, 'NA', 'NA'),
x<-data.frame(stock.df, SMA(stock.close, 200), SMA(stock.close, 50)))
colnames(x)<-c(names(stock.df), 'sma_200','sma_50')
x<-x[complete.cases(x$sma_200),]
return(x)
}
Con esta función mov.avgs() puedes usar pblapply() para añadir los cálculos de medias móviles a cada uno de los 70 data frames.
stocks.ts<-pblapply(stocks.ts, mov.avgs)
Usa el siguiente código para visualizar las medias móviles de una acción con un dygraph. De nuevo, este código usa el operador "%>%" para encadenar funciones. dygraph() recibe el data frame stocks.ts$FOX. Concretamente, se indexa por nombre de columna con c('sma_200','sma_50'). Este objeto se pasa a dySeries() en las 2 líneas siguientes. Puedes referirte a una columna por nombre, así que dySeries() traza una línea para "sma_50" y otra para "sma_200" en las líneas 2 y 3. Este objeto se pasa a dyRangeSelector() para ajustar la altura del selector. Por último, añadí sombreado para marcar los periodos en los que querrías comprar/mantener la acción y los periodos en los que deberías haber vendido o mantenidote al margen según tu posición.
dygraph(stocks.ts$FOX[,c('sma_200','sma_50')],main = 'FOX Moving Averages') %>%
dySeries('sma_50', label = 'sma 50') %>%
dySeries('sma_200', label = 'sma 200') %>%
dyRangeSelector(height = 30) %>%
dyShading(from = '2016-4-28', to = '2016-7-27', color = '#CCEBD6') %>%
dyShading(from = '2016-7-28', to = '2016-12-30', color = '#FFE6E6')
Este es el resultado final en una serie temporal interactiva.
Conclusión
Como trader algorítmico en ciernes, no necesitas trazar las 70 acciones. En su lugar, querrás ejecutar el código cada día y añadir una forma programática de identificar las que cumplan el método basado en reglas: "compra si la media móvil de 50 días está por encima de la de 200". Si revisas el gráfico anterior, la sección verde es un periodo en el que habrías comprado la acción de FOX. La sección roja representa el momento de vender y no reentrar.
Como el gráfico es interactivo, puedes usar el deslizador para cambiar la vista. Basándonos en este enfoque sencillo de trading algorítmico, ¡ahora podría ser un buen momento para comprar FOX! ¡El 30 de diciembre de 2016 fue un día en el que la media de 50 días superó en $0.01 a la de 200 días!
Por supuesto, recuerda que toda inversión puede perder valor. Para aprender más sobre finanzas y trading algorítmico, echa un vistazo a los cursos de DataCamp aquí.
También puedes ver nuestro tutorial de habilidades básicas de programación en R.
