1 RESUMEN EJECUTIVO

1.1 Abstract

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)


2 INTRODUCCIÓN

2.1 Contexto y Motivación Científica

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.

2.2 Objetivos de Investigación

Este trabajo persigue los siguientes objetivos específicos, organizados en orden de complejidad creciente:

  1. 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

  2. Presentar técnicas fundamentales de análisis exploratorio aplicadas a series temporales financieras, con énfasis en visualización efectiva y estadísticas descriptivas relevantes

  3. Implementar métricas estándar de evaluación de desempeño ajustado por riesgo, incluyendo Sharpe Ratio, Information Ratio, y medidas de downside risk

  4. Desarrollar metodologías para análisis de correlaciones dinámicas y construcción de matrices de covarianza, fundamentales para diversificación de portafolios

  5. Introducir modelos econométricos para análisis de retornos, incluyendo regresión lineal múltiple y estimación de sensibilidades sistemáticas (betas)

  6. Presentar aplicaciones de teoría moderna de portafolios, incluyendo construcción de fronteras eficientes y optimización media-varianza

  7. Garantizar reproducibilidad completa mediante código exhaustivamente documentado, auto-contenido, y estructurado según mejores prácticas de programación científica

2.3 Contribuciones del Artículo

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.

2.4 Estructura del Documento

El artículo se organiza siguiendo la secuencia lógica de un proyecto de análisis financiero cuantitativo:

  • Sección 2: Configuración del entorno computacional y carga de librerías especializadas
  • Sección 3: Fundamentos de Yahoo Finance y adquisición programática de datos
  • Sección 4: Limpieza, validación y tratamiento de anomalías en datos financieros
  • Sección 5: Análisis exploratorio de precios: visualización, normalización, estadísticas
  • Sección 6: Transformación a retornos y análisis de distribuciones empíricas
  • Sección 7: Métricas de desempeño y riesgo: volatilidad, Sharpe Ratio, gráficos riesgo-retorno
  • Sección 8: Análisis de correlaciones y matrices de covarianza
  • Sección 9: Volatilidad dinámica y análisis de clustering de volatilidad
  • Sección 10: Value at Risk: métodos históricos y paramétricos
  • Sección 11: Modelado estadístico: regresión múltiple y diagnósticos
  • Sección 12: Capital Asset Pricing Model y estimación de betas
  • Sección 13: Teoría de portafolios: frontera eficiente y optimización
  • Sección 14: Conclusiones, limitaciones, y direcciones futuras

3 CONFIGURACIÓN DEL ENTORNO COMPUTACIONAL

3.1 Fundamentos de Programación Financiera en R

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.

3.2 Paquetes Especializados Requeridos

El análisis presentado requiere los siguientes paquetes, cada uno cumpliendo roles específicos:

  • quantmod: Adquisición de datos financieros desde múltiples fuentes (Yahoo Finance, FRED, Google Finance), manipulación de objetos xts, gráficos de velas (candlestick charts)
  • xts: Manejo de series temporales extensibles, operaciones vectorizadas eficientes, alineamiento automático por fechas
  • zoo: Objetos ordenados con índices irregulares, funciones de ventanas móviles (rolling windows)
  • PerformanceAnalytics: Métricas de desempeño de portafolios, análisis de drawdowns, funciones de retorno/riesgo
  • corrplot: Visualización avanzada de matrices de correlación con clustering jerárquico
  • TTR: Indicadores técnicos (medias móviles, RSI, Bollinger Bands, etc.)
  • knitr/kableExtra: Generación de tablas HTML estilizadas para reportes profesionales

3.3 Carga e Inicialización

# 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
cat("✓ Configuración de sesión completada\n")
## ✓ Configuración de sesión completada

3.4 Información del Entorno Computacional

La reproducibilidad científica requiere documentación exhaustiva del entorno computacional. A continuación se presenta información completa de la configuración utilizada:

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("         INFORMACIÓN DEL ENTORNO COMPUTACIONAL\n")
##          INFORMACIÓN DEL ENTORNO COMPUTACIONAL
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("R versión:            ", as.character(getRversion()), "\n")
## R versión:             4.4.0
cat("Sistema operativo:    ", Sys.info()["sysname"], Sys.info()["release"], "\n")
## Sistema operativo:     Windows 10 x64
cat("Arquitectura:         ", Sys.info()["machine"], "\n")
## Arquitectura:          x86-64
cat("Fecha de análisis:    ", format(Sys.Date(), "%d de %B de %Y"), "\n")
## Fecha de análisis:     06 de febrero de 2026
cat("Zona horaria:         ", Sys.timezone(), "\n\n")
## Zona horaria:          America/Lima
cat("VERSIONES DE PAQUETES CRÍTICOS:\n")
## VERSIONES DE PAQUETES CRÍTICOS:
cat("  quantmod:           ", as.character(packageVersion("quantmod")), "\n")
##   quantmod:            0.4.27
cat("  xts:                ", as.character(packageVersion("xts")), "\n")
##   xts:                 0.14.0
cat("  PerformanceAnalytics:", as.character(packageVersion("PerformanceAnalytics")), "\n")
##   PerformanceAnalytics: 2.0.8
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════

4 ADQUISICIÓN DE DATOS DESDE YAHOO FINANCE

4.1 Fundamentos Teóricos y Técnicos

4.1.1 Arquitectura de Yahoo Finance API

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:

  1. Precios OHLC (Open-High-Low-Close): Fundamentales para análisis técnico y cálculo de volatilidad intra-diaria
  2. Volumen: Proxy de liquidez y actividad de negociación
  3. Precios Ajustados: Esenciales para análisis de retornos correctamente especificados
  4. Eventos Corporativos: Dividendos, splits, spin-offs (disponibles mediante queries adicionales)

4.1.2 Ajustes Corporativos y Su Importancia

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.

4.2 Selección de Activos y Justificación

# 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")
## ══════════════════════════════════════════════════════════════
cat("              PARÁMETROS DE DESCARGA DE DATOS\n")
##               PARÁMETROS DE DESCARGA DE DATOS
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("Número de activos:    ", length(simbolos), "\n")
## Número de activos:     10
cat("Símbolos:             ", paste(simbolos, collapse = ", "), "\n\n")
## Símbolos:              AAPL, MSFT, GOOGL, AMZN, TSLA, JPM, V, JNJ, WMT, PG
cat("PERIODO DE ANÁLISIS:\n")
## PERIODO DE ANÁLISIS:
cat("  Inicio:             ", format(as.Date(fecha_inicio), "%d/%m/%Y"), "\n")
##   Inicio:              01/01/2019
cat("  Fin:                ", format(fecha_fin, "%d/%m/%Y"), "\n")
##   Fin:                 06/02/2026
cat("  Duración:           ", as.numeric(fecha_fin - as.Date(fecha_inicio)), "días calendario\n")
##   Duración:            2593 días calendario
cat("  Días aprox trading: ", round(as.numeric(fecha_fin - as.Date(fecha_inicio)) * 5/7), "\n")
##   Días aprox trading:  1852
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════

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.

4.3 Descarga Programática con Manejo Robusto de Errores

cat("\n🔄 INICIANDO PROCESO DE DESCARGA DE DATOS\n")
## 
## 🔄 INICIANDO PROCESO DE DESCARGA DE DATOS
cat("   Fuente: Yahoo Finance API\n")
##    Fuente: Yahoo Finance API
cat("   Método: quantmod::getSymbols()\n\n")
##    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")
## 
## ══════════════════════════════════════════════════════════════
cat("              RESUMEN DE DESCARGA\n")
##               RESUMEN DE DESCARGA
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("Símbolos solicitados: ", length(simbolos), "\n")
## Símbolos solicitados:  10
cat("Descargas exitosas:   ", sum(descargas_exitosas), " (", 
    round(mean(descargas_exitosas)*100, 1), "%)\n")
## Descargas exitosas:    10  ( 100 %)
cat("Descargas fallidas:   ", sum(!descargas_exitosas), "\n")
## 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
cat("  Activos disponibles para análisis:", length(simbolos), "\n\n")
##   Activos disponibles para análisis: 10

4.4 Estructura de Objetos XTS

# Examinar en detalle el primer activo
primer_activo <- simbolos[1]
datos_ejemplo <- lista_datos[[primer_activo]]

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("       ANATOMÍA DE OBJETO XTS - Ejemplo:", primer_activo, "\n")
##        ANATOMÍA DE OBJETO XTS - Ejemplo: AAPL
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
cat("CARACTERÍSTICAS ESTRUCTURALES:\n")
## CARACTERÍSTICAS ESTRUCTURALES:
cat("  Clase:              ", paste(class(datos_ejemplo), collapse = ", "), "\n")
##   Clase:               xts, zoo
cat("  Dimensiones:        ", nrow(datos_ejemplo), "filas ×", 
    ncol(datos_ejemplo), "columnas\n")
##   Dimensiones:         1784 filas × 6 columnas
cat("  Tamaño en memoria:  ", format(object.size(datos_ejemplo), units = "Kb"), "\n\n")
##   Tamaño en memoria:   100 Kb
cat("INFORMACIÓN TEMPORAL:\n")
## INFORMACIÓN TEMPORAL:
cat("  Primer registro:    ", as.character(start(datos_ejemplo)), "\n")
##   Primer registro:     2019-01-02
cat("  Último registro:    ", as.character(end(datos_ejemplo)), "\n")
##   Último registro:     2026-02-05
cat("  Periodicidad:       ", periodicity(datos_ejemplo)$scale, "\n")
##   Periodicidad:        daily
cat("  Total de puntos:    ", nrow(datos_ejemplo), "\n\n")
##   Total de puntos:     1784
cat("COLUMNAS (Variables):\n")
## COLUMNAS (Variables):
for(col in colnames(datos_ejemplo)) {
  cat("  •", col, "\n")
}
##   • AAPL.Open 
##   • AAPL.High 
##   • AAPL.Low 
##   • AAPL.Close 
##   • AAPL.Volume 
##   • AAPL.Adjusted
cat("\n══════════════════════════════════════════════════════════════\n\n")
## 
## ══════════════════════════════════════════════════════════════
# Mostrar muestras de datos
cat("MUESTRA DE DATOS - Primeras 5 observaciones:\n")
## MUESTRA DE DATOS - Primeras 5 observaciones:
print(kable(head(datos_ejemplo, 5), digits = 2))
## 
## 
## | 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|
cat("\n\nMUESTRA DE DATOS - Últimas 5 observaciones:\n")
## 
## 
## MUESTRA DE DATOS - Últimas 5 observaciones:
print(kable(tail(datos_ejemplo, 5), digits = 2))
## 
## 
## | 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:

  • Open: Precio de primer transacción de la sesión (9:30 AM EST)
  • High: Precio máximo alcanzado intra-día
  • Low: Precio mínimo alcanzado intra-día
  • Close: Precio de última transacción (4:00 PM EST)
  • Volume: Número total de acciones negociadas
  • Adjusted: Precio de cierre retroactivamente ajustado por eventos corporativos

5 LIMPIEZA Y VALIDACIÓN DE DATOS

5.1 Fundamentos de Calidad de Datos Financieros

Los datos financieros de fuentes públicas presentan diversos desafíos de calidad:

  1. Valores faltantes: Días no negociables (fines de semana, feriados), suspensiones de negociación
  2. Outliers: Errores de registro, flash crashes, eventos extremos genuinos
  3. Inconsistencias lógicas: Violaciones de restricciones OHLC (e.g., Low > High)
  4. Ajustes incorrectos: Errores en retroajustes por splits/dividendos

Una limpieza rigurosa es prerequisito para cualquier análisis subsecuente válido.

5.2 Tratamiento de Valores Faltantes

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           ANÁLISIS Y TRATAMIENTO DE VALORES FALTANTES\n")
##            ANÁLISIS Y TRATAMIENTO DE VALORES FALTANTES
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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
cat("\n✓ Proceso de imputación completado\n")
## 
## ✓ Proceso de imputación completado
cat("  Método: Interpolación lineal + LOCF\n")
##   Método: Interpolación lineal + LOCF
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════

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.


6 ANÁLISIS EXPLORATORIO DE PRECIOS

6.1 Extracción y Consolidación de Precios Ajustados

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("       EXTRACCIÓN DE PRECIOS AJUSTADOS\n")
##        EXTRACCIÓN DE PRECIOS AJUSTADOS
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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
cat("  Observaciones:     ", nrow(df_precios), "días de negociación\n")
##   Observaciones:      1784 días de negociación
cat("  Activos:           ", ncol(df_precios) - 1, "símbolos\n")
##   Activos:            10 símbolos
cat("  Completitud:       ", round(mean(complete.cases(df_precios)) * 100, 2), "%\n")
##   Completitud:        100 %
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# Tabla de primeras observaciones
cat("MUESTRA DE DATOS CONSOLIDADOS:\n")
## 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|

6.2 Visualización de Series Temporales

# 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

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.

6.3 Normalización de Precios (Rebase 100)

# 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

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


7 CÁLCULO DE RETORNOS

7.1 Fundamentos Teóricos de Retornos Financieros

7.1.1 Retornos Simples vs. Logarítmicos

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})\]

7.1.2 Comparación de Propiedades

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.

7.2 Implementación del Cálculo

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           CÁLCULO DE RETORNOS LOGARÍTMICOS\n")
##            CÁLCULO DE RETORNOS LOGARÍTMICOS
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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:
cat("  Tipo de retorno:    Logarítmico continuo\n")
##   Tipo de retorno:    Logarítmico continuo
cat("  Frecuencia:         Diaria\n")
##   Frecuencia:         Diaria
cat("  Unidades:           Porcentaje (%)\n")
##   Unidades:           Porcentaje (%)
cat("  Fórmula:            r_t = ln(P_t / P_{t-1}) × 100\n\n")
##   Fórmula:            r_t = ln(P_t / P_{t-1}) × 100
cat("ESTADÍSTICAS DEL DATASET DE RETORNOS:\n")
## ESTADÍSTICAS DEL DATASET DE RETORNOS:
cat("  Observaciones:      ", nrow(df_retornos), "retornos diarios\n")
##   Observaciones:       1783 retornos diarios
cat("  Periodo perdido:    1 día (por diferenciación)\n")
##   Periodo perdido:    1 día (por diferenciación)
cat("  Completitud:        ", round(mean(complete.cases(df_retornos)) * 100, 2), "%\n")
##   Completitud:         100 %
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# Muestra de datos
cat("MUESTRA DE RETORNOS CALCULADOS:\n")
## 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|

7.3 Distribuciones Empíricas de Retornos

# 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

Figura 3: Distribuciones Empíricas de Retornos Diarios

par(mfrow = c(1, 1))

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


8 MÉTRICAS DE DESEMPEÑO Y RIESGO

8.1 Volatilidad: Medida Fundamental de Riesgo

8.1.1 Fundamento Teórico

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

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

8.2 Sharpe Ratio: Desempeño Ajustado por Riesgo

8.2.1 Fundamento Teórico

El Sharpe Ratio, desarrollado por William F. Sharpe (1966), mide exceso de retorno por unidad de riesgo:

\[SR = \frac{E[R_p] - R_f}{\sigma_p}\]

donde: - \(E[R_p]\): Retorno esperado del portafolio - \(R_f\): Tasa libre de riesgo - \(\sigma_p\): Volatilidad del portafolio

Interpretación: - SR > 1: Desempeño superior - SR > 2: Desempeño excelente - SR < 0: Retorno inferior a tasa libre de riesgo

# Definir tasa libre de riesgo
tasa_libre_riesgo <- 4.0

# Calcular retornos anualizados
retornos_anualizados <- sapply(df_retornos[,-1], function(x) {
  mean(x, na.rm = TRUE) * 252
})

# Calcular Sharpe Ratios
sharpe_ratios <- (retornos_anualizados - tasa_libre_riesgo) / volatilidades

# Ordenar
sharpe_ratios <- sort(sharpe_ratios, decreasing = TRUE)

# Gráfico de barras
par(mar = c(5, 8, 4, 2))
colores_sharpe <- ifelse(sharpe_ratios > 0, "darkgreen", "darkred")

barplot(sharpe_ratios, horiz = TRUE, las = 1, col = colores_sharpe,
        main = paste("Sharpe Ratio por Activo (Rf =", tasa_libre_riesgo, "%)"),
        xlab = "Sharpe Ratio",
        cex.main = 1.4, font.main = 2,
        xlim = c(min(sharpe_ratios) * 1.2, max(sharpe_ratios) * 1.2))

# Línea de referencia
abline(v = 0, lty = 2, col = "black", lwd = 2)
abline(v = 1, lty = 3, col = "blue", lwd = 1.5)
text(1, length(sharpe_ratios) * 0.9, "SR = 1\n(Umbral 'bueno')",
     pos = 4, cex = 0.75, col = "blue")

# Valores numéricos
text(sharpe_ratios, seq_along(sharpe_ratios),
     labels = round(sharpe_ratios, 3),
     pos = ifelse(sharpe_ratios > 0, 4, 2),
     cex = 0.9, font = 2)

grid(col = "gray80", lty = "dotted")
Figura 5: Sharpe Ratio por Activo

Figura 5: Sharpe Ratio por Activo


9 ANÁLISIS DE CORRELACIONES

9.1 Fundamentos de Correlación en Finanzas

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

Figura 6: Matriz de Correlación con Clustering Jerárquico

cat("\n")
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("       ANÁLISIS DE MATRIZ DE CORRELACIÓN\n")
##        ANÁLISIS DE MATRIZ DE CORRELACIÓN
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("Método:                Correlación de Pearson\n")
## Método:                Correlación de Pearson
cat("Tratamiento de NAs:    Pairwise complete observations\n")
## Tratamiento de NAs:    Pairwise complete observations
cat("Ordenamiento:          Clustering jerárquico (método Ward)\n")
## 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
cat("Correlación media:     ", round(mean(matriz_cor[lower.tri(matriz_cor)]), 3), "\n")
## Correlación media:      0.393
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════

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


10 VOLATILIDAD DINÁMICA

# Ventana de estimación
ventana <- 30

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           CÁLCULO DE VOLATILIDAD MÓVIL\n")
##            CÁLCULO DE VOLATILIDAD MÓVIL
cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("Método:    Rolling standard deviation\n")
## Método:    Rolling standard deviation
cat("Ventana:   ", ventana, "días de negociación (~1 mes)\n")
## Ventana:    30 días de negociación (~1 mes)
cat("Frecuencia: Anualizada (× √252)\n")
## Frecuencia: Anualizada (× √252)
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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

Figura 7: Evolución Temporal de Volatilidad Móvil

par(mfrow = c(1, 1))

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


11 VALUE AT RISK (VaR)

11.1 Marco Conceptual

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%).

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           CÁLCULO DE VALUE AT RISK HISTÓRICO\n")
##            CÁLCULO DE VALUE AT RISK HISTÓRICO
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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)
cat("INTERPRETACIÓN:\n")
## INTERPRETACIÓN:
cat("  VaR 95%: En 95% de días, pérdida no excederá este valor\n")
##   VaR 95%: En 95% de días, pérdida no excederá este valor
cat("  VaR 99%: En 99% de días, pérdida no excederá este valor\n")
##   VaR 99%: En 99% de días, pérdida no excederá este valor
cat("  VaR 99.9%: Pérdidas extremas (1 en 1000 días)\n\n")
##   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|
cat("\n══════════════════════════════════════════════════════════════\n")
## 
## ══════════════════════════════════════════════════════════════

11.2 Visualización de VaR en Distribución

# 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

Figura 8: Distribución de Retornos con Umbrales VaR


12 MODELADO ESTADÍSTICO

12.1 Regresión Lineal Múltiple

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("       MODELO DE REGRESIÓN LINEAL MÚLTIPLE\n")
##        MODELO DE REGRESIÓN LINEAL MÚLTIPLE
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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:
cat("  Variable dependiente: AAPL (retornos)\n")
##   Variable dependiente: AAPL (retornos)
cat("  Variables independientes: MSFT, GOOGL, AMZN\n")
##   Variables independientes: MSFT, GOOGL, AMZN
cat("  Observaciones: ", nrow(df_modelo), "\n\n")
##   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:
cat("════════════════════════════════════════════════════════════════\n")
## ════════════════════════════════════════════════════════════════
print(summary(modelo_lm))
## 
## 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
cat("════════════════════════════════════════════════════════════════\n\n")
## ════════════════════════════════════════════════════════════════
# 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

12.2 Diagnósticos del Modelo

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

Figura 9: Diagnósticos del Modelo de Regresión

par(mfrow = c(1, 1))

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


13 CAPITAL ASSET PRICING MODEL (CAPM)

13.1 Marco Teórico

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)

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           ESTIMACIÓN DE BETAS (CAPM)\n")
##            ESTIMACIÓN DE BETAS (CAPM)
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# Descargar índice de mercado
cat("Descargando proxy de mercado: S&P 500 (SPY)...\n")
## 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

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  |
cat("\n══════════════════════════════════════════════════════════════\n")
## 
## ══════════════════════════════════════════════════════════════

14 TEORÍA DE PORTAFOLIOS

14.1 Frontera Eficiente

cat("══════════════════════════════════════════════════════════════\n")
## ══════════════════════════════════════════════════════════════
cat("           CONSTRUCCIÓN DE FRONTERA EFICIENTE\n")
##            CONSTRUCCIÓN DE FRONTERA EFICIENTE
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# Seleccionar dos activos
activo_1 <- "AAPL"
activo_2 <- "JNJ"

cat("Activos seleccionados:\n")
## Activos seleccionados:
cat("  Activo 1:", activo_1, "\n")
##   Activo 1: AAPL
cat("  Activo 2:", activo_2, "\n\n")
##   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:
cat("  Retorno", activo_1, ":", round(r1, 2), "%\n")
##   Retorno AAPL : 28.19 %
cat("  Retorno", activo_2, ":", round(r2, 2), "%\n")
##   Retorno JNJ : 11.56 %
cat("  Volatilidad", activo_1, ":", round(s1, 2), "%\n")
##   Volatilidad AAPL : 30.95 %
cat("  Volatilidad", activo_2, ":", round(s2, 2), "%\n")
##   Volatilidad JNJ : 19.19 %
cat("  Correlación:", round(rho, 3), "\n\n")
##   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:
cat("  Peso", activo_1, ":", round(peso_min_var * 100, 2), "%\n")
##   Peso AAPL : 19 %
cat("  Peso", activo_2, ":", round((1 - peso_min_var) * 100, 2), "%\n")
##   Peso JNJ : 81 %
cat("══════════════════════════════════════════════════════════════\n\n")
## ══════════════════════════════════════════════════════════════
# 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

Figura 11: Frontera Eficiente


15 CONCLUSIONES

15.1 Síntesis de Hallazgos

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.

15.2 Aplicabilidad Práctica

Este framework es directamente aplicable por investigadores académicos, gestores de activos, analistas de riesgo, traders algorítmicos, y estudiantes avanzados en finanzas cuantitativas.

15.3 Limitaciones y Extensiones

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.


16 REFERENCIAS BIBLIOGRÁFICAS

  • Campbell, J. Y., Lo, A. W., & MacKinlay, A. C. (1997). The Econometrics of Financial Markets. Princeton University Press.
  • Tsay, R. S. (2010). Analysis of Financial Time Series (3rd ed.). John Wiley & Sons.
  • Sharpe, W. F. (1966). Mutual fund performance. Journal of Business, 39(1), 119-138.
  • Perlin, M. S. (2020). Processing and analyzing financial data with R.
  • Yahoo Finance. (2024). https://finance.yahoo.com/

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.