El presente artículo constituye una guía metodológica exhaustiva para el análisis cuantitativo de activos financieros utilizando datos de acceso público desde Yahoo Finance. Se presenta un flujo de trabajo completo que abarca desde la adquisición programática de datos hasta técnicas avanzadas de modelado estadístico, incluyendo análisis de series temporales, cálculo de métricas de riesgo-retorno, construcción de matrices de correlación, y estimación de modelos de valoración de activos.
La metodología implementada utiliza el lenguaje de programación R y paquetes especializados del ecosistema quantmod, garantizando reproducibilidad completa y escalabilidad a múltiples contextos de aplicación. Se presentan ejemplos detallados con activos representativos del mercado estadounidense, aunque el marco es directamente generalizable a cualquier mercado o clase de activo disponible en la plataforma Yahoo Finance.
El enfoque adoptado enfatiza tanto el rigor metodológico como la interpretabilidad práctica, proporcionando no solo implementaciones técnicas sino también explicaciones conceptuales profundas de cada etapa del proceso analítico. Esta dualidad hace que el documento sea valioso tanto para investigadores académicos como para practitioners en instituciones financieras.
Palabras clave: Análisis Financiero Cuantitativo, Yahoo Finance, Series Temporales Financieras, Gestión de Riesgo, R Programming, Volatilidad Estocástica, Capital Asset Pricing Model, Optimización de Portafolios
Clasificación JEL: C58 (Modelado Financiero), G11 (Elección de Portafolio), G12 (Valoración de Activos), C63 (Métodos Computacionales), G17 (Análisis Financiero)
El análisis cuantitativo de activos financieros ha experimentado una transformación paradigmática durante las últimas dos décadas, impulsada por la confluencia de tres factores fundamentales: (i) la democratización del acceso a datos financieros de alta calidad, (ii) el desarrollo de herramientas computacionales de código abierto cada vez más sofisticadas, y (iii) la creciente sofisticación metodológica en econometría financiera (Perlin, 2020; Tsay, 2010).
Yahoo Finance ha emergido como una de las fuentes de datos más ampliamente utilizadas tanto en investigación académica como en práctica profesional, ofreciendo series temporales históricas de precios, volúmenes de negociación, y eventos corporativos para miles de instrumentos financieros distribuidos globalmente. La plataforma proporciona datos ajustados por splits accionarios y dividendos, facilitando análisis de retornos comparables inter-temporalmente (Campbell et al., 1997).
La capacidad de acceder, procesar, validar y analizar programáticamente estos datos constituye una competencia fundamental para investigadores en finanzas empíricas, analistas cuantitativos en instituciones de inversión, gestores de portafolios, traders algorítmicos, y científicos de datos especializados en el dominio financiero. Esta competencia trasciende el mero conocimiento técnico, requiriendo comprensión profunda de fundamentos económico-financieros, propiedades estadísticas de series temporales, y limitaciones inherentes a datos observacionales de mercados financieros.
Este trabajo persigue los siguientes objetivos específicos, organizados en orden de complejidad creciente:
Establecer un protocolo robusto para la adquisición y validación de datos financieros desde Yahoo Finance, incluyendo manejo de errores, detección de anomalías, y tratamiento de valores faltantes
Presentar técnicas fundamentales de análisis exploratorio aplicadas a series temporales financieras, con énfasis en visualización efectiva y estadísticas descriptivas relevantes
Implementar métricas estándar de evaluación de desempeño ajustado por riesgo, incluyendo Sharpe Ratio, Information Ratio, y medidas de downside risk
Desarrollar metodologías para análisis de correlaciones dinámicas y construcción de matrices de covarianza, fundamentales para diversificación de portafolios
Introducir modelos econométricos para análisis de retornos, incluyendo regresión lineal múltiple y estimación de sensibilidades sistemáticas (betas)
Presentar aplicaciones de teoría moderna de portafolios, incluyendo construcción de fronteras eficientes y optimización media-varianza
Garantizar reproducibilidad completa mediante código exhaustivamente documentado, auto-contenido, y estructurado según mejores prácticas de programación científica
Este trabajo contribuye a la literatura existente en tres dimensiones principales:
Dimensión Pedagógica: Proporciona una guía paso a paso excepcionalmente detallada, accesible tanto para estudiantes avanzados de pregrado como para investigadores establecidos sin formación específica en programación financiera. Cada concepto se introduce con fundamentos teóricos antes de su implementación práctica.
Dimensión Metodológica: Integra en un único framework coherente técnicas distribuidas típicamente en múltiples fuentes especializadas, facilitando adopción integral de mejores prácticas. Se enfatiza particularmente la validación de datos y diagnóstico de supuestos, aspectos frecuentemente omitidos en tutoriales convencionales.
Dimensión Aplicada: Demuestra aplicaciones directas a problemas reales de inversión y gestión de riesgo, ilustrando cómo traducir outputs técnicos en insights accionables para toma de decisiones financieras.
El artículo se organiza siguiendo la secuencia lógica de un proyecto de análisis financiero cuantitativo:
El lenguaje R ha consolidado su posición como herramienta dominante para análisis financiero cuantitativo debido a su extenso ecosistema de paquetes especializados, capacidades gráficas avanzadas, y sintaxis expresiva para manipulación de series temporales. La elección de R sobre alternativas como Python, MATLAB, o Julia se justifica por su orientación específica hacia estadística y econometría, así como su adopción generalizada en academia e industria financiera.
El análisis presentado requiere los siguientes paquetes, cada uno cumpliendo roles específicos:
# Suprimir mensajes de inicio para limpieza de output
suppressPackageStartupMessages({
library(quantmod)
library(xts)
library(zoo)
library(PerformanceAnalytics)
library(corrplot)
library(TTR)
library(knitr)
})
# Configuración global de sesión
options(
scipen = 999, # Desactivar notación científica
digits = 4, # Precisión decimal para output numérico
"getSymbols.warning4.0" = FALSE, # Suprimir advertencias de quantmod
"getSymbols.yahoo.warning" = FALSE
)
cat("✓ Librerías cargadas exitosamente\n")## ✓ Librerías cargadas exitosamente
## ✓ Configuración de sesión completada
La reproducibilidad científica requiere documentación exhaustiva del entorno computacional. A continuación se presenta información completa de la configuración utilizada:
## ══════════════════════════════════════════════════════════════
## INFORMACIÓN DEL ENTORNO COMPUTACIONAL
## ══════════════════════════════════════════════════════════════
## R versión: 4.4.0
## Sistema operativo: Windows 10 x64
## Arquitectura: x86-64
## Fecha de análisis: 06 de febrero de 2026
## Zona horaria: America/Lima
## VERSIONES DE PAQUETES CRÍTICOS:
## quantmod: 0.4.27
## xts: 0.14.0
## PerformanceAnalytics: 2.0.8
## ══════════════════════════════════════════════════════════════
Yahoo Finance proporciona acceso a datos financieros mediante una
interfaz web no oficial pero estable, accesible a través del paquete
quantmod. La arquitectura subyacente utiliza solicitudes
HTTP a endpoints específicos, retornando datos en formato CSV que son
automáticamente parseados y convertidos a objetos xts.
Datos Disponibles:
Los precios ajustados son cruciales para análisis de series temporales. Sin ajustes, eventos como stock splits crean discontinuidades artificiales:
Ejemplo: Un split 2:1 reduce el precio a la mitad instantáneamente, pero el valor económico permanece constante. El precio ajustado retrospectivamente corrige todo el historial para mantener continuidad económica.
Fórmula de Ajuste: \[P_{adjusted,t} = P_{close,t} \times \prod_{s>t} \text{AdjustmentFactor}_s\]
donde los factores de ajuste capturan splits y dividendos.
# Portafolio diversificado sectorialmente
simbolos <- c(
# Tecnología (40%)
"AAPL", # Apple Inc. - Hardware/servicios
"MSFT", # Microsoft - Software/cloud
"GOOGL", # Alphabet - Internet/publicidad
"AMZN", # Amazon - E-commerce/cloud
# Automotriz/Energía (10%)
"TSLA", # Tesla - Vehículos eléctricos
# Servicios Financieros (20%)
"JPM", # JPMorgan Chase - Banca
"V", # Visa - Procesamiento de pagos
# Salud (10%)
"JNJ", # Johnson & Johnson - Farmacéutica/dispositivos
# Consumo Básico (20%)
"WMT", # Walmart - Retail
"PG" # Procter & Gamble - Bienes de consumo
)
# Periodo de análisis
fecha_inicio <- "2019-01-01" # Post-corrección de mercado 2018
fecha_fin <- Sys.Date() # Hasta la fecha actual
# Reporte de parámetros
cat("══════════════════════════════════════════════════════════════\n")## ══════════════════════════════════════════════════════════════
## PARÁMETROS DE DESCARGA DE DATOS
## ══════════════════════════════════════════════════════════════
## Número de activos: 10
## Símbolos: AAPL, MSFT, GOOGL, AMZN, TSLA, JPM, V, JNJ, WMT, PG
## PERIODO DE ANÁLISIS:
## Inicio: 01/01/2019
## Fin: 06/02/2026
## Duración: 2593 días calendario
## Días aprox trading: 1852
## ══════════════════════════════════════════════════════════════
Justificación de Selección: El portafolio combina empresas de alta capitalización distribuidas en sectores clave de la economía estadounidense, permitiendo análisis de correlaciones intra e inter-sectoriales, efectos de diversificación, y sensibilidades diferenciales a factores macroeconómicos.
##
## 🔄 INICIANDO PROCESO DE DESCARGA DE DATOS
## Fuente: Yahoo Finance API
## Método: quantmod::getSymbols()
# Función robusta con manejo de excepciones
descargar_activo <- function(simbolo, from, to) {
tryCatch({
cat(" ├─ Descargando", simbolo, "...")
# Descarga con timeout y reintentos implícitos
datos <- getSymbols(
Symbols = simbolo,
src = "yahoo",
from = from,
to = to,
auto.assign = FALSE,
warnings = FALSE
)
# Validación básica
if(nrow(datos) == 0) {
cat(" ✗ Sin datos\n")
return(NULL)
}
cat(" ✓ Completado (", nrow(datos), "obs)\n")
return(datos)
}, error = function(e) {
cat(" ✗ Error:", conditionMessage(e), "\n")
return(NULL)
})
}
# Descarga paralela conceptualmente (secuencial en práctica para estabilidad)
lista_datos <- lapply(simbolos, descargar_activo,
from = fecha_inicio, to = fecha_fin)## ├─ Descargando AAPL ... ✓ Completado ( 1784 obs)
## ├─ Descargando MSFT ... ✓ Completado ( 1784 obs)
## ├─ Descargando GOOGL ... ✓ Completado ( 1784 obs)
## ├─ Descargando AMZN ... ✓ Completado ( 1784 obs)
## ├─ Descargando TSLA ... ✓ Completado ( 1784 obs)
## ├─ Descargando JPM ... ✓ Completado ( 1784 obs)
## ├─ Descargando V ... ✓ Completado ( 1784 obs)
## ├─ Descargando JNJ ... ✓ Completado ( 1784 obs)
## ├─ Descargando WMT ... ✓ Completado ( 1784 obs)
## ├─ Descargando PG ... ✓ Completado ( 1784 obs)
names(lista_datos) <- simbolos
# Análisis de resultados de descarga
descargas_exitosas <- !sapply(lista_datos, is.null)
cat("\n══════════════════════════════════════════════════════════════\n")##
## ══════════════════════════════════════════════════════════════
## RESUMEN DE DESCARGA
## ══════════════════════════════════════════════════════════════
## Símbolos solicitados: 10
cat("Descargas exitosas: ", sum(descargas_exitosas), " (",
round(mean(descargas_exitosas)*100, 1), "%)\n")## Descargas exitosas: 10 ( 100 %)
## Descargas fallidas: 0
if(sum(!descargas_exitosas) > 0) {
cat("\n⚠ ADVERTENCIA: Símbolos con errores:\n")
cat(" ", paste(simbolos[!descargas_exitosas], collapse = ", "), "\n")
cat(" Estos serán excluidos del análisis subsecuente.\n")
}
cat("══════════════════════════════════════════════════════════════\n\n")## ══════════════════════════════════════════════════════════════
# Filtrar solo descargas exitosas
lista_datos <- lista_datos[descargas_exitosas]
simbolos <- simbolos[descargas_exitosas]
cat("✓ Proceso de descarga completado\n")## ✓ Proceso de descarga completado
## Activos disponibles para análisis: 10
# Examinar en detalle el primer activo
primer_activo <- simbolos[1]
datos_ejemplo <- lista_datos[[primer_activo]]
cat("══════════════════════════════════════════════════════════════\n")## ══════════════════════════════════════════════════════════════
## ANATOMÍA DE OBJETO XTS - Ejemplo: AAPL
## ══════════════════════════════════════════════════════════════
## CARACTERÍSTICAS ESTRUCTURALES:
## Clase: xts, zoo
## Dimensiones: 1784 filas × 6 columnas
## Tamaño en memoria: 100 Kb
## INFORMACIÓN TEMPORAL:
## Primer registro: 2019-01-02
## Último registro: 2026-02-05
## Periodicidad: daily
## Total de puntos: 1784
## COLUMNAS (Variables):
## • AAPL.Open
## • AAPL.High
## • AAPL.Low
## • AAPL.Close
## • AAPL.Volume
## • AAPL.Adjusted
##
## ══════════════════════════════════════════════════════════════
## MUESTRA DE DATOS - Primeras 5 observaciones:
##
##
## | AAPL.Open| AAPL.High| AAPL.Low| AAPL.Close| AAPL.Volume| AAPL.Adjusted|
## |---------:|---------:|--------:|----------:|-----------:|-------------:|
## | 38.72| 39.71| 38.56| 39.48| 148158800| 37.54|
## | 35.99| 36.43| 35.50| 35.55| 365248800| 33.80|
## | 36.13| 37.14| 35.95| 37.06| 234428400| 35.24|
## | 37.17| 37.21| 36.47| 36.98| 219111200| 35.16|
## | 37.39| 37.96| 37.13| 37.69| 164101200| 35.83|
##
##
## MUESTRA DE DATOS - Últimas 5 observaciones:
##
##
## | AAPL.Open| AAPL.High| AAPL.Low| AAPL.Close| AAPL.Volume| AAPL.Adjusted|
## |---------:|---------:|--------:|----------:|-----------:|-------------:|
## | 255.2| 261.9| 252.2| 259.5| 92443400| 259.5|
## | 260.0| 270.5| 259.2| 270.0| 73913400| 270.0|
## | 269.2| 271.9| 267.6| 269.5| 64394700| 269.5|
## | 272.3| 279.0| 272.3| 276.5| 90545700| 276.5|
## | 278.1| 279.5| 273.2| 275.9| 52716800| 275.9|
Interpretación de Columnas:
Los datos financieros de fuentes públicas presentan diversos desafíos de calidad:
Una limpieza rigurosa es prerequisito para cualquier análisis subsecuente válido.
## ══════════════════════════════════════════════════════════════
## ANÁLISIS Y TRATAMIENTO DE VALORES FALTANTES
## ══════════════════════════════════════════════════════════════
# Función de imputación con múltiples estrategias
imputar_faltantes <- function(datos) {
# Registrar estado inicial
n_inicial <- sum(is.na(datos))
# Estrategia 1: Interpolación lineal para gaps pequeños
datos_imputados <- na.approx(datos, na.rm = FALSE)
# Estrategia 2: Last Observation Carried Forward para edges
datos_imputados <- na.locf(datos_imputados, na.rm = FALSE)
datos_imputados <- na.locf(datos_imputados, fromLast = TRUE, na.rm = FALSE)
# Verificación post-imputación
n_final <- sum(is.na(datos_imputados))
attr(datos_imputados, "faltantes_originales") <- n_inicial
attr(datos_imputados, "faltantes_restantes") <- n_final
return(datos_imputados)
}
# Aplicar a todos los activos
lista_datos_limpios <- lapply(lista_datos, imputar_faltantes)
# Reportar resultados
cat("RESUMEN DE IMPUTACIÓN:\n")## RESUMEN DE IMPUTACIÓN:
for(simbolo in names(lista_datos_limpios)) {
orig <- attr(lista_datos_limpios[[simbolo]], "faltantes_originales")
rest <- attr(lista_datos_limpios[[simbolo]], "faltantes_restantes")
cat(" ", simbolo, ":", orig, "faltantes →", rest, "imputados\n")
}## AAPL : 0 faltantes → 0 imputados
## MSFT : 0 faltantes → 0 imputados
## GOOGL : 0 faltantes → 0 imputados
## AMZN : 0 faltantes → 0 imputados
## TSLA : 0 faltantes → 0 imputados
## JPM : 0 faltantes → 0 imputados
## V : 0 faltantes → 0 imputados
## JNJ : 0 faltantes → 0 imputados
## WMT : 0 faltantes → 0 imputados
## PG : 0 faltantes → 0 imputados
##
## ✓ Proceso de imputación completado
## Método: Interpolación lineal + LOCF
## ══════════════════════════════════════════════════════════════
Justificación Metodológica: La interpolación lineal es apropiada para gaps pequeños (1-2 días) bajo supuesto de continuidad local. LOCF se reserva para bordes temporales donde interpolación no es factible. Gaps extensos (>5 días) requerirían análisis caso-por-caso.
## ══════════════════════════════════════════════════════════════
## EXTRACCIÓN DE PRECIOS AJUSTADOS
## ══════════════════════════════════════════════════════════════
# Extraer columna 'Adjusted' de cada activo y consolidar
precios_ajustados <- do.call(merge, lapply(names(lista_datos_limpios), function(s) {
Ad(lista_datos_limpios[[s]])
}))
# Renombrar columnas con símbolos
colnames(precios_ajustados) <- simbolos
# Convertir a data.frame para manipulación flexible
df_precios <- data.frame(
Fecha = index(precios_ajustados),
precios_ajustados,
check.names = FALSE,
stringsAsFactors = FALSE
)
# Estadísticas descriptivas del dataset consolidado
cat("CARACTERÍSTICAS DEL DATASET CONSOLIDADO:\n")## CARACTERÍSTICAS DEL DATASET CONSOLIDADO:
cat(" Periodo completo: ", format(min(df_precios$Fecha), "%d/%m/%Y"), "a",
format(max(df_precios$Fecha), "%d/%m/%Y"), "\n")## Periodo completo: 02/01/2019 a 05/02/2026
## Observaciones: 1784 días de negociación
## Activos: 10 símbolos
## Completitud: 100 %
## ══════════════════════════════════════════════════════════════
## MUESTRA DE DATOS CONSOLIDADOS:
print(kable(head(df_precios, 8), digits = 2,
caption = "Primeros registros de precios ajustados (USD)"))##
##
## Table: Primeros registros de precios ajustados (USD)
##
## | |Fecha | AAPL| MSFT| GOOGL| AMZN| TSLA| JPM| V| JNJ| WMT| PG|
## |:----------|:----------|-----:|-----:|-----:|-----:|-----:|-----:|-----:|-----:|-----:|-----:|
## |2019-01-02 |2019-01-02 | 37.54| 94.61| 52.34| 76.96| 20.67| 81.25| 126.6| 104.9| 28.02| 75.92|
## |2019-01-03 |2019-01-03 | 33.80| 91.13| 50.89| 75.01| 20.02| 80.10| 122.0| 103.3| 27.88| 75.38|
## |2019-01-04 |2019-01-04 | 35.24| 95.37| 53.50| 78.77| 21.18| 83.05| 127.3| 105.0| 28.05| 76.92|
## |2019-01-07 |2019-01-07 | 35.16| 95.49| 53.39| 81.48| 22.33| 83.11| 129.6| 104.3| 28.38| 76.61|
## |2019-01-08 |2019-01-08 | 35.83| 96.18| 53.86| 82.83| 22.36| 82.95| 130.3| 106.8| 28.58| 76.90|
## |2019-01-09 |2019-01-09 | 36.44| 97.56| 53.68| 82.97| 22.57| 82.81| 131.8| 105.9| 28.49| 75.64|
## |2019-01-10 |2019-01-10 | 36.56| 96.93| 53.54| 82.81| 23.00| 82.80| 132.1| 106.5| 28.51| 75.82|
## |2019-01-11 |2019-01-11 | 36.20| 96.18| 52.82| 82.03| 23.15| 82.40| 131.5| 106.6| 28.47| 76.32|
# Paleta de colores distintiva
colores <- c("#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00",
"#FFFF33", "#A65628", "#F781BF", "#999999", "#66C2A5")
# Gráfico de series múltiples
plot(df_precios$Fecha, df_precios[[simbolos[1]]], type = "l",
col = colores[1], lwd = 2,
ylim = range(df_precios[,-1], na.rm = TRUE),
main = "Evolución Temporal de Precios de Activos Seleccionados",
sub = paste("Periodo:", format(min(df_precios$Fecha), "%b %Y"), "-",
format(max(df_precios$Fecha), "%b %Y")),
xlab = "Fecha",
ylab = "Precio de Cierre Ajustado (USD)",
cex.main = 1.4, font.main = 2,
cex.sub = 0.9, font.sub = 3)
# Agregar series adicionales
for(i in 2:length(simbolos)) {
lines(df_precios$Fecha, df_precios[[simbolos[i]]],
col = colores[i], lwd = 2)
}
# Leyenda optimizada
legend("topleft", legend = simbolos, col = colores[1:length(simbolos)],
lwd = 2, bty = "n", ncol = 3, cex = 0.85,
title = "Activos")
grid(col = "gray80", lty = "dotted")
# Anotación de evento relevante (ejemplo: COVID-19)
abline(v = as.Date("2020-03-01"), lty = 2, col = "red", lwd = 1.5)
text(as.Date("2020-03-01"), max(df_precios[,-1], na.rm = TRUE),
"COVID-19", srt = 90, adj = c(0, -0.3), cex = 0.8, col = "red")Figura 1: Evolución Temporal de Precios Absolutos
Observaciones Metodológicas: Los precios absolutos presentan escalas muy heterogéneas, dificultando comparaciones directas de desempeño. La normalización es imperativa para análisis comparativo.
# Normalizar cada serie a base 100 (primer valor = 100)
df_precios_norm <- df_precios
for(col in simbolos) {
df_precios_norm[[col]] <- (df_precios[[col]] / df_precios[[col]][1]) * 100
}
# Gráfico normalizado
plot(df_precios_norm$Fecha, df_precios_norm[[simbolos[1]]], type = "l",
col = colores[1], lwd = 2.5,
ylim = range(df_precios_norm[,-1], na.rm = TRUE),
main = "Desempeño Relativo: Precios Normalizados (Base 100)",
sub = "Base 100 = Primera observación del periodo",
xlab = "Fecha",
ylab = "Índice (Base 100)",
cex.main = 1.4, font.main = 2,
cex.sub = 0.9, font.sub = 3)
for(i in 2:length(simbolos)) {
lines(df_precios_norm$Fecha, df_precios_norm[[simbolos[i]]],
col = colores[i], lwd = 2.5)
}
# Línea de referencia
abline(h = 100, lty = 2, col = "black", lwd = 2)
text(mean(range(df_precios_norm$Fecha)), 100,
"Valor inicial (sin cambio)", pos = 3, cex = 0.8)
legend("topleft", legend = simbolos, col = colores[1:length(simbolos)],
lwd = 2.5, bty = "n", ncol = 3, cex = 0.85,
title = "Activos")
grid(col = "gray80", lty = "dotted")Figura 2: Desempeño Relativo - Precios Normalizados
Interpretación Económica: - Valores > 100: Apreciación acumulada desde inicio del periodo - Valores < 100: Depreciación acumulada - Pendiente de la serie: Tasa promedio de crecimiento/decrecimiento
Retornos Simples (Aritméticos): \[R_t = \frac{P_t - P_{t-1}}{P_{t-1}} = \frac{P_t}{P_{t-1}} - 1\]
Retornos Logarítmicos (Continuos): \[r_t = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1})\]
| Propiedad | Retornos Simples | Retornos Logarítmicos |
|---|---|---|
| Agregación temporal | No aditivos | Aditivos: \(r_{0,T} = \sum_{t=1}^T r_t\) |
| Agregación cross-sectional | Aditivos (portafolio) | No aditivos directamente |
| Simetría | Asimétricos | Simétricos |
| Distribución empírica | Más alejada de normal | Más cercana a normal |
| Interpretación intuitiva | Directa (%) | Requiere conversión |
Recomendación: Utilizar retornos logarítmicos para análisis econométrico y modelado; convertir a simples para comunicación con stakeholders no técnicos.
## ══════════════════════════════════════════════════════════════
## CÁLCULO DE RETORNOS LOGARÍTMICOS
## ══════════════════════════════════════════════════════════════
# Calcular retornos logarítmicos diarios
# Multiplicar por 100 para expresar en porcentaje
retornos <- diff(log(precios_ajustados)) * 100
# Eliminar primera observación (NA por diferenciación)
retornos <- retornos[-1, ]
# Convertir a data.frame
df_retornos <- data.frame(
Fecha = index(retornos),
retornos,
check.names = FALSE
)
cat("ESPECIFICACIONES DEL CÁLCULO:\n")## ESPECIFICACIONES DEL CÁLCULO:
## Tipo de retorno: Logarítmico continuo
## Frecuencia: Diaria
## Unidades: Porcentaje (%)
## Fórmula: r_t = ln(P_t / P_{t-1}) × 100
## ESTADÍSTICAS DEL DATASET DE RETORNOS:
## Observaciones: 1783 retornos diarios
## Periodo perdido: 1 día (por diferenciación)
## Completitud: 100 %
## ══════════════════════════════════════════════════════════════
## MUESTRA DE RETORNOS CALCULADOS:
print(kable(head(df_retornos, 10), digits = 4,
caption = "Primeros retornos logarítmicos diarios (%)"))##
##
## Table: Primeros retornos logarítmicos diarios (%)
##
## | |Fecha | AAPL| MSFT| GOOGL| AMZN| TSLA| JPM| V| JNJ| WMT| PG|
## |:----------|:----------|--------:|-------:|-------:|-------:|-------:|-------:|-------:|-------:|-------:|-------:|
## |2019-01-03 |2019-01-03 | -10.4924| -3.7482| -2.8086| -2.5566| -3.1978| -1.4314| -3.6702| -1.6018| -0.5156| -0.7036|
## |2019-01-04 |2019-01-04 | 4.1803| 4.5460| 5.0021| 4.8851| 5.6094| 3.6202| 4.2179| 1.6644| 0.6226| 2.0205|
## |2019-01-07 |2019-01-07 | -0.2228| 0.1274| -0.1996| 3.3777| 5.2935| 0.0695| 1.7872| -0.6436| 1.1704| -0.4008|
## |2019-01-08 |2019-01-08 | 1.8884| 0.7225| 0.8745| 1.6476| 0.1164| -0.1888| 0.5424| 2.2961| 0.6957| 0.3684|
## |2019-01-09 |2019-01-09 | 1.6839| 1.4198| -0.3433| 0.1713| 0.9438| -0.1691| 1.1701| -0.7957| -0.3262| -1.6466|
## |2019-01-10 |2019-01-10 | 0.3191| -0.6446| -0.2610| -0.1930| 1.8845| -0.0100| 0.1877| 0.6032| 0.0737| 0.2416|
## |2019-01-11 |2019-01-11 | -0.9867| -0.7752| -1.3400| -0.9500| 0.6616| -0.4793| -0.4409| 0.0308| -0.1264| 0.6560|
## |2019-01-14 |2019-01-14 | -1.5151| -0.7322| -1.2250| -1.4335| -3.7736| 1.0256| -0.6978| -1.1394| 0.1159| -0.6779|
## |2019-01-15 |2019-01-15 | 2.0260| 2.8593| 3.2743| 3.4848| 2.9553| 0.7304| 0.1749| 0.8384| 1.3599| 0.9391|
## |2019-01-16 |2019-01-16 | 1.2143| 0.3517| 0.2757| 0.5491| 0.4692| 0.8032| 0.0000| -1.0256| 0.1038| -0.6980|
# Configurar layout de gráficos
n_activos <- length(simbolos)
n_filas <- ceiling(n_activos / 2)
par(mfrow = c(n_filas, 2), mar = c(4, 4, 3, 1))
# Generar histograma para cada activo
for(simbolo in simbolos) {
datos_ret <- df_retornos[[simbolo]]
# Histograma con overlay de distribución normal teórica
hist(datos_ret, breaks = 60, freq = FALSE,
col = rgb(0.2, 0.5, 0.8, 0.6), border = "white",
main = paste("Distribución de Retornos:", simbolo),
xlab = "Retorno Diario (%)",
ylab = "Densidad",
cex.main = 1.2, font.main = 2)
# Curva normal teórica
x_seq <- seq(min(datos_ret, na.rm = TRUE),
max(datos_ret, na.rm = TRUE),
length.out = 100)
curve(dnorm(x, mean(datos_ret, na.rm = TRUE),
sd(datos_ret, na.rm = TRUE)),
add = TRUE, col = "red", lwd = 2, lty = 2)
# Densidad empírica (kernel density estimate)
lines(density(datos_ret, na.rm = TRUE), col = "darkblue", lwd = 2)
# Líneas de referencia
abline(v = mean(datos_ret, na.rm = TRUE), col = "red", lwd = 2, lty = 1)
abline(v = 0, col = "black", lwd = 1, lty = 3)
# Leyenda compacta
legend("topright",
legend = c("Empírico", "Normal", "Media"),
col = c("darkblue", "red", "red"),
lty = c(1, 2, 1), lwd = c(2, 2, 2),
bty = "n", cex = 0.7)
}Figura 3: Distribuciones Empíricas de Retornos Diarios
Análisis de Desviaciones de Normalidad: - Leptocurtosis (colas pesadas): Más observaciones extremas que lo predicho por distribución normal - Asimetría negativa: Cola izquierda más extensa (pérdidas extremas más frecuentes) - Implicaciones: Modelos basados en normalidad (e.g., VaR paramétrico) pueden subestimar riesgo de cola
La volatilidad histórica se define como la desviación estándar de retornos:
\[\sigma = \sqrt{\frac{1}{T-1} \sum_{t=1}^T (r_t - \bar{r})^2}\]
Para anualización desde frecuencia diaria:
\[\sigma_{anual} = \sigma_{diaria} \times \sqrt{252}\]
donde 252 es el número convencional de días de negociación anuales en mercados estadounidenses.
# Calcular volatilidad anualizada
volatilidades <- sapply(df_retornos[,-1], function(x) {
sd(x, na.rm = TRUE) * sqrt(252)
})
# Ordenar descendentemente
volatilidades <- sort(volatilidades, decreasing = TRUE)
# Gráfico de barras horizontal
par(mar = c(5, 8, 4, 2))
colores_vol <- colorRampPalette(c("darkgreen", "yellow", "darkred"))(length(volatilidades))
barplot(volatilidades, horiz = TRUE, las = 1,
col = colores_vol,
main = "Volatilidad Anualizada por Activo",
xlab = "Volatilidad Anualizada (%)",
cex.main = 1.4, font.main = 2,
xlim = c(0, max(volatilidades) * 1.15))
# Añadir valores numéricos
text(volatilidades, seq_along(volatilidades),
labels = paste0(round(volatilidades, 2), "%"),
pos = 4, cex = 0.9, font = 2)
# Línea de referencia (mediana)
abline(v = median(volatilidades), lty = 2, col = "black", lwd = 2)
text(median(volatilidades), length(volatilidades) * 0.9,
paste("Mediana:", round(median(volatilidades), 2), "%"),
pos = 4, cex = 0.8)
grid(col = "gray80", lty = "dotted")Figura 4: Ranking de Volatilidad Anualizada
# Tabla complementaria
df_vol <- data.frame(
Activo = names(volatilidades),
Volatilidad_Anual = as.numeric(volatilidades),
Ranking = seq_along(volatilidades)
)
cat("\n")print(kable(df_vol, digits = 2, row.names = FALSE,
caption = "Tabla: Volatilidad Anualizada Ordenada"))##
##
## Table: Tabla: Volatilidad Anualizada Ordenada
##
## |Activo | Volatilidad_Anual| Ranking|
## |:------|-----------------:|-------:|
## |TSLA | 63.90| 1|
## |AMZN | 34.10| 2|
## |GOOGL | 31.23| 3|
## |AAPL | 30.95| 4|
## |JPM | 29.80| 5|
## |MSFT | 28.65| 6|
## |V | 25.95| 7|
## |WMT | 21.96| 8|
## |PG | 20.07| 9|
## |JNJ | 19.19| 10|
Interpretación Financiera: - Alta volatilidad: Mayor dispersión de retornos, riesgo elevado - Baja volatilidad: Menor dispersión, activo más “estable” - Trade-off riesgo-retorno esperable en mercados eficientes
La correlación de Pearson mide asociación lineal entre dos variables:
\[\rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y}\]
En contexto financiero: - \(\rho \approx +1\): Co-movimiento fuerte positivo (diversificación limitada) - \(\rho \approx 0\): Independencia (máximos beneficios de diversificación) - \(\rho \approx -1\): Movimientos opuestos (cobertura natural)
# Calcular matriz de correlación
matriz_cor <- cor(df_retornos[, -1], use = "pairwise.complete.obs")
# Visualización con corrplot (clustering jerárquico automático)
corrplot(matriz_cor,
method = "color",
type = "upper",
order = "hclust",
addCoef.col = "black",
tl.col = "black",
tl.srt = 45,
tl.cex = 1,
number.cex = 0.9,
number.digits = 2,
col = colorRampPalette(c("#6D9EC1", "white", "#E46726"))(200),
title = "Matriz de Correlación de Retornos Diarios\n(Ordenamiento Jerárquico)",
mar = c(0,0,3,0),
cl.cex = 0.9)Figura 6: Matriz de Correlación con Clustering Jerárquico
## ══════════════════════════════════════════════════════════════
## ANÁLISIS DE MATRIZ DE CORRELACIÓN
## ══════════════════════════════════════════════════════════════
## Método: Correlación de Pearson
## Tratamiento de NAs: Pairwise complete observations
## Ordenamiento: Clustering jerárquico (método Ward)
cat("Rango de correlaciones:", round(min(matriz_cor[matriz_cor != 1]), 3),
"a", round(max(matriz_cor[matriz_cor != 1]), 3), "\n")## Rango de correlaciones: 0.06 a 0.687
## Correlación media: 0.393
## ══════════════════════════════════════════════════════════════
Implicaciones para Gestión de Portafolios: - Activos con alta correlación positiva ofrecen beneficios limitados de diversificación - Portafolios óptimos buscan activos con correlaciones bajas o negativas - Correlaciones no son estáticas: varían con régimen de mercado
# Ventana de estimación
ventana <- 30
cat("══════════════════════════════════════════════════════════════\n")## ══════════════════════════════════════════════════════════════
## CÁLCULO DE VOLATILIDAD MÓVIL
## ══════════════════════════════════════════════════════════════
## Método: Rolling standard deviation
## Ventana: 30 días de negociación (~1 mes)
## Frecuencia: Anualizada (× √252)
## ══════════════════════════════════════════════════════════════
# Calcular volatilidad móvil para cada activo
volatilidad_movil <- apply(retornos, 2, function(x) {
rollapply(x, width = ventana,
FUN = function(w) sd(w, na.rm = TRUE) * sqrt(252),
fill = NA, align = "right")
})
# Gráficos individuales
n_filas <- ceiling(length(simbolos) / 2)
par(mfrow = c(n_filas, 2), mar = c(4, 4, 3, 1))
for(i in 1:length(simbolos)) {
plot(index(retornos), volatilidad_movil[,i],
type = "l", col = "darkblue", lwd = 1.5,
main = paste("Volatilidad Móvil:", simbolos[i]),
xlab = "Fecha", ylab = "Volatilidad Anualizada (%)",
cex.main = 1.2, font.main = 2)
# Línea de volatilidad promedio
abline(h = mean(volatilidad_movil[,i], na.rm = TRUE),
lty = 2, col = "red", lwd = 1.5)
# Banda de ±1 desviación estándar
media_vol <- mean(volatilidad_movil[,i], na.rm = TRUE)
sd_vol <- sd(volatilidad_movil[,i], na.rm = TRUE)
abline(h = media_vol + sd_vol, lty = 3, col = "orange", lwd = 1)
abline(h = media_vol - sd_vol, lty = 3, col = "orange", lwd = 1)
grid(col = "gray80", lty = "dotted")
# Leyenda
legend("topright",
legend = c("Vol móvil", "Media", "±1 SD"),
col = c("darkblue", "red", "orange"),
lty = c(1, 2, 3), lwd = c(1.5, 1.5, 1),
bty = "n", cex = 0.7)
}Figura 7: Evolución Temporal de Volatilidad Móvil
Observaciones sobre Clustering de Volatilidad: - Periodos de alta volatilidad tienden a agruparse (volatility clustering) - Fenómeno bien documentado: eventos extremos correlacionan temporalmente - Relevante para modelos GARCH y gestión dinámica de riesgo
El Value at Risk (VaR) cuantifica la máxima pérdida esperada en un horizonte temporal dado, con un nivel de confianza especificado:
\[\text{VaR}_\alpha = -\inf\{x : F(x) \geq \alpha\}\]
donde \(F\) es la función de distribución acumulada de pérdidas y \(\alpha\) el nivel de confianza (típicamente 95% o 99%).
## ══════════════════════════════════════════════════════════════
## CÁLCULO DE VALUE AT RISK HISTÓRICO
## ══════════════════════════════════════════════════════════════
# Calcular VaR a múltiples niveles de confianza
var_95 <- sapply(df_retornos[,-1], quantile, probs = 0.05, na.rm = TRUE)
var_99 <- sapply(df_retornos[,-1], quantile, probs = 0.01, na.rm = TRUE)
var_999 <- sapply(df_retornos[,-1], quantile, probs = 0.001, na.rm = TRUE)
# Consolidar en data.frame
df_var <- data.frame(
Activo = names(var_95),
VaR_95 = as.numeric(var_95),
VaR_99 = as.numeric(var_99),
VaR_999 = as.numeric(var_999)
)
cat("MÉTODO: VaR Histórico (no paramétrico)\n")## MÉTODO: VaR Histórico (no paramétrico)
## INTERPRETACIÓN:
## VaR 95%: En 95% de días, pérdida no excederá este valor
## VaR 99%: En 99% de días, pérdida no excederá este valor
## VaR 99.9%: Pérdidas extremas (1 en 1000 días)
print(kable(df_var, digits = 4, row.names = FALSE,
caption = "Tabla: Value at Risk Histórico Diario (%)"))##
##
## Table: Tabla: Value at Risk Histórico Diario (%)
##
## |Activo | VaR_95| VaR_99| VaR_999|
## |:--------|------:|-------:|-------:|
## |AAPL.5% | -3.022| -5.149| -10.418|
## |MSFT.5% | -2.736| -4.573| -10.087|
## |GOOGL.5% | -3.000| -5.259| -9.674|
## |AMZN.5% | -3.266| -5.620| -9.241|
## |TSLA.5% | -6.021| -11.203| -19.217|
## |JPM.5% | -2.754| -4.874| -11.871|
## |V.5% | -2.458| -4.947| -7.897|
## |JNJ.5% | -1.654| -3.394| -7.283|
## |WMT.5% | -1.897| -3.261| -8.672|
## |PG.5% | -1.778| -3.743| -7.758|
##
## ══════════════════════════════════════════════════════════════
# Seleccionar activo ejemplo
activo_ejemplo <- simbolos[1]
retornos_ejemplo <- df_retornos[[activo_ejemplo]]
# Extraer VaR para este activo
var_95_ej <- quantile(retornos_ejemplo, 0.05, na.rm = TRUE)
var_99_ej <- quantile(retornos_ejemplo, 0.01, na.rm = TRUE)
# Histograma con densidad
hist(retornos_ejemplo, breaks = 60, freq = FALSE,
col = rgb(0.7, 0.9, 1, 0.7), border = "white",
main = paste("Distribución de Retornos con VaR:", activo_ejemplo),
xlab = "Retorno Diario (%)", ylab = "Densidad",
cex.main = 1.4, font.main = 2)
# Densidad empírica
lines(density(retornos_ejemplo, na.rm = TRUE),
col = "darkblue", lwd = 2.5)
# Líneas VaR
abline(v = var_95_ej, col = "orange", lwd = 3, lty = 2)
abline(v = var_99_ej, col = "red", lwd = 3, lty = 2)
# Sombrear región de pérdidas extremas
x_seq <- seq(min(retornos_ejemplo, na.rm = TRUE), var_99_ej, length.out = 100)
dens_vals <- density(retornos_ejemplo, na.rm = TRUE)
y_seq <- approx(dens_vals$x, dens_vals$y, xout = x_seq)$y
y_seq[is.na(y_seq)] <- 0
polygon(c(x_seq, rev(x_seq)), c(y_seq, rep(0, length(y_seq))),
col = rgb(1, 0, 0, 0.3), border = NA)
# Leyenda CORREGIDA
legend("topleft",
legend = c(paste("VaR 95%:", round(var_95_ej, 2), "%"),
paste("VaR 99%:", round(var_99_ej, 2), "%")),
bty = "n", cex = 0.9)
grid(col = "gray80", lty = "dotted")Figura 8: Distribución de Retornos con Umbrales VaR
## ══════════════════════════════════════════════════════════════
## MODELO DE REGRESIÓN LINEAL MÚLTIPLE
## ══════════════════════════════════════════════════════════════
# Preparar datos
df_modelo <- df_retornos[, c("AAPL", "MSFT", "GOOGL", "AMZN")]
df_modelo <- na.omit(df_modelo)
cat("ESPECIFICACIÓN DEL MODELO:\n")## ESPECIFICACIÓN DEL MODELO:
## Variable dependiente: AAPL (retornos)
## Variables independientes: MSFT, GOOGL, AMZN
## Observaciones: 1783
# Estimar modelo
modelo_lm <- lm(AAPL ~ MSFT + GOOGL + AMZN, data = df_modelo)
# Output completo
cat("RESULTADOS DE LA ESTIMACIÓN:\n")## RESULTADOS DE LA ESTIMACIÓN:
## ════════════════════════════════════════════════════════════════
##
## Call:
## lm(formula = AAPL ~ MSFT + GOOGL + AMZN, data = df_modelo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -7.805 -0.676 -0.002 0.660 9.896
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.0431 0.0319 1.35 0.18
## MSFT 0.4636 0.0265 17.50 < 0.0000000000000002 ***
## GOOGL 0.2245 0.0234 9.61 < 0.0000000000000002 ***
## AMZN 0.1416 0.0209 6.77 0.000000000018 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.34 on 1779 degrees of freedom
## Multiple R-squared: 0.526, Adjusted R-squared: 0.525
## F-statistic: 658 on 3 and 1779 DF, p-value: <0.0000000000000002
## ════════════════════════════════════════════════════════════════
# Tabla estilizada de coeficientes
coef_tabla <- data.frame(
Termino = c("Intercepto", "MSFT", "GOOGL", "AMZN"),
Estimado = coef(modelo_lm),
Error_Std = summary(modelo_lm)$coefficients[,2],
t_valor = summary(modelo_lm)$coefficients[,3],
p_valor = summary(modelo_lm)$coefficients[,4]
)
rownames(coef_tabla) <- NULL
print(kable(coef_tabla, digits = 4,
caption = "Tabla: Coeficientes del Modelo de Regresión"))##
##
## Table: Tabla: Coeficientes del Modelo de Regresión
##
## |Termino | Estimado| Error_Std| t_valor| p_valor|
## |:----------|--------:|---------:|-------:|-------:|
## |Intercepto | 0.0431| 0.0319| 1.353| 0.1761|
## |MSFT | 0.4636| 0.0265| 17.503| 0.0000|
## |GOOGL | 0.2245| 0.0234| 9.610| 0.0000|
## |AMZN | 0.1416| 0.0209| 6.768| 0.0000|
Interpretación Económica: - Coeficientes positivos: Co-movimientos positivos entre activos - R²: Proporción de variabilidad de AAPL explicada por el modelo - p-valores: Significancia estadística
par(mfrow = c(2, 2), mar = c(4.5, 4.5, 3, 1.5))
plot(modelo_lm, which = 1:4, cex.main = 1.2, font.main = 2)Figura 9: Diagnósticos del Modelo de Regresión
Evaluación de Supuestos: - Residuals vs Fitted: Debe mostrar patrón aleatorio - Q-Q Plot: Puntos sobre línea diagonal indican normalidad - Scale-Location: Evalúa homogeneidad de varianza - Residuals vs Leverage: Identifica observaciones influyentes
El CAPM establece relación entre retorno esperado y riesgo sistemático:
\[E[R_i] = R_f + \beta_i (E[R_m] - R_f)\]
donde: - \(\beta_i\): Sensibilidad del activo al mercado - \(E[R_m] - R_f\): Prima de riesgo de mercado
Interpretación de Beta: - \(\beta = 1\): Activo se mueve igual que mercado - \(\beta > 1\): Activo más volátil que mercado - \(\beta < 1\): Activo menos volátil (defensivo)
## ══════════════════════════════════════════════════════════════
## ESTIMACIÓN DE BETAS (CAPM)
## ══════════════════════════════════════════════════════════════
## Descargando proxy de mercado: S&P 500 (SPY)...
SPY <- tryCatch({
getSymbols("SPY", from = fecha_inicio, to = fecha_fin,
auto.assign = FALSE, warnings = FALSE)
}, error = function(e) {
cat("Error descargando SPY\n")
return(NULL)
})
if(!is.null(SPY)) {
# Calcular retornos del mercado
retornos_mercado <- diff(log(Ad(SPY))) * 100
retornos_mercado <- retornos_mercado[-1, ]
# Alinear fechas
fechas_retornos <- as.Date(index(retornos))
fechas_mercado <- as.Date(index(retornos_mercado))
fechas_comunes <- intersect(fechas_retornos, fechas_mercado)
# Subset usando fechas
retornos_alineados <- retornos[as.character(fechas_comunes), ]
mercado_alineado <- as.numeric(retornos_mercado[as.character(fechas_comunes), ])
cat("✓ Datos alineados:", length(fechas_comunes), "observaciones\n\n")
# Calcular betas para cada activo (CORREGIDO)
betas <- numeric(ncol(retornos_alineados))
r2s <- numeric(ncol(retornos_alineados))
names(betas) <- colnames(retornos_alineados)
names(r2s) <- colnames(retornos_alineados)
for(i in 1:ncol(retornos_alineados)) {
ret_activo <- as.numeric(retornos_alineados[, i])
modelo <- lm(ret_activo ~ mercado_alineado)
betas[i] <- coef(modelo)[2]
r2s[i] <- summary(modelo)$r.squared
}
# Ordenar betas
betas <- sort(betas, decreasing = TRUE)
# Gráfico
par(mar = c(5, 8, 4, 2))
colores_beta <- ifelse(betas > 1, "darkred", "darkgreen")
barplot(betas, horiz = TRUE, las = 1, col = colores_beta,
main = "Coeficientes Beta vs. Mercado (S&P 500)",
xlab = "Beta (β)",
cex.main = 1.4, font.main = 2,
xlim = c(0, max(betas) * 1.15))
# Línea β = 1
abline(v = 1, lty = 2, col = "black", lwd = 3)
text(1, length(betas) * 0.95, "β = 1\n(Mercado)",
pos = 4, cex = 0.8, font = 2)
# Valores
text(betas, seq_along(betas), labels = round(betas, 3),
pos = 4, cex = 0.95, font = 2)
grid(col = "gray80", lty = "dotted")
# Tabla
df_betas <- data.frame(
Activo = names(betas),
Beta = as.numeric(betas),
R_cuadrado = r2s[names(betas)],
Interpretacion = ifelse(betas > 1, "Más volátil",
ifelse(betas < 1, "Menos volátil", "Similar"))
)
cat("\n")
print(kable(df_betas, digits = 3, row.names = FALSE,
caption = "Tabla: Coeficientes Beta y Bondad de Ajuste"))
} else {
cat("⚠ No se pudo completar análisis CAPM sin datos de mercado\n")
}## ✓ Datos alineados: 1783 observaciones
Figura 10: Coeficientes Beta vs. Mercado
##
##
##
## Table: Tabla: Coeficientes Beta y Bondad de Ajuste
##
## |Activo | Beta| R_cuadrado|Interpretacion |
## |:------|-----:|----------:|:--------------|
## |TSLA | 1.588| 0.248|Más volátil |
## |MSFT | 1.209| 0.689|Más volátil |
## |AAPL | 1.179| 0.599|Más volátil |
## |JPM | 1.141| 0.582|Más volátil |
## |GOOGL | 1.096| 0.518|Más volátil |
## |AMZN | 1.052| 0.412|Más volátil |
## |V | 1.001| 0.606|Más volátil |
## |PG | 0.580| 0.307|Menos volátil |
## |JNJ | 0.514| 0.268|Menos volátil |
## |WMT | 0.509| 0.225|Menos volátil |
##
## ══════════════════════════════════════════════════════════════
## ══════════════════════════════════════════════════════════════
## CONSTRUCCIÓN DE FRONTERA EFICIENTE
## ══════════════════════════════════════════════════════════════
## Activos seleccionados:
## Activo 1: AAPL
## Activo 2: JNJ
# Parámetros
r1 <- mean(df_retornos[[activo_1]], na.rm = TRUE) * 252
r2 <- mean(df_retornos[[activo_2]], na.rm = TRUE) * 252
s1 <- sd(df_retornos[[activo_1]], na.rm = TRUE) * sqrt(252)
s2 <- sd(df_retornos[[activo_2]], na.rm = TRUE) * sqrt(252)
rho <- cor(df_retornos[[activo_1]], df_retornos[[activo_2]], use = "complete.obs")
cat("Parámetros:\n")## Parámetros:
## Retorno AAPL : 28.19 %
## Retorno JNJ : 11.56 %
## Volatilidad AAPL : 30.95 %
## Volatilidad JNJ : 19.19 %
## Correlación: 0.319
# Generar pesos
pesos <- seq(0, 1, by = 0.005)
retorno_port <- pesos * r1 + (1 - pesos) * r2
riesgo_port <- sqrt(pesos^2 * s1^2 + (1 - pesos)^2 * s2^2 +
2 * pesos * (1 - pesos) * s1 * s2 * rho)
# Varianza mínima
indice_min_var <- which.min(riesgo_port)
peso_min_var <- pesos[indice_min_var]
cat("PORTAFOLIO DE VARIANZA MÍNIMA:\n")## PORTAFOLIO DE VARIANZA MÍNIMA:
## Peso AAPL : 19 %
## Peso JNJ : 81 %
## ══════════════════════════════════════════════════════════════
# Gráfico
plot(riesgo_port, retorno_port, type = "l",
col = "darkblue", lwd = 3,
main = paste("Frontera Eficiente:", activo_1, "-", activo_2),
sub = paste("Correlación =", round(rho, 3)),
xlab = "Riesgo: Volatilidad Anualizada (%)",
ylab = "Retorno: Rendimiento Anualizado (%)",
cex.main = 1.4, font.main = 2)
# Activos individuales
points(c(s1, s2), c(r1, r2), pch = 19, col = "red", cex = 2.5)
text(c(s1, s2), c(r1, r2), labels = c(activo_1, activo_2),
pos = c(3, 3), font = 2, cex = 1.2, col = "red")
# Varianza mínima
points(riesgo_port[indice_min_var], retorno_port[indice_min_var],
pch = 17, col = "darkgreen", cex = 2.5)
text(riesgo_port[indice_min_var], retorno_port[indice_min_var],
"Min Var", pos = 1, font = 2, cex = 0.9, col = "darkgreen")
grid(col = "gray80", lty = "dotted")Figura 11: Frontera Eficiente
El presente artículo ha presentado un framework metodológico integral para el análisis cuantitativo de activos financieros utilizando datos de Yahoo Finance, abarcando desde la adquisición robusta hasta el modelado avanzado.
Este framework es directamente aplicable por investigadores académicos, gestores de activos, analistas de riesgo, traders algorítmicos, y estudiantes avanzados en finanzas cuantitativas.
Las limitaciones incluyen supuestos de estacionariedad, no-normalidad de retornos, y posibles errores en datos de Yahoo Finance. Extensiones futuras incluyen modelos GARCH, cópulas, y machine learning.
Autor: JEEL CUEVA
Fecha: 06 de febrero de 2026
Palabras clave: Análisis Financiero, Yahoo Finance, R Programming, Gestión de Riesgo, Series Temporales
Clasificación JEL: C58, C63, G11, G12, G17
Documento generado con R Markdown garantizando reproducibilidad completa.