Un Pantallazo en R: Basic Value Stock Screener

Alessandro__ARG

12/09/2020

Intro

Este el primero de una serie de post mostrando y desmitificando como utilizar R, en lenguaje de programación favorito de los estadísticos y académicos, para analizar información en los mercados financieros. Para quién empieza de 0, dentro de poco publicaré un cuadernillo llamado “Investing: We Are Friends” donde en el apéndice se enseñará las bases con aplicaciones financieras.

Simple Screener

Para hacer una demostración aplicada crearemos un Escáner de Acciones, que sea fácil de codificar y escalable. El objetivo de este será calcular el valor de una acción mediante el ingreso neto como medida de rentabilidad, también conocido como Price-to-Earnings:
\(\frac{Price}{Net Income * shares}\)

Set Up

Primero lo primero. R, como también Python y Julia, tienen una serie de ventajas por sobre software propietario como Matlab, Stata o el gran Excel, que es su plataforma Open Source. Esto permite que sus funcionalidades estén siempre actualizadas y por sobre todas las cosas se ven potenciadas gracias al desarrollo de Paquetes (packages) que son escritos y publicados gratuitamente.

Para este ejemplo usaremos los siguientes:

  • tidyverse: famoso paradigma para manipular datos.
  • tidyquant: paquete para manipular series de tiempo financieras.
  • simfinR: sitio de información financiera estandarizada.
  • httr: set de fórmulas para hablar con webs sencillamente.
  • jsonlite: paquete para convertir data frames de formato JSON a R.

Los primeros dos paquetes serán los principales. Lo importante es siempre tener en cuenta que existen diversos paquetes o el mismo Base R que pueden hacer las mismas tareas. Como consejo es elegir aquellos que son populares para poder obtener ayuda de la comunindad (o StackOverflow) y en el largo plazo no correr riesgos de quedar huéfanos. Otro consejo también es saber que limitaciones tienen, por ejemplo el tidyverse es super conocido pero su Syntax alla Python fácil de aprender y leer pierde eficiencia cuando se trabaja con muchos datos.

Ahora cargamos los datos (previamente instalados):

library(tidyquant)
library(tidyverse)
library(simfinR)
library(httr)
library(jsonlite)

Simfin

Esta es mi joya de la corona, ya que cuenta con una gran cantidad de información financiera estandarizada que otros paquetes como quantmod ya no pueden descargar de Yahoo! Finance desde que cambió las reglas de uso de su API.

Lo primero es hacerse un usuario para conseguir una api_key que servirá para descargar los datos. Una vez conseguida

my_api_key <- "apikey" # de simfin

Ahora si podemos ojear lo que hay adentro de Simfin. Para empezar vamos a descargar las compañias disponibles, luego para nuestro análisis usaremos el caso de AAPL.

# Descargar listado de empresas disponibles ne SimFin
info_companies <- simfinR_get_available_companies(my_api_key)
head(info_companies)
##    simId ticker                          name
## 1 171401   ZYXI                     ZYNEX INC
## 2 901704   ZYNE Zynerba Pharmaceuticals, Inc.
## 3 901866    ZVO                     Zovio Inc
## 4 994625    ZUO                     ZUORA INC
## 5  45730   ZUMZ                    Zumiez Inc
## 6 378251    ZTS                        Zoetis
# Buscamos el SimFin ID para Apple
stock <- info_companies %>% 
            filter(ticker == "AAPL") %>% 
                  select(simId) %>% 
                        as.numeric(.)
head(stock)
## [1] 111052

Importar Datos

En mi opinión este fue el principal motivo por el cúal migré, sin darme cuenta, hacia R. La facilidad con la que uno puede descargar - importar - limpar - manipular los datos y automatizarlo me permitió concentrarme en los análisis en vez de cada vez que quería estudiar algo o simplemente actualizarlo perder horas recontruyendo el proceso.

Parametrizar

Desde la programación hay que entender y ser honestos de cuando algo va a ser one shot y cuando una tarea recurrente. Suponiendo lo segundo, crearemos un super sencillo escáner de acciones. El primer paso es Parametrizar los argumentos de algunas funciones. Este apartado suele ser reiterativo, uno siempre encuentra algo que puede ser parametrizado a medida que conoce mejor los paquetes, las funciones y la tarea que está llevando a cabo.

En nuestro caso de hoy los parametros serán los siguientes, sumados a stock (donde guardamos el código Simfin de Apple):

# Preparamos los parametros para descargar los datos

## Empresa
stock # 111052 el de AAPL
## [1] 111052
## Tipo de Informe financiero "P/L"
statements = c("pl")
## Periodos 
period = c("Q1", "Q2", "Q3", "Q4")
## Horizontes
yrs = 2008:2019

Entonces la próxima vez que querás correr todo el análisis solo debemos modificar estos parámetros sin leer todo el código corriendo el riesgo de romper algo.

Descargar / Improtar los Datos

Para nuestro Escáner Simple elegiremos:
- Ingreso Neto
- Acciones
- Precio

Usamos simfinR_get_fin_statements() para descargar el P/L Statement.

# Descargar Datos de Informes Financieros
stock_pl <- simfinR_get_fin_statements(
                                      id_companies = stock,
                                      api_key = my_api_key,
                                      type_statements = statements,
                                      periods = period,
                                      years = yrs)

head(stock_pl)
## # A tibble: 6 x 13
##   company_name company_sector type_statement period  year ref_date   acc_name
##   <chr>        <chr>          <fct>          <fct>  <int> <date>     <chr>   
## 1 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Revenue 
## 2 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Sales &~
## 3 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Financi~
## 4 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Other R~
## 5 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Cost of~
## 6 APPLE INC    Computer Hard~ pl             Q3      2008 2008-09-30 Cost of~
## # ... with 6 more variables: acc_value <dbl>, tid <chr>, uid <chr>,
## #   parent_tid <chr>, display_level <chr>, check_possible <lgl>
tail(stock_pl)
## # A tibble: 6 x 13
##   company_name company_sector type_statement period  year ref_date   acc_name
##   <chr>        <chr>          <fct>          <fct>  <int> <date>     <chr>   
## 1 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Income ~
## 2 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Minorit~
## 3 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Net Inc~
## 4 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Preferr~
## 5 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Other A~
## 6 APPLE INC    Computer Hard~ pl             Q4      2019 2019-12-31 Net Inc~
## # ... with 6 more variables: acc_value <dbl>, tid <chr>, uid <chr>,
## #   parent_tid <chr>, display_level <chr>, check_possible <lgl>

Para buscar la cantidad de acciones sueltas en el mercado usaremos los paquetes httr y jsonlite. Si bien son temas “avanzados” quería mostrar que no es tan difícil y segundo que R puede trabajar con la web (contrario a lo que los fans de Python suelen creer).

# Descargar Datos de la cantidad de acciones 

url_stock <- paste("https://simfin.com/api/v1/companies/id/", stock, "/shares/aggregated", sep = "")

url <- paste(url_stock, "?api-key=",
             my_api_key,
             "&type=","common",
             "$measure", "period",
             "&ptype=", "TTM",
             "&fyear=", 2019,
             sep = "")

get_data <- httr::GET(url)

stock_shares <- jsonlite::fromJSON(content(get_data, "text"), flatten = TRUE)
head(stock_shares)
##               figure   type       measure       date period fyear       value
## 1 common-outstanding common point-in-time 2020-07-17   <NA>  <NA> 17102536000
## 2 common-outstanding common point-in-time 2020-06-27   <NA>  <NA> 17135756000
## 3 common-outstanding common point-in-time 2020-04-17   <NA>  <NA> 17337340000
## 4 common-outstanding common point-in-time 2020-03-28   <NA>  <NA> 17295948000
## 5 common-outstanding common point-in-time 2020-01-17   <NA>  <NA> 17501920000
## 6 common-outstanding common point-in-time 2019-12-28   <NA>  <NA> 17539836000

Por último descargamos el precio de la acción:

# Descargar Datos Precio
stock_price <- simfinR_get_price_data(id_companies = stock,
                                     api_key = my_api_key)
head(stock_price)
##     ref_date close_adj split_coef share_class_id share_class_name
## 1 2020-09-11    112.00       <NA>         824449    Common Shares
## 2 2020-09-10    113.49       <NA>         824449    Common Shares
## 3 2020-09-09    117.32       <NA>         824449    Common Shares
## 4 2020-09-08    112.82       <NA>         824449    Common Shares
## 5 2020-09-04    120.96       <NA>         824449    Common Shares
## 6 2020-09-03    120.88       <NA>         824449    Common Shares
##   share_class_type currency company_name
## 1           common      USD    APPLE INC
## 2           common      USD    APPLE INC
## 3           common      USD    APPLE INC
## 4           common      USD    APPLE INC
## 5           common      USD    APPLE INC
## 6           common      USD    APPLE INC

Limpiar y ordenar los Datos

Como mencionamos antes, todavía no estamos listos para realizar ningún análisis. Ahora estructuraremos los datos en modo que podamos trabajar con ellos, sea filtrando aquello que nos sirve como dandole la forma más adecuada para realizar cálculos.

Corroboramos que nuestra cuenta, Net Income este presente en los datos. Para ahorrar espacio usamos tail() pero para ver el elenco completo basta solo quitarla.

## Verificar que cuentas contiene el informe financiero
tail(unique(stock_pl$acc_name)) # 55 es Net Income
## [1] "Income (Loss) Including Minority Interest"  
## [2] "Minority Interest"                          
## [3] "Net Income"                                 
## [4] "Preferred Dividends"                        
## [5] "Other Adjustments"                          
## [6] "Net Income Available to Common Shareholders"

Para acotar nuestro data frame:
- filtramos la cuenta “Net Income” de la columna acc_name y omitimos los valores nulos.
- elegimos las columnas ref_date, acc_name y acc_value
- renombramos las columnas

# Preparar df para Net Income
net_income <- stock_pl %>% filter(acc_name == "Net Income",
                                 acc_value != 0) %>% 
                              select(ref_date, acc_name, acc_value) %>% 
                                    spread(acc_name, acc_value) %>% 
                                          rename(date = ref_date,
                                                 net_income = "Net Income")

head(net_income)
## # A tibble: 6 x 2
##   date       net_income
##   <date>          <dbl>
## 1 2008-09-30 1072000000
## 2 2008-12-31 2421000000
## 3 2009-03-31 2255000000
## 4 2009-06-30 1620000000
## 5 2009-09-30 1828000000
## 6 2009-12-31 2532000000

Nuestra segundo elección para el Escáner es:
- tomar los últimos 12 meses (Last Twelve Months = ltm)
- anualizar los retornos de cada trimestre

Para ello hacemos los calculos utilizando lag() para tomar los periodos anteriores y mutate() para guardar los resultados en nuevas columnas.

# Calcular el retorno anual y el retorno anualizado de cada trimestre
net_income %<>% mutate(ltm_net = net_income + 
                                 lag(net_income, 1) + 
                                 lag(net_income, 2) +
                                 lag(net_income, 3),
                       annual_net = net_income * 4) %>% 
                mutate(ltm_net = ifelse(is.na(ltm_net), net_income * 4, ltm_net))

head(net_income)
## # A tibble: 6 x 4
##   date       net_income    ltm_net  annual_net
##   <date>          <dbl>      <dbl>       <dbl>
## 1 2008-09-30 1072000000 4288000000  4288000000
## 2 2008-12-31 2421000000 9684000000  9684000000
## 3 2009-03-31 2255000000 9020000000  9020000000
## 4 2009-06-30 1620000000 7368000000  6480000000
## 5 2009-09-30 1828000000 8124000000  7312000000
## 6 2009-12-31 2532000000 8235000000 10128000000

Luego hacemos lo mismo para las acciones y el precio:

# Cantidad de Acciones
dil_shares <- stock_shares %>% 
  filter(figure == "common-outstanding-diluted",
         period %in% c("Q1", "Q2", "Q3", "Q4")) %>% 
  select(date, figure, value) %>% 
  spread(figure, value) %>% 
  rename(shares = "common-outstanding-diluted") %>% 
  mutate(date = ymd(date),
         shares = as.numeric(shares))

head(dil_shares)
##         date      shares
## 1 2008-06-30 25288676000
## 2 2008-09-30 25353216000
## 3 2008-12-31 25241832000
## 4 2009-03-31 25283804000
## 5 2009-06-30 25456480000
## 6 2009-09-30 25602444000
# Precio
price <- stock_price %>% 
  select(ref_date, close_adj) %>% 
  rename(date = "ref_date",
         price = "close_adj") %>% 
  arrange(date) %>% 
  filter(date >= "2009-03-31")

head(price)
##         date price
## 1 2009-03-31  3.24
## 2 2009-04-01  3.35
## 3 2009-04-02  3.48
## 4 2009-04-03  3.58
## 5 2009-04-06  3.66
## 6 2009-04-07  3.55

Se puede observar como redujimos el raw dataset y formatamos las tres variables de la misma manera, con la columna date en común para utilizar como nexo entre los tres data frames. Dado el descalabro reciente en el la bolsa y sobre todo en las grandes empresas TECH, focalizaremos el análisis hasta fines de 2019.

# Creamos el data frame unico
df <- net_income %>% 
  left_join(dil_shares, by = "date")

df <- price %>% 
  left_join(df, by = "date") %>% 
  na.locf() %>% # elimina los valarores faltantes (NA)
       # realizamos los cálculos
        mutate(ltm_eps = ltm_net/shares,
         annl_eps = annual_net/shares,
         pe_ltm = price/ltm_eps,
         pe_annl = price/annl_eps)

df %<>% filter(date < "2020-01-01") 

head(df)
##         date price net_income  ltm_net annual_net      shares   ltm_eps
## 1 2009-03-31  3.24  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
## 2 2009-04-01  3.35  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
## 3 2009-04-02  3.48  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
## 4 2009-04-03  3.58  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
## 5 2009-04-06  3.66  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
## 6 2009-04-07  3.55  2.255e+09 9.02e+09   9.02e+09 25283804000 0.3567501
##    annl_eps    pe_ltm   pe_annl
## 1 0.3567501  9.081987  9.081987
## 2 0.3567501  9.390326  9.390326
## 3 0.3567501  9.754727  9.754727
## 4 0.3567501 10.035035 10.035035
## 5 0.3567501 10.259282 10.259282
## 6 0.3567501  9.950943  9.950943

Reflexiones Preliminares

En esta primera parte del trabajo pudimos ver que realizar un script tiene varias ventajas:
- Facilmente Automatizable
- Documentación del procedimiento
- Manipulaicón de datos eficaz
- Centralización del trabajo

A mi parecer, de sólo pensar tener que realizar estos pasos en una hója de cálculos me quita las ganas de hacerlo. Pero sobre todo, la parametización del trabajo cambia la cadena de “Pivot Tables” o infinidad de “index + match” que ocupan lugar en las celdas para realizar cálculos, la condesanción de todo el proceso en pocas líneas de código ayuda a encontrar errores, mejorar el procedimiento y ESCALAR EL TRABAJO.

Visualización

Si hay un motivo por el cual R es conocido es por sus motores gráficos. Además de ser bellos, su versatilidad ayuda a realizar todo tipo de análisis exploratorio o crear un buen Stoy Telling.

Para empezar lo fundamental es ver el recorrido que ha tenido su precio.

# Precio
df %>% 
  ggplot(aes(date)) + 
  geom_line(aes(y = price), color = "#C73866") +
  labs(title = "Precio Apple",
       x = "",
       y = "Price (US$)") 

Pero como bien sabemos poco nos dice el precio actual y pasado sobre su performance futura. Entonces representaremos el LTM - P/E, es decir, el multiplo del precio en base a los ingresos netos de los últimos doce meses que calculamos anteriormente.

# Multiplo
df %>% 
  ggplot(aes(date)) + 
  geom_line(aes(y = pe_ltm), color = "#fba29d") +
  labs(title = "Múltiplo LTM-P/E Apple",
       x = "",
       y = "LTM P/E (x)") 

Podemos observar que, salvo el descalabro reciente, el ratio promedia aproximadamente 16x, oscilando entre 10 y 20.

# Tabla Multiplo LTM-P/E 
df %>% 
  summarise(Average = round(mean(pe_ltm),1),
            "Std. deviation" = round(sd(pe_ltm),1),
            Minimum = round(min(pe_ltm),1),
            Maximum = round(max(pe_ltm),1)) %>% 
  knitr::kable(caption = "Resumen Estadístico Apple LTM-P/E")
Resumen Estadístico Apple LTM-P/E
Average Std. deviation Minimum Maximum
15.9 4.2 7.8 25.9

Ahora sí estamos en grado de colocar ambos gráficos a la par y observar como relacionan los ciclos.

# Confrontación Precio y LTM-P/E
df %>% 
  ggplot(aes(date)) + 
  geom_line(aes(y = pe_ltm), color = "#fba29d") +
  geom_line(aes(y = price), color = "#C73866") +
  labs(title = "Precio vs Múltiplo LTM-P/E Apple",
       x = "",
       y = "")

Queda bastante en evidencia que a pesar de que el precio parecía desorbitante, enrealidad iba siempre de la mano de los ingresos. Otra medida y tal vez más informativa es el histograma, para darnos una idea de la frecuencia de los distintos nivels del múltiplo. Para ello superpondremos una distribución normal.

# Multiplo histograma 
df %>% 
  ggplot(aes(pe_ltm)) +
  geom_histogram(aes(y = ..density..), 
                 bins = 50,
                 fill = "#C2649A",
                 color = "white") +
  stat_function(fun = dnorm, 
                args = list(mean = mean(df$pe_ltm), 
                            sd = sd(df$pe_ltm)),
                lwd = 1.25,
                alpha = 0.7,
                color = "#FFBD71") +
  labs(title = "Histograma Apple multiplo LTM-P/E",
       x = "",
       y = "Density")

Para empezar, la distribución de nuestro LTM-P/E no es normal. Lo principal acá es tener esto en cuento a la hora de modelizar, ya que la normalidad es una asunción presente en gran parte de los tests y modelos.

Modelando

Quisieramos con todos estos datos ya procesados crear algún mecanismo para tomar decisiones. Salvando las distancias ya discutidas, crearemos un modelo lineal para averiguar si la valuación tiene algún efecto en el precio de la acción.

Próximo paso será crear un data frame con los retornos (ganancia/pérdida de tener la acción, no los ingresos de Apple) a 1, 3, 6 y 12 meses. Así como en pasado usamos la función lag() ahora analogamente utilizaremos lead(). Agregaremos los resultado al final de nuestro df pero lo guardaremos en uno nuevo llamado mult_mod. Recomiendo hacerlo sobre todo cuando los data sets tienen usos distintos.

# Crear el data frame
mult_mod <- df %>% 
  mutate(ret_1m = lead(price,22)/price - 1,
         ret_3m = lead(price, 66)/price -1,
         ret_6m = lead(price, 132)/price - 1,
         ret_12m = lead(price, 252)/price - 1)

La segunda parte para crear nuestro modelo es algo avanzada desde la programación, lo importante es la intución del poder de los loops.

Desde lo estadístico regresaremos los distintos retornos contra el múltiplo de valuación LTM-P/E con el que hemos estado trabajando.

# Correr el modelo
model_list <- list()
y_vars <- c("ret_1m", "ret_3m", "ret_6m", "ret_12m")

for(vars in y_vars){
  forms <- as.formula(paste(vars, "pe_ltm", sep = " ~ "))
  model_list[[vars]] <- lm(forms, mult_mod)
}

# Produce results
mod_out <- data.frame(mods = y_vars, 
                      size_eff = rep(0,4),
                      p_vals = rep(0,4),
                      rsqs = rep(0,4))

for(i in 1:length(model_list)){
  mod_out[i,2] <- as.numeric(summary(model_list[[i]])$coeff[2,1])
  mod_out[i,3] <- as.numeric(summary(model_list[[i]])$coeff[2,4])
  mod_out[i,4] <- as.numeric(summary(model_list[[i]])$r.squared)
 }

Los resultados de la regresión lineal son los siguientes:

mod_out %>% 
  mutate(mods = case_when(mods == "ret_1m" ~ "One-month",
                          mods == "ret_3m" ~ "Three-month",
                          mods == "ret_6m" ~ "Six-month",
                          mods == "ret_12m" ~ "One-year"),
         size_eff = round(size_eff, 3),
         p_vals = format(round(p_vals,3), nsmall = 3),
         rsqs = round(rsqs,3)*100) %>% 
  rename("Models" = mods,
         "Size effect" = size_eff,
         "P value" = p_vals,
         "R-squared (%)" = rsqs) %>% 
  knitr::kable(caption = "Resultado del Modelo", 
               align = c("l", "r", "r", "r"))
Resultado del Modelo
Models Size effect P value R-squared (%)
One-month -0.002 0.000 1.4
Three-month -0.008 0.000 6.2
Six-month -0.016 0.000 11.8
One-year -0.027 0.000 13.2

Antes de mirar el R2 y descartar todo, algo de sentido encontramos en la relación inversa entre el precio y el ratio (size effect of the ratio en inglés), ya que por cada punto que sube, la acción se vuelve relativamente más costosa en relación a su valor. Y los p-value son practicamente 0, es decir que la posibilidad de que los valores no son diferentes a valores aleatorios es menor al 5%.

Igualmente no es sorprendente que el R2 sea bajo, he hecho es reconfortante ya que es en este tipo de análisis cuando es muy bueno y relativamente simple…suele haber algún error.

Dado neustro primer insight, probemos a montar el modelo completo ahora. Usando la valuación para predecir el precio. Nos sirve entonves dividir nuestros datos disponibles y reservar algunos para entrenar y otros para testear a nuestro modelo.

Elegimos, arbitrareamente 2009-2015 para entrenar y 2016-2019 para testear el modelo entonces. Sin hacer ningún tipo de time-series cross-validation para manternlo simple el modelo es el siguiente:

# modelo predictivo: determinar los periodos
train <- mult_mod %>% filter(date < "2016-01-01")
test <- mult_mod %>% filter(date >= "2016-01-01", date <= "2018-12-31")

# RMSE (root-mean-squared-error) del modelo entrenado
train_mods <- data.frame(mods = y_vars, rmsqs = rep(0,4))
for(i in 1:length(y_vars)){
  forms <- as.formula(paste(y_vars[i], "pe_ltm", sep = " ~ "))
  lm_mod <- lm(forms, train)
  lm_pred <- predict(lm_mod, train)
  train_mods[i,2] <- sqrt(mean((lm_pred - train[,y_vars[i]])^2, na.rm = TRUE))
}

# RMSE en la prueba (tests)
test_mods <- data.frame(mods = y_vars, rmsqs = rep(0,4))
for(i in 1:length(y_vars)){
  forms <- as.formula(paste(y_vars[i], "pe_ltm", sep = " ~ "))
  lm_mod <- lm(forms, train)
  lm_pred <- predict(lm_mod, test)
  test_mods[i,2] <- sqrt(mean((lm_pred - test[,y_vars[i]])^2, na.rm = TRUE))
}

Veamos los resultados de nuestro modelo:

# Graph train and test RMSE
train_graph <- train_mods %>% 
  mutate(mods = case_when(mods == "ret_1m" ~ "1-m",
                          mods == "ret_3m" ~ "3-m",
                          mods == "ret_6m" ~ "6-m",
                          mods == "ret_12m" ~ "12-m"),
         rmsqs = round(rmsqs, 3)*100) %>% 
  ggplot(aes(reorder(mods,rmsqs), rmsqs)) +
  geom_bar(stat = "identity", fill = "#6AAB9C", color = "#6AAB9C") +
  labs(title = "RMSE M. Entrenado",
       x = "",
       y = "RMSE (%)") +
  scale_y_continuous(limits = c(0,40)) +
  geom_text(aes(y = rmsqs, label = rmsqs), 
            nudge_y = 2,
            size = 3.5)


test_graph <- test_mods %>% 
  mutate(mods = case_when(mods == "ret_1m" ~ "1-m",
                          mods == "ret_3m" ~ "3-m",
                          mods == "ret_6m" ~ "6-m",
                          mods == "ret_12m" ~ "12-m"),
         rmsqs = round(rmsqs, 3)*100) %>%
  ggplot(aes(reorder(mods,rmsqs), rmsqs)) +
  geom_bar(stat = "identity", fill = "#FFBD71", color = "#FFBD71") +
  labs(title = "RMSE Set de Test",
       x = "",
       y = "") +
  scale_y_continuous(limits = c(0,40)) +
  geom_text(aes(y = rmsqs, label = rmsqs), 
            nudge_y = 2,
            size = 3.5)

gridExtra::grid.arrange(train_graph, test_graph, nrow = 1)

Antes de seguir con algunas explicaciones, podemos decir que el modelo funcionó bien en el test ya que el error es menor. Otro detalle a tener en cuenta es que a mayor horizonte temporal mayor el error, lo cuál es lógico ya que l señal pierde fuerza en el tiempo. Pero a no emocionarse, si observamos el periodo de tiempo del test (elegido arbitrariamente, si claro…) la correlación es más mayor ahí que en el tiempo de entrenamiento.

Conclusión

LLegado el final pudimos observar el proceso completo, de la recopilación de los datos al modelo de decisión con R. Es claro que no es una tarea extremadamente sencilla, pero vale la pena animarse y si sirve de motivación para otros develar que se puede hacer análisis avanzado de forma automática desde tu casa sin gastar fortunas.

R es gratuito, aprendiendo a utilizar algunas herramientas al final solo se debe pagar por los datos, y ni siquiera tanto gracias a la creciente competencia. A mi me motivó para empezar el hecho que es facilmente reproducible y escalable, entonces si a este código quiero sumar más tests o observaciones no tengo que empezar de 0. Ni hablar de la velocidad de ejecución y capacidad de manejo de bases de datos mayores, pero un paso a la vez.

El Primero de muchos…espero

Para matenerte actualizado, suscribíte a mi newsletter desde mi sitio web o seguíme en twitter:

www.alessandroarg.com
twitter.com/Alessandro__ARG