Antes de comenzar con nuestra optimización de cartera, utilizando la teoría del portafolio de Markowitz, designaremos las configuraciones iniciales para el trabajo, comenzando con la verificación, instalación y carga de los paquetes a utilizar para el caso, luego de ello, al mismo tiempo hacemos unas configuraciones aparte para el entorno de trabajo propiamente tal (no es necesario para el markdown, sin embargo, para aquellos que quieran replicar el modelo, les sea útil), con esto todo
knitr::opts_chunk$set(echo = TRUE, include = TRUE, fig.align = "center",fig.width = 8.5, fig.height = 9, message = FALSE, warning = FALSE)
#############################################################################
# Portfolio Opimization
# Author: Matías Vicuña Cofré
#############################################################################
#******************** Configuraciones e Inicializacion *********************#
#############################################################################
{
# Librerías
{
# Lista de paquetes que quieres verificar
paquetes <- c("tidyverse","janitor","skimr","scales","ggthemes",
"tseries","fPortfolio","knitr","kableExtra","gplots",
"plotly", "lubridate","gplots","formattable","qrmtools")
# Verificar e instalar paquetes que no estén instalados
for (paquete in paquetes) {
if (!require(paquete, character.only = TRUE)) {
install.packages(paquete)
}
}
remove(paquetes)
# Activacion Librerias
suppressPackageStartupMessages({
library(tidyverse) # Nucleo de trabajo
library(janitor) # Ajustes para data frames
library(skimr) # Estadísticas de datos detalladas
library(scales) # Ajuste de escalas (principalmente en ggplot2)
library(ggthemes) # Temas para ggplot2
library(tseries) # Transformación y uso de formato "ts", "zoo", etc
library(fPortfolio) # Optimización de Cartera
library(knitr) # Ajustes para tablas (junto con formattable)
library(kableExtra) # Complemento de knitr
library(plotly) # Gráficos Interactivos
library(lubridate) # Ajustes de Fechas
library(gplots) # Mapa de calor
library(formattable) # Tablas HTML
library(qrmtools) # Para calculos de rendimientos
})
}
# Configuraciones Entorno de Trabajo
{
# Limpiamos el entorno de trabajo
rm(list = ls())
# Limpiamos la Memoria (RAM)
gc(reset = TRUE)
# Limpiamos la consola
cat("\014")
# De número científico a natural
options(scipen = 999)
}
}
Luego de las configuraciones, generamos la primera muestra de datos a
utilizar, usando el paquete tseries
, como primer ejemplo,
utilizaremos el índice S&P 500 Dow Jones, al cuál observaremo su
precio histórico desde Enero del 2000 hasta el 31 de diciembre del 2023,
usaremos su ticket de Yahoo Finance (^GSPC) (veáse más detalle del
índice aquí).
Junto con ello, usamos el paquete skimr
para realizar un
análisis estadístico básico de la serie con los precios históricos,
teniendo su media, percentiles y demás detalles.
# Preambulo (Rango de fechas a considerar para medición)
start_port <- as.Date("2000-01-01")
end_port <- as.Date("2023-12-31")
quote_port <- "AdjClose"
# Carga serie S&P 500.
SP500 <- get.hist.quote(
instrument = "^GSPC",
start = start_port,
end = end_port,
quote = quote_port,
quiet = TRUE
)
# Renombramos la base original
colnames(SP500) <- "SP500"
# Ajustamos la muestra
SP_500 <- SP500 %>%
as_tibble() %>%
mutate(Fecha = as_date(time(SP500))) %>%
rename(Precio = SP500)
# Generamos tabla resumen de precios históricos
formattable(skim_without_charts(SP_500$Precio))
skim_type | skim_variable | n_missing | complete_rate | numeric.mean | numeric.sd | numeric.p0 | numeric.p25 | numeric.p50 | numeric.p75 | numeric.p100 |
---|---|---|---|---|---|---|---|---|---|---|
numeric | data | 0 | 1 | 1973.587 | 1061.336 | 676.53 | 1191.38 | 1457.34 | 2584.84 | 4796.56 |
Ya teniendo la serie cargada, generamos ahora un pequeño gráfico que entrega el rendimiento histórico desde el 2000 al 2023 del precio ajustado del indicador.
Para ello, hacemos uso de un recursos super relevantes para el
análisis y visualización de datos: ggplot2
, con este
paquete y usando de adicional para la interacción el paquete
plotly
, tenemos un gráfico que nos entrega en detalle e
intuitivamente el desarrollo de este mercado Norteamericano.
# Generamos el gráfico
int_plot_SP500 <- SP_500 %>%
ggplot(mapping = aes(x = Fecha,
y = Precio)) +
geom_line(aes(y = Precio),
color = "blue4") +
labs(x = "Años",
y = "Precio $USD",
title = "Precio Ajustado S&P 500") +
theme_minimal() +
theme(axis.title.x = element_text(size = 12,
face = "bold"),
axis.title.y = element_text(size = 12,
face = "bold"),
plot.title = element_text(size = 20,
face = "bold",
hjust = 0.5))
# Generamos el gráfico de forma interactiva
ggplotly(int_plot_SP500)
Además de tener los datos de este índice de mercado norteamericano, necesitamos tener un instrumento que, siguiendo la teoría de Markowitz, sea el que simule nuestro rendimiento “libre de riesgo” (risk free), de este modo, y siguiendo la dinámica de este ejercicio, usaremos el Treasury Yield 30 Years, el cuál será nuesrtro indicador histórico que seguirá la idea de tasa “segura” de ganar. Este indicador, al igual que todos los demás, son recuperados directamente desde
# Cargamos los datos desde Yahoo Finance
riskfree <- get.hist.quote(instrument = "^TYX",
start = start_port,
end = end_port,
quote = quote_port,
quiet = TRUE
)
# Cambiamos el nombre de la columna
colnames(riskfree) <- "RiskFree"
# Ajustamos la muestra
risk_free <- riskfree %>%
as_tibble() %>%
mutate(Fecha = as_date(time(riskfree))) %>%
rename(Tasa = RiskFree)
# Estadística Descriptiva de los datos
formattable(skim_without_charts(risk_free$Tasa))
skim_type | skim_variable | n_missing | complete_rate | numeric.mean | numeric.sd | numeric.p0 | numeric.p25 | numeric.p50 | numeric.p75 | numeric.p100 |
---|---|---|---|---|---|---|---|---|---|---|
numeric | data | 1329 | 0.8194048 | 3.832461 | 1.1775 | 0.937 | 2.93125 | 3.8415 | 4.781 | 6.742 |
# Generamos el gráfico
int_plot_risk_free <- risk_free %>%
na.omit() %>%
ggplot(mapping = aes(x = Fecha,
y = Tasa)) +
geom_line(aes(y = Tasa),
color = "red4") +
labs(x = "Años",
y = "Tasa rendimiento",
title = "Bono del Tesoro de EE.UU. a 30 años") +
theme_minimal() +
theme(axis.title.x = element_text(size = 12,
face = "bold"),
axis.title.y = element_text(size = 12,
face = "bold"),
plot.title = element_text(size = 18,
face = "bold",
hjust = 0.5))
# Generamos el gráfico de forma interactiva
ggplotly(int_plot_risk_free)
Siguiendo con esta ídea, ahora generaremos una cartera, la cuál se compondrá de a lo menos 7 tipos de empresas, para este ejemplo, usaremos empresas que cotizan en la bolsa de Nueva York, especificamente serán las siguientes:
Con este listado de empresas, ahora construiremos nuestra base.
# Datos Previos
tickets <- c("KO", "PEP", "WMT", "TGT", "AAPL", "MSFT", "AMZN")
portafolio <- tibble()
# Iteración de Recopilación de Series
for (i in seq_along(tickets)) {
data <- get.hist.quote(
instrument = tickets[i],
start = start_port,
end = end_port,
quote = "Adjusted",
quiet = TRUE
)
# Verifica si el objeto portafolio está vacío
if (nrow(portafolio) == 0) {
portafolio <- data
} else {
# Combina las series temporales por columna
portafolio <- cbind(portafolio, data)
}
}
# Asigna nombres de columna al portafolio
colnames(portafolio) <- tickets
# Extraemos la fecha de la base (para gráficar)
Fecha <- time(portafolio)
# Imprime el resultado
rmarkdown::paged_table((cbind(format(Fecha, "%d-%m-%Y"),as_tibble(portafolio))))
Teniendo ya conformada la cartera, visualizamos la evolución histórica de cada serie
# Creamos un reshape "wide to long" para facilitar la graficación
portafolio_long <- cbind(Fecha,as_tibble(portafolio)) %>%
gather(key = "Ticket", value = "Precio", -Fecha)
# Creamos el gráfico de series de tiempo
int_plot_portafolio <- ggplot(portafolio_long,
aes(x = Fecha, y = Precio, group = Ticket, color = Ticket)) +
geom_line(aes(y = Precio)) +
facet_wrap(~Ticket, scales = "free_y") +
labs(title = "Evolución Histórica",
x = "Fecha", y = "Precio $USD") +
scale_color_calc() +
theme_minimal() +
theme(axis.title.x = element_text(size = 15, face = "bold"),
axis.title.y = element_text(size = 15, face = "bold"),
plot.title = element_text(size = 20, face = "bold", hjust = 0.5),
axis.text.x = element_text(face = "bold", vjust = 1),
legend.position = "none")
# Agregamos la interacción
ggplotly(int_plot_portafolio)
Posterior a esto, crearemos los beneficios de cada serie (incluida el S&P 500 y el bono del tesoro), añadiendo además una normalización logartímica para reescalar la muestra.
Retornos para cada serie
# Calculamos los rendimientos de la cartera
returns_portafolio <- round(returns(portafolio, method = "logarithmic"), digits = 4)
# Calculamos los rendimientos del indice S&P 500
aux_SP500 <- returns(SP500, method = "logarithmic")
returns_SP500 <- aux_SP500 %>%
as_tibble() %>%
rename(SP500 = V1)
returns_SP500 <- xts::reclass(returns_SP500,match.to = aux_SP500)
# Calculamos los rendimientos del Bono del Tesoro
aux_riskfree <- returns(riskfree, method = "logarithmic")
returns_riskfree <- aux_riskfree %>%
as_tibble() %>%
rename(riskfree = V1)
returns_riskfree <- xts::reclass(returns_riskfree,match.to = aux_riskfree)
# Imprimimos los resultados
aux_data_printed <- merge(returns_portafolio,round(returns_riskfree,digits = 4),round(returns_SP500, digits = 4))
Date <- time(aux_data_printed)
Date <- format(Date, "%d-%m-%Y")
rmarkdown::paged_table(cbind(Date,as_tibble(aux_data_printed)))
Gráficamente esto se ve de la siguiente manera:
# Ajustes a la muestra (formateo de zoo a tibble)
data_long <- merge(returns_portafolio,returns_SP500,returns_riskfree)
Time <- time(data_long)
# Creamos un reshape "wide to long" para facilitar la graficación
data_long <- cbind(Time,as_tibble(data_long)) %>%
gather(key = "Serie", value = "Precio", -Time)
# Creamos el gráfico de series de tiempo
int_plot_data_long <- ggplot(data_long,
aes(x = Time,
y = Precio,
group = Serie,
color = Serie)) +
geom_line(aes(y = Precio)) +
facet_wrap(~Serie, scales = "free_y") +
labs(title = "Variación Precios Histórica Normalizada",
x = "Fecha", y = "Precio $USD") +
scale_color_calc() +
theme_minimal() +
theme(axis.title.x = element_text(size = 13, face = "bold"),
axis.title.y = element_text(size = 13, face = "bold"),
plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
axis.text.x = element_text(face = "bold", vjust = 1),
legend.position = "none")
# Agregamos la interacción
ggplotly(int_plot_data_long)
Posterior a tener las variaciones diarias de cada serie, procedemos a generar los rendimientos promedios, desviación, varianza, covarianza y correlación de las series respectivamente
# Promedios Rendimientos
promedio <- round(sapply(returns_portafolio,mean), digits = 6)
# Varianza Rendimientos
varianza <- round(sapply(returns_portafolio, var), digits = 6)
# Desviación Rendimientos
desviacion <- round(sapply(returns_portafolio, sd), digits = 6)
# Matriz de Varianzas y Covarianzas
covarianzas <- round(cov(returns_portafolio), digits = 6)
# Matriz de Correlación
correlacion <- round(cor(returns_portafolio) * 100, 2)
# Imprimimos los resultados de Promedio, Varianza y Desviación
recuadro <- rbind(promedio,varianza,desviacion)
formattable(as_tibble(recuadro, rownames = NA))
KO | PEP | WMT | TGT | AAPL | MSFT | AMZN | |
---|---|---|---|---|---|---|---|
promedio | 0.000232 | 0.000351 | 0.000212 | 0.000303 | 0.000899 | 0.000388 | 0.000585 |
varianza | 0.000171 | 0.000159 | 0.000220 | 0.000429 | 0.000656 | 0.000371 | 0.000971 |
desviacion | 0.013067 | 0.012605 | 0.014817 | 0.020717 | 0.025609 | 0.019251 | 0.031166 |
Teniendo la información estadística relevante, ahora finalizamos el análisis previo con un mapa de calor, el cuál visualiza la correlación que posee cada una de las series de nuestra muestra, siendo los sectores más rojizos aquellos que se alejan de la correlación positiva (\(\rho<1\)), mientras que las zonas más amarillas o anaranjadas corresponden a una correlación entre ellas mayor (\(\rho\sim1\)). Para una mayor comprensión y simplicidad del análisis, transformamos en la chunck anterior la correlación entre las series, siendo estas multiplicadas por 100, de este modo en el gráfico el valor que representa cada recuadro corresponde a su correlación porcentual.
generate_heat_map <- function(correlationMatrix, title)
{
heatmap.2(
x = correlationMatrix,
cellnote = correlationMatrix,
main = title,
symm = TRUE,
dendrogram = "none",
Rowv = FALSE,
trace = "none",
density.info = "none",
notecol = "black",
key.title = "Nivel Correlación",
key.xlab = ""
)
}
generate_heat_map(correlacion, "Mapa Correlación Cartera")
Con este análisis previo tenemos la siguiente información
# Especificaciones del Modelo
markov <- portfolioSpec() # Generamos las especificaciones
constraints <- "Shorts" # Determinamos la restricción a "venta corta"
# Determinamos la tasa libre de riesgo del Modelo
setRiskFreeRate(markov) <- as.numeric(mean(returns_riskfree, na.rm = TRUE))
# Seteamos el número de portafolios a simular en la frontera eficiente
setNFrontierPoints(markov) <- 50
# Calculamos el Modelo de Markowitz de Mínima Varianza
Frontera <-
portfolioFrontier(as.timeSeries(returns_portafolio),
spec = markov,
constraints)
# Imprimimos los resultados
print(Frontera)
##
## Title:
## MV Portfolio Frontier
## Estimator: covEstimator
## Solver: solveRquadprog
## Optimize: minRisk
## Constraints: Short
## Portfolio Points: 5 of 50
##
## Portfolio Weights:
## KO PEP WMT TGT AAPL MSFT AMZN
## 1 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000
## 13 0.2116 0.4272 0.1768 0.0027 0.1307 0.0221 0.0288
## 25 0.0000 0.6082 0.0019 0.0000 0.3394 0.0000 0.0505
## 37 0.0000 0.3088 0.0000 0.0000 0.6497 0.0000 0.0415
## 50 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000
##
## Covariance Risk Budgets:
## KO PEP WMT TGT AAPL MSFT AMZN
## 1 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000
## 13 0.1889 0.4182 0.1553 0.0025 0.1798 0.0223 0.0331
## 25 0.0000 0.4176 0.0009 0.0000 0.5281 0.0000 0.0534
## 37 0.0000 0.0863 0.0000 0.0000 0.8830 0.0000 0.0307
## 50 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 1 0.0002 0.0148 0.0346 0.0213
## 13 0.0004 0.0108 0.0253 0.0162
## 25 0.0005 0.0133 0.0309 0.0199
## 37 0.0007 0.0184 0.0418 0.0271
## 50 0.0009 0.0256 0.0574 0.0371
##
## Description:
## Fri Feb 9 04:12:51 2024 by user: matias
# Generamos el Gráfico de nuestra simulación
frontierPlot(Frontera)
grid()
tangencyPoints(Frontera,
pch = 19,
col = "red",
cex = 2)
tangencyLines(Frontera,
col = "grey",
pch = 19,
cex = 2)
minvariancePoints(Frontera,
col = "blue",
pch = 19,
cex = 2)
monteCarloPoints(Frontera,
mCsteps = 10000,
col = "#0098D5",
cex = 0.001)
Copyright © 2024 Matías Vicuña Cofré, Todos los Derechos Reservados.