Sobre el autor:

  1. Jimmy Cueva Ruesta

  2. E-mail: jcueva@usat.edu.pe

  3. LinkedIn: https://www.linkedin.com/in/jimmy-cueva-ruesta-30852811b/

Introducción

En el ámbito de las finanzas modernas, la gestión de portafolios y la medición del riesgo son elementos fundamentales para la toma de decisiones de inversión. Uno de los enfoques pioneros en la construcción eficiente de carteras es el Modelo de Portafolio de Markowitz, también conocido como teoría de la media-varianza. Este modelo, desarrollado por Harry Markowitz en 1952, propone una metodología cuantitativa para seleccionar una combinación óptima de activos que maximice el rendimiento esperado para un nivel dado de riesgo, o que minimice el riesgo para un nivel dado de rendimiento esperado.

No obstante, en entornos financieros reales donde prevalecen escenarios inciertos y extremos, se requiere complementar esta metodología con herramientas que permitan evaluar el riesgo de forma más robusta. En este contexto, las medidas de riesgo Value at Risk (VaR) y Conditional Value at Risk (CVaR) se han consolidado como estándares para cuantificar las pérdidas potenciales de una cartera bajo condiciones adversas de mercado.

El VaR estima la pérdida máxima esperada con un nivel de confianza dado (por ejemplo, 95% o 99%) en un horizonte temporal específico. El CVaR, por su parte, representa el valor promedio de las pérdidas que exceden el VaR, ofreciendo así una visión más conservadora del riesgo extremo.

En este proyecto se implementará, utilizando el lenguaje de programación R a través del entorno RStudio, la construcción de portafolios eficientes según la teoría de Markowitz, así como el cálculo y análisis del VaR y CVaR como medidas de riesgo de mercado. Esta combinación de técnicas permite no solo optimizar la rentabilidad, sino también gestionar de forma rigurosa la exposición al riesgo, alineando los objetivos financieros con los criterios prudenciales.

Paquetes fundamentales

library("data.table") #Manipulación eficiente de datos.
library("stringr")    #Procesamiento y manipulación de cadenas de texto.
library("ggplot2")    #Visualización de datos con gráficos de alta calidad.
library("quantmod")   #Para descargar datos financieros directamente de algunas fuentes abiertas, incluidas Yahoo Finance, Google Finance y Federal Reserve Economic Data (FRED) del Federal Reserve Bank of St. Louis.

Descargar data

getSymbols("AAPL", from= "2020-01-01", source= "yahoo") #Extraer datos de Yahoo
## [1] "AAPL"
barChart(AAPL) #Crear un gráfico de barras de velas (candlestick o barra OHLC) de una serie financiera. Muestra precios de apertura, cierre, máximo y mínimo (OHLC).

chartSeries(AAPL,theme = "white") #Crear un gráfico interactivo más detallado y flexible de series de tiempo financieras.

chartSeries(AAPL,theme = "white",TA = NULL) #Obtenemos el trama sin volumen

candleChart(AAPL,multi.col=TRUE,theme='white', subset = 'last 4 months') #Muestra visualmente la evolución del precio de una acción, resaltando cada vela como una sesión de trading (día, semana, etc.).

addMACD()

MACD (Moving Average Convergence Divergence) es un indicador de tendencia y momento muy usado en análisis técnico. Se basa en la diferencia entre dos medias móviles exponenciales (EMAs) de los precios y ayuda a identificar:Cambios en la fuerza, dirección, impulso y duración de una tendencia. Señales de compra/venta basadas en cruces de líneas y del eje cero.

¿Cómo se interpreta el MACD?

  1. Cruce MACD sobre la señal: Posible señal de compra.

  2. Cruce MACD debajo de la señal: Posible señal de venta.

  3. MACD por encima del 0: Tendencia alcista.

  4. MACD por debajo del 0: Tendencia bajista.

    Interpretación del gráfico:

    1. Gráfico de precios (velas japonesas)

    • Tendencia inicial bajista: Desde mediados de febrero hasta marzo, se observa una clara caída en el precio de AAPL, con varias velas grandes bajistas (rojas y negras), señalando presión vendedora fuerte.

    • Fase de consolidación: Después del mínimo cercano a los $175, el precio entra en una etapa de recuperación y consolidación. Se ve un rango de movimiento lateral con ligera tendencia al alza en abril y mayo.

    • Últimos días (cierre alrededor de $211): AAPL está mostrando velas pequeñas y consecutivas en la zona de los $210–$215, lo que puede señalar indecisión o resistencia.

    ### **MACD (Moving Average Convergence Divergence)**
    
    -   **MACD: -0.305**
    
        **Signal: -0.438**
    
        El **MACD está por encima de la línea de señal** y **ambas están subiendo**, aunque **todavía debajo de 0**, lo cual sugiere:
    
        **Reversión de tendencia bajista**: Es probable que el momentum bajista esté perdiendo fuerza.
    
    -   **Señal de compra moderada**, pero aún con cautela, porque:
    
        -   No han cruzado la línea cero (aún territorio bajista).
    
            -    El impulso alcista es débil (histograma pequeño y reciente).

Descarga masiva de acciones

v_symbols <- c("TSLA","C","AAPL","JPM","AMZN") #Crear un vector con los tickers bursátiles de las empresas que quieres analizar.

k <- 1 #Contador usado para identificar el primer ciclo del bucle for.
dt_acciones <- data.table() #Se inicia como tabla vacía para luego almacenar los datos combinados de todas las acciones
for (i in v_symbols){
  dt_base <- data.table(Ad(getSymbols(i, 
                                      from = "2018-01-01",
                                      src = "yahoo",
                                      auto.assign = F)),keep.rownames = T)
  if (k == 1){
    dt_acciones <- copy(dt_base)
  } else {
    dt_acciones <- merge(dt_acciones,dt_base,by = "index", all.x = T, all.y = T)
  }
  k <- k + 1
} #auto.assign = FALSE: hace que la función retorne directamente el objeto en vez de guardarlo como variable.Ad(...) extrae solo el precio de cierre ajustado. data.table(..., keep.rownames = T) convierte el objeto a un data.table y guarda las fechas como columna llamada "index". 

setnames(dt_acciones,"index","Fecha") #Renombra la columna "index" a "Fecha" en el data.table llamado dt_acciones.
colnames(dt_acciones) <- str_replace_all(colnames(dt_acciones),
                                         pattern = ".Adjusted",
                                         replacement = "")
dt_acciones

Retorno de las acciones

library(PerformanceAnalytics)
dt_retorno <- data.table(Return.calculate(dt_acciones),keep.rownames = T)
setnames(dt_retorno, "index","Fecha")

dt_retorno <- na.omit(dt_retorno)
dt_retorno

Gráficas Personalizadas

library(dygraphs)
datewindow <- c(min(dt_retorno$Fecha),max(dt_retorno$Fecha))
dygraph(dt_retorno)
dygraph(dt_acciones, main = "Price", group = "Stock") %>%
  dyRangeSelector(dateWindow = datewindow)

Optimización de Portafolio

library(fPortfolio)
library(timeSeries)
# Convertir los datos a series de tiempo
date1 <- as.Date(dt_retorno$Fecha,"%Y/%m/%d")
ts_retorno <- timeSeries(dt_retorno[,-1],date1)
PerformanceAnalytics::chart.CumReturns(ts_retorno,legend.loc = "topleft",
                                       main = "Standarized Stock Prices ")

# Especificaciones Iniciales 
showClass("fPFOLIOSPEC")
## Class "fPFOLIOSPEC" [package "fPortfolio"]
## 
## Slots:
##                                                         
## Name:      model portfolio     optim  messages      ampl
## Class:      list      list      list      list      list
formals(portfolioSpec)
## $model
## list(type = "MV", optimize = "minRisk", estimator = "covEstimator", 
##     tailRisk = list(), params = list(alpha = 0.05))
## 
## $portfolio
## list(weights = NULL, targetReturn = NULL, targetRisk = NULL, 
##     riskFreeRate = 0, nFrontierPoints = 50, status = NA)
## 
## $optim
## list(solver = "solveRquadprog", objective = c("portfolioObjective", 
##     "portfolioReturn", "portfolioRisk"), options = list(meq = 2), 
##     control = list(), trace = FALSE)
## 
## $messages
## list(messages = FALSE, note = "")
## 
## $ampl
## list(ampl = FALSE, project = "ampl", solver = "ipopt", protocol = FALSE, 
##     trace = FALSE)
Spec <- portfolioSpec()
# setTargetReturn(Spec) <- 0.05/252
efficientPortfolio(ts_retorno,Spec)
## 
## Title:
##  MV Efficient Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          minRisk 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Covariance Risk Budgets:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0009 0.0158 0.0367 0.0234 
## 
## Description:
##  Sat May 17 19:22:18 2025 by user: Jimmy Cueva
# Model Type 
setType(Spec) <- "MV"

# Set Matriz de Covarianzas
setEstimator(Spec) <- "covEstimator"
# setEstimator(Spec) <- "covMcdEstimator"

# Problema a Optimizar
setOptimize(Spec) <- "minRisk"
setOptimize(Spec) <- "maxReturn"
efficientPortfolio(ts_retorno,Spec)
## 
## Title:
##  MV Efficient Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          maxReturn 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Covariance Risk Budgets:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0009 0.0158 0.0367 0.0234 
## 
## Description:
##  Sat May 17 19:22:18 2025 by user: Jimmy Cueva
# Método de Optimización
setOptimize(Spec) <- "solveRquadprog"

efficientPortfolio(ts_retorno,Spec)
## 
## Title:
##  MV Efficient Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          solveRquadprog 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Covariance Risk Budgets:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.0000 0.0000 0.2762 0.4960 0.2278 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0009 0.0158 0.0367 0.0234 
## 
## Description:
##  Sat May 17 19:22:18 2025 by user: Jimmy Cueva
tangencyPortfolio(ts_retorno,Spec)
## 
## Title:
##  MV Tangency Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          minRisk 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.2774 0.0000 0.4243 0.2714 0.0269 
## 
## Covariance Risk Budgets:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.4814 0.0000 0.3435 0.1570 0.0182 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0013 0.0200 0.0447 0.0296 
## 
## Description:
##  Sat May 17 19:22:18 2025 by user: Jimmy Cueva
# Agregar Parámetros
setParams(Spec) <- 0.01
tangencyPortfolio(ts_retorno,Spec)
## 
## Title:
##  MV Tangency Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          minRisk 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.2774 0.0000 0.4243 0.2714 0.0269 
## 
## Covariance Risk Budgets:
##   TSLA      C   AAPL    JPM   AMZN 
## 0.4814 0.0000 0.3435 0.1570 0.0182 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0013 0.0200 0.0741 0.0493 
## 
## Description:
##  Sat May 17 19:22:18 2025 by user: Jimmy Cueva
# Constrains
constraints <- "LongOnly"

Optimización

RatioSharpeOptimization <- tangencyPortfolio(ts_retorno,Spec,constraints)
RatioSharpeOptimization@portfolio@portfolio$weights
##      TSLA         C      AAPL       JPM      AMZN 
## 0.2774131 0.0000000 0.4242600 0.2714191 0.0269078
EfficientPortfolio <- efficientPortfolio(ts_retorno,Spec,constraints)

MinimaVarianzaPortfolio <- minvariancePortfolio(ts_retorno,Spec,constraints)

Construir la frontera eficiente del Portafolio

FronteraEficiente <- portfolioFrontier(ts_retorno,Spec,constraints)

frontierPlot(FronteraEficiente)

frontierPlot(FronteraEficiente,
             col = c("orange","red"),
             pch = 19,
             labels = T)
sharpeRatioLines(FronteraEficiente,col = "green")
monteCarloPoints(FronteraEficiente,
                 mcSteps = 1000,
                 lwd = 0.75,
                 pch = 1,
                 cex = 0.25)
equalWeightsPoints(FronteraEficiente, col = "blue",pch = 20)

plot.new()
frontierPlot(FronteraEficiente,labels = T)

tailoredFrontierPlot(FronteraEficiente)

weightsPlot(FronteraEficiente)