library(readr)
library(dplyr)
library(tidyverse)
library(lubridate)
library(mice)
library(ggplot2)
library(plotly)
library(tidyr)
library(skimr)
library(naniar)
library(scales)
library(caret)
library(kknn)
library(class)
library(rpart)
library(rpart.plot)
library(e1071)
library(naivebayes)

pacman::p_load(
  rio, here, janitor, epikit, naniar
)

Introducción:

El mercado de valores es uno de los sistemas más complejos de la la economía moderna. El índice de S&P500, agrupa las 500 empresas de mayor capitulización en Estados Unidos, este es considerado como un termometro que representa la economía americana y, una referencia global del comportamiento financiero. No obstante, aunque en teoría, a largo plazo ofrece un rendimiento promedio de 10% anual, su comportamiento no es homogéneo a lo largo del tiempo: existen periodos de estabilidad, donde lo retornos son estables y la volatibilidad es baja y periodos de turbulencia que son conocidos como periodos de “crisis

La motivación de este proyecto surge de la pregunta financiera si: Es posible, a partir de varible macroeconómicas se puede encontrar en el mercado estados de estabilidad y crisis? Por tal razón, decidimos usar técnicas de minería de datos como: KNN, Arboles de decisión y Clasificador Bayesiano, para aprender patrones que diferencian regímenes el mercado y evaluar que tan bien estos modelos pueden clasificarlos.


Parte 1: Base de datos

Fuente de los datos

Todos los datos utilizados en este proyecto fueron descargados de FRED (Federal Reserve Economic Data),el repositorio de series económicas y financieras de la Reserva Federal de los Estados Unidos. FRED es una fuente primaria, de acceso público utilizada para investigaciones académicas y análisis financiero profesional.

El periodo de análisis que utilizamos fue desde 8 de marzo de 2021 hasta el 27 de febrero de 2026, cubriendo así tanto periodos e recuperación post-pandemia, el ciclo e 2022-2023 con el alzamiento de tasas más agresivo de la Reserva Federal y la estabilización después del mercado. Este rango temporal es particularmente rico porque aunque es corto, contiene episodios bien diferenciados de estabilidad y crisis.

Variables seleccionada y justificación

La selección de cada variable no fue arbitraria . Cada una fue escogida porque representa cierta dimensión distinta dentro del comportamiento del mercado y tiene su rol explicativo dentro de la literatura económica y financiera.

S&P500

Descripción: Valor de cierre diario del índice S&P 500

EL S&P 500 es la variable central del estudio. Representa el nivel del mercado de renta variable estadounidense. Sus valores capturan el sentimiento general de los inversores, las expectativas de crecimiento corporativo y la reacción al nivel de riesgo que representa. Su comportamiento en un nivel elevado puede ser indice de estabilidad o confianza; caídas sostenidas señalan deterioro económico o incertidumbre. Es, en esencia, el objeto de estudio que queremos entender y clasificar.

VIX

Descripción: Índice de volatilidad implícita del mercado, calculado por el CBOE a partir de opciones sobre el S&P 500

El VIX s conocido como el “Indice del miedo”. Este mide la volatibilidad esperada por el mercado.Un VIX bajo (por debajo de 20) indica que los participantes del mercado anticipan un ambiente tranquilo; un VIX por encima de 20 refleja incertidumbre elevada, y valores superiores a 30 corresponden a episodios de alta tensión financiera. Esta variable cumple un rol especial en el proyecto: se utiliza como criterio de clasificación para definir si un periodo es de Estabilidad (VIX < 20) o de Crisis (VIX ≥ 20). Es importante destacar que, precisamente por este motivo, el VIX es excluido como predictor en los modelos de clasificación, lo cual se explica en detalle en la sección de metodología.

TASA

Descripción: Rendimiento del bono del Tesoro de los Estados Unidos a 10 años, expresado en porcentaje

La tasa de interés a largo plazo es uno de los indicadores macroeconómicos más importantes para los mercados financieros. Cuando la Reserva Federal sube la tasa de interes para combatir la inflación (como en el periodo de 2022 al 2023), el costo del crédito corporativo aumenta, las valuaciones de las acciones en el mercado caen, y los bonos se vuelven más competitivos. Esta variable captura el estado de la política monetaria y su presión sobre el mercado de acciones.

INFLA

Descripcción: Tasa de inflación esperada a 10 años, derivada de los valores del Tesoro protegidos contra la inflación

Las expectativas de inflación afectan directamente las decisiones de inversión.Una inflación esperada elevada erosiona el valor real de los retornos futuros, genera presión sobre la Reserva Federal para subir tasas, y aumenta la incertidumbre macroeconómica. Durante el periodo 2021–2023, las expectativas de inflación jugaron un papel central en la volatilidad del S&P 500, lo que hace que esta variable sea especialmente relevante para nuestro análisis.

Dollar

Descripcción: Valor del dólar frente a una canasta de monedas de los principales socios comerciales de Estados Unidos

La relación del dólar y el mercado es ciertamente compleja, un dólar fuerte puede perjudicar a las empresas exportadoras del S&P 500.Pero también, puede reflejar lo que se conoce como un “flight to safety”, la búsqueda de activos refugio en periodos de crisis global.

Diccionario de Variables:

Aquí presentamos el diccionario completo de la base de datos, incluyendo las variables originales y las variables derivadas que se crean durante el proceso de preparación:

Variable Tipo Descripción Unidad
date Fecha Fecha de observación YYYY-MM-DD
SP500 Numérica Valor de cierre del índice S&P 500 Puntos
VIX Numérica Índice de volatilidad implícita Puntos
TASA Numérica Rendimiento del Tesoro a 10 años % anual
INFLA Numérica Expectativas de inflación a 10 años % anual
Dollar Numérica Índice del valor del dólar Índice
retorno_diario Numérica Variación porcentual diaria del SP500 %
volatilidad_movil Numérica Desviación estándar de retornos (ventana 20 días)
anio Entera Año de la observación Año
periodo Factor Clasificación del periodo: Estabilidad / Crisis Categórica

Variables Derivadas

retorno_diario

Se calcula como la variación porcentual del SP500 respecto al día anterior usando lag(). Esta es la medida estándar de retorno en finanzas. Es más informativa que el nivel del índice porque nos dice cuánto ganó o perdió el mercado en un día, independientemente del nivel absoluto del índice.

volatilidad_movil

Es la desviación estándar de los últimos 20 retornos diarios. Una ventana de 20 días equivale aproximadamente a un mes de trading (días hábiles), que es el periodo convencional para medir volatilidad de corto plazo en finanzas. A mayor volatilidad móvil, mayor incertidumbre en el mercado.

periodo

Clasifica cada observación como “Estabilidad” o “Crisis” según si el VIX es menor o mayor que 20. Se eligió este umbral porque es el estándar ampliamente reconocido en la industria financiera: un VIX por debajo de 20 indica condiciones tranquilas, mientras que un VIX de 20 o más señala tensión. Valores superiores a 30 corresponden a episodios severos. Es importante destacar que aunque el VIX forma parte del dataset, se excluye como predictor en los modelos para evitar data leakage, ya que es la misma variable que define la etiqueta que los modelos intentan predecir. Este punto se desarrolla en la sección de metodología.

Descarga Base de Datos aquí:

Descargar ivestmentFinal


Visualización de Datos:

En esta sección presentamos tres gráficos diseñados para entender visualmente el comportamiento del SP500 a lo largo del tiempo y las diferencias entre periodos de Estabilidad y Crisis. Cada gráfico representa ciertas preguntas solo utilizando la base de datos, no hay ningún método de clasificación supervisada impuesta en los modelos.

Gráfico 1 — Evolución del SP500 con periodos de crisis resaltados (Linea del Tiempo)

plot_ly() %>%
  # Línea base completa en gris (toda la serie continua)
  add_lines(data = base_completa,
            x    = ~date,
            y    = ~SP500,
            name = "SP500",
            line = list(color = "#185FA5", width = 1),
            showlegend = FALSE) %>%
  # Puntos rojos encima para marcar los días de Crisis
  add_markers(data   = crisis,
              x      = ~date,
              y      = ~SP500,
              name   = "Crisis",
              marker = list(color = "#E24B4A",
                            size  = 3,
                            opacity = 0.6)) %>%
  layout(
    title  = "Evolución del S&P 500 (2021-2026)",
    xaxis  = list(title      = "Fecha",
                  type       = "date",
                  tickformat = "%Y"),
    yaxis  = list(title = "Valor del índice (puntos)"),
    legend = list(title = list(text = "Periodo"))
  )

Observación:

El gráfico muestra la trayectoria diaria del índice S&P 500 desde marzo 2021 hasta Febrero 2026. La linea azul representa la evolución del índice, mientras los puntos rojos identifican los días clasificados como crisis (VIX ≥ 20). Al observar la gráfica podemos ver tres episodios de Crisis:el primero entre 2021 y principios de 2022, coincidiendo con las primeras señales de presión post-pandemia; luego en el periodo de 2022 al 2023, que corresponde al ciclo más agresivo de la elevación de la tasa de interés, donde se puede ver que el S&P500 cayó casi hasta los 3,500 puntos; y por último el periodo de 2025 al 2026, que puede mostrar turbulencias nuevas, muy probale relacionadas a otras variables macroeconomicas. Después del 2024 se puede ver un movimiento alcista que permitió llegar hasta casi 7000 puntos en el 2026, aún así la gráfica muestra que en ese periodo hay señales de Crisis intercalados. Esta gráfica nos ayuda entender que los periodos de Crisis no son permanentes ni continuos sino episodios que se pueden identificar a lo largo del tiempo.

Gráfica 2:Boxplot de retornos diarios por periodo (Boxplot)

plot_ly(base_completa,
        x     = ~periodo,
        y     = ~retorno_diario,
        color = ~periodo,
        colors = c("Estabilidad" = "#185FA5",
                   "Crisis"      = "#E24B4A")) %>%
  add_boxplot() %>%
  layout(
    title  = "Distribución del retorno diario por periodo",
    xaxis  = list(title = "Periodo"),
    yaxis  = list(title = "Retorno diario (%)"),
    legend = list(title = list(text = "Periodo"))
  )

Observación:

El boxplot compara la distribución del retorno diario del S&P500 entre los periodos de Estabilidad y Crisis. Cuando observamos el boxplot de “Estabilidad” se ve que la caja es más estrecha y más centrada cerca del cero, lo que puede indicar que la mayoría de los días el mercado registra movimientos pequeños. Cuando vemos el boxplot de “Crisis” podemos ver que caja es más ancha y ligeramente a los valores negativos aunque no se nota mucho. Al ver ambos, podemos concluir que son presentaciones atípicas que llegan a más menos 30%. En un contexto de mercado financiero esto puede indicar algo normal ya que los movimientos más extremos pueden ocurrir en cualquier régimen, pero son más frecuentes y más negativos en Crisis.

Gráfico 3 — Dispersión retorno diario vs volatilidad móvil

p_scatter <- ggplot(base_completa,
                    aes(x     = retorno_diario,
                        y     = volatilidad_movil,
                        color = periodo)) +
  geom_point(alpha = 0.5, size = 1.5) +
  geom_smooth(method = "lm", linetype = "dashed", se = TRUE) +
  scale_color_manual(values = c("Estabilidad" = "#185FA5",
                                "Crisis"      = "#E24B4A")) +
  labs(title = "Retorno diario vs Volatilidad móvil (20 días)",
       x     = "Retorno diario (%)",
       y     = "Volatilidad móvil (%)") +
  theme_minimal()

ggplotly(p_scatter)

Observación:

Parte 2: Base de Datos y Códigos utilizados

En esta parte estaremos exponiendo el proceso de codificaciones, desde la formación de la base de datos hasta la aplicación de los tres métodos de clasificación supervisada escogidos para analizar y contestar nuestras preguntas en base a nuestra investigación.

Preparación de Base de Datos:

La base de datos utilizada en este análisis integra cinco series temporales diarias descargadas de FRED (Federal Reserve Economic Data),cubriendo el periodo del 8 de marzo de 2021 al 27 de febrero de 2026. Cada serie fue cargada de forma independiente para facilitar su inspección individual antes de ser integrada.

Paso 1

Descargar datos

Cada dataset se carga por separado desde su archivo CSV original descargado de FRED. Usar head() y dim() permite verificar rápidamente que los datos se cargaron correctamente — head() muestra las primeras filas para confirmar el formato, y dim() informa las dimensiones (filas y columnas) de cada serie.

#Volatibilidad del mercado 
VIX <- read_csv("VIXCLS.csv")

#Valor del SP500 diario 
SP500 <- read_csv("SP500-2.csv")


#Tasa 
TASA <- read_csv("DGS10-2.csv")

#Valor del dolar 
Dollar <- read_csv("Dollar.csv")

# Inflacion 
INFLA <- read_csv("T10YIE.csv")



head(SP500)
head(VIX)
dim(SP500)
## [1] 2606    2
dim(VIX)
## [1] 2652    2
head(TASA)
dim(TASA)
## [1] 2652    2

Paso 2

as.Date() formato de fecha

Antes del merge, todas las columnas de fecha se convierten explícitamente al tipo Date. Este paso es crítico: si una serie tiene la fecha como texto y otra como Date, R no puede alinearlas correctamente y el merge producirá un resultado vacío o con errores. La función as.Date() garantiza que todas las series hablen el mismo formato de fecha.

#Asegurar el formato de las fechas 
SP500$observation_date <- as.Date(SP500$observation_date)
VIX$observation_date   <- as.Date(VIX$observation_date)
TASA$observation_date   <- as.Date(TASA$observation_date)
Dollar$observation_date   <- as.Date(Dollar$observation_date)
INFLA$observation_date   <- as.Date(INFLA$observation_date)

Paso 3

Renombrar columna “Date”

Se estandarizan los nombres de columnas en todos los datasets antes del merge. Todas las series de FRED vienen con la columna de fecha llamada observation_date. Al renombrarla a date de forma uniforme en todos los datasets, el merge por "date" funciona sin ambigüedades.

#Renombrar columnas 
colnames(SP500) <- c("date","SP500")
colnames(VIX) <- c("date","VIX")
colnames(TASA) <- c("date","TASA")
colnames(Dollar) <- c("date","Dollar")
colnames(INFLA) <- c("date","INFLA")

Paso 4

merge()

Se integran las cinco series en un solo dataset usando merge() con by = "date". Se utiliza un merge interno porque queremos conservar únicamente las observaciones donde todas las variables están presentes simultáneamente. Dado que las cinco series pueden tener días distintos.

#Merge los datos 

base <- merge(SP500, VIX, by= "date")
base <- merge(base, TASA, by= "date")
base <- merge(base, INFLA, by= "date")
base <- merge(base, Dollar, by= "date")

head(base)

Procesamiento de Datos Faltantes:

En esta parte vamos a explorar la base de datos e identificar los valores faltantes de la base de datos que acabos de formar. Como estamos trabajando con datos financieros diarios, podemos ver que la base en si tiene varios NAs, debido a que hay días que el mercado no esta operando por días feriados o finde semana, o simplemente la FRED no reportó el valor para esas fechas. Por eso ignorar los datos faltantes nos puede generar errores en la trayectoria de la investigación y resultados. Así que, debemos diagnosticar y luego aplicar alguna estrategia de imputación que persevere la distribución original de los datos.

Paso 1

Ver problemas y datos faltantes

Antes de imputar, es obligatorio ver la magnitud de los datos faltantes dentro de la base, y también entender su patrón. Usaremos gg_miss_var() que nos muestra cuántos NAs tiene cada variable en términos absolutos. Por el otro lado vis_miss() nos presenta un mapa visual que permite detectar si los NAs están concentrados en fechas específicas o distribuidos aleatoriamente. colSums(is.na()) cuantifica el número exacto de faltantes por variable y después lo ponemos en porcentaje.

gg_miss_var(base)

vis_miss(base)

naniar::pct_miss(base)
## [1] 2.987179
colSums(is.na(base))
##   date  SP500    VIX   TASA  INFLA Dollar 
##      0     49     20     55     55     54
colMeans(is.na(base)) * 100
##     date    SP500      VIX     TASA    INFLA   Dollar 
## 0.000000 3.769231 1.538462 4.230769 4.230769 4.153846

Observaciones:

  1. La gráfica de gg_miss_var(base) gg_miss_var()` muestra que las variables con mayor número de NAs son TASA (55), INFLA (55) y Dollar (54), seguidas de SP500 (49) y VIX (20).

  2. El mapa vis_miss() confirma que los NAs representan solo el 3% del total de observaciones y que están distribuidos a lo largo del dataset en fechas puntuales, así que no están concentrados en un bloque continuo. Podemos entender que los NAs corresponden a ciertos días en especificos donde no fueron reportados por la FRED, y no es que hay un problema estructurar o de patrón en la base de datos.

El missigness fue muy bajo (menos de 5%), y los NAs estan dispersados en la base, así que hemos decidido irnos con el método de imputación por media utilizando MICE.

Paso 2

Imputación

Se utiliza MICE, con el método de media (method = "mean") para reemplazar esos valores por el promedio de la variable. Esta decisión es apropiada dado que el porcentaje de datos faltantes es pequeño y el objetivo es preservar la distribución general de los datos.

# Trabajar con procesamiento de datos faltantes (Imputación con Mice)

Imput_mean <- mice(base, method = "mean", print = FALSE)
base_completa <- mice::complete(Imput_mean)

# Verificar que en la base nueva no queden NAs

colSums(is.na(base_completa))
##   date  SP500    VIX   TASA  INFLA Dollar 
##      0      0      0      0      0      0
vis_miss(base_completa)

Luego de aplicar este método Verificamos que la base nueva no tenga más NAs. Al utilizar vis_miss vemos que no tenemos datos faltantes. Pero, para verificar que este método que utilizamos esta correcto debemos completar otro paso adicional.

Paso 3

Comparar la base imputada con la base original

Este paso es importante porque el gráfico de densidad es la validación visual de la imputación. Este se construye combinando la base original y la base imputada en un formato largo con pivot_longer(), lo que permite graficar ambas distribuciones en el mismo panel para cada variable.

data_long <- bind_rows(
  base %>% 
    select(SP500, VIX, TASA, INFLA, Dollar) %>% 
    mutate(Origen = "Original"),
  base_completa %>% 
    select(SP500, VIX, TASA, INFLA, Dollar) %>% 
    mutate(Origen = "Imputado")
) %>%
  pivot_longer(
    cols = c(SP500, VIX, TASA, INFLA, Dollar),
    names_to  = "Variable",
    values_to = "Valor"
  )

# Gráfico de densidad Original e imputado: 


p1 <- ggplot(data_long, 
             aes(x = Valor, color = Origen, fill = Origen,
                 text = paste("Origen:", Origen))) +
  geom_density(alpha = 0.2) +
  facet_wrap(~Variable, scales = "free") +
  labs(title = "Comportamiento de la Imputación",
       x = "Variables", y = "Densidad") +
  theme_minimal()

ggplotly(p1, tooltip = c("text", "x", "y"))

Observaciones:

Cuando vemos estas gráficas en las cinco variables — Dollar, INFLA, SP500, TASA y VIX, las curvas rosa e azul se reflejan casi perfectamente, lo que indica que la imputación no alteró la forma ni la tendencia central de ninguna distribución.Era esperado ya que el porcentaje de NAs era bajo. Concluyendo que la imputación por media es válida para este dataset.

Creación de Variables:

Crear versiones del dataset y dividir:

Aplicacación de los tres modelos:

Validación cruzada y comparación: