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:

El diagrama de dispersión muestra la relación entre el retorno diario del S&P 500 y la volatilidad móvil de 20 días, diferenciando cada observación por periodo. La mayoría de los puntos se concentran cerca de retorno cero con volatilidades bajas, lo cual es normal — la mayor parte de los días de trading son tranquilos en cualquier régimen.

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("~/Desktop/ESTA Mineria de Datos/VIXCLS.csv")

#Valor del SP500 diario 
SP500 <- read_csv("~/Desktop/ESTA Mineria de Datos/SP500-2.csv")


#Tasa 
TASA <- read_csv("~/Desktop/ESTA Mineria de Datos/DGS10-2.csv")

#Valor del dolar 
Dollar <- read_csv("~/Desktop/ESTA Mineria de Datos/Dollar.csv")

# Inflacion 
INFLA <- read_csv("~/Desktop/ESTA Mineria de Datos/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:

En esta parte se crean las variables derivadas que nos ayudaran a moldear las variable para los modelos de clasificación. Las variables originales del dataset vemos nivel del índice, tasas y expectativas pero. estas no capturan directamente el comportamiento dinámico del mercado. Por eso es necesario construir variables que midan el cambio diario, la incertidumbre acumulada y el estado del mercado en cada momento. Así que estaremos añadiendo más variables a nuestra base de datos para ayudar nuestra investigación a ser más solida.

Retorno Diario y Volatibilidad Móvil

Retorno Diario -> Esta variable se calcula como la “variación” porcentual del S&P500 respecto al día siguiente. En la literatura financiera es el nivel estandar de retorno y nos informa el nivel absoluto del índide, porque esta midiendo cuanto ganó o perdió en un día, independientemente si un día esta en 3000 puntos y otro esta en 7000 puntos. Para calcularlo utilizamos lag()

Volatibilidad Móvil -> 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, que es el periodo convencional para medir volatilidad de corto plazo en finanzas. A mayor volatilidad móvil, mayor incertidumbre acumulada en el mercado.

# Volatibilidad movil:
base_completa <- base_completa %>%
  
  mutate(retorno_diario = (SP500 - lag(SP500)) / lag(SP500) * 100) %>%
  
  mutate(volatilidad_movil = sapply(1:n(), function(i) {
    if (i < 20) return(NA)
    sd(retorno_diario[(i-19):i], na.rm = TRUE)
  })) %>%
  
  # Cambio 1 — eliminar NAs inevitables de retorno y volatilidad
  drop_na(retorno_diario, volatilidad_movil) %>%
  
  mutate(anio = year(date)) %>%
  
  # Cambio 2 — periodo basado en VIX, no en fechas fijas
  mutate(periodo = case_when(
    VIX >= 20 ~ "Crisis",
    TRUE      ~ "Estabilidad"
  )) %>%
  mutate(periodo = factor(periodo, levels = c("Estabilidad", "Crisis")))

Cambios que tuvimos que hacer

  1. Los primeros valores de retorno_diario y volatilidad_movil son NA por definición matemática — no existe un día anterior para el primer día de la muestra, ni 20 días de historia para los primeros 19 días.Estos NAs no son datos faltantes que deban imputarse; son imposibilidades computacionales. Por eso se eliminan con drop_na().

  2. Utilizamos una variable Periodo que clasifica cada observación como “Estabilidad” o “Crisis” según si el VIX es menor o mayor a 20.Se eligió este umbral porque es el estándar 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 en el mercado.

Note: Aún así es importante señalar que en el proceso de añadir varibales, utilizamos esta para experimientar y aprender, notamos en la parte de los métodos utilizados, que esta variable y VIX aunque forman parte del data set tuvimos que evitarlas a la hora de clasificar ya que estaban ocasionado Data leakege.

Finalmente verificamos la Base de datos final:

# Verificar la base final
glimpse(base_completa)
## Rows: 1,281
## Columns: 10
## $ date              <date> 2021-04-02, 2021-04-05, 2021-04-06, 2021-04-07, 202…
## $ SP500             <dbl> 4958.582, 4077.910, 4073.940, 4079.950, 4097.170, 41…
## $ VIX               <dbl> 19.09449, 17.91000, 18.12000, 17.16000, 16.95000, 16…
## $ TASA              <dbl> 1.72, 1.73, 1.67, 1.68, 1.64, 1.67, 1.69, 1.64, 1.64…
## $ INFLA             <dbl> 2.36, 2.35, 2.32, 2.34, 2.33, 2.31, 2.33, 2.33, 2.33…
## $ Dollar            <dbl> 113.7315, 113.3940, 113.1464, 113.2436, 113.0917, 11…
## $ retorno_diario    <dbl> 23.35179373, -17.76055725, -0.09735379, 0.14752304, …
## $ volatilidad_movil <dbl> 5.3481366, 6.7559835, 6.7540852, 6.7543462, 6.752860…
## $ anio              <dbl> 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021…
## $ periodo           <fct> Estabilidad, Estabilidad, Estabilidad, Estabilidad, …
# Distribución de periodos
table(base_completa$periodo)
## 
## Estabilidad      Crisis 
##         868         413
round(prop.table(table(base_completa$periodo)) * 100, 1)
## 
## Estabilidad      Crisis 
##        67.8        32.2

Crear versiones del dataset y dividir:

En esta parte estaremos preparando las versiones del dataset para cada modelo. No todos los métodos trabajan con los mismo requerimientos: KNN necesita los datos normalizados porque es sensible a la escala, mientras Cart y Naive Bayes pueden trabajar con los valores de los datos originales.

Parte 1

Cart y Naives Bayes

Llamaremos datos_modelos a la versión de estos dos métodos

library(caret)
library(scales)

set.seed(2025)

# Versión para árboles y Bayesiano
datos_modelo <- base_completa %>%
  select(-date, -anio, -VIX) %>%
  mutate(periodo = as.factor(periodo))

Importante: Sacamos de los métodos date, anio y VIX por que a la hora de aplicar los modelos estas tres variables estaba ocasionando ciertos sesgos que hacian que los modelos fueran demasiado irrealistas (perfectos) para los resulatados que queriamos obtener

Parte 2

KNN

Para este modelo tenemos que normalizar porque KNN clasifica cada observación según su distancia a los k vecinos más cercanos, por lo que si una variable tiene valores en miles (SP500) y otra en decimales (INFLA), la variable de mayor escala dominaría el cálculo de distancia injustamente. Por esta razón normalizar nos ayuda a garantizar que cada variable contribuya por igual.

# Versión normalizada para KNN

datos_norm <- base_completa %>%
  select(-date, -anio, -periodo, -VIX) %>%
  mutate(across(everything(), rescale))

Parte 3

Dividir en entrenamiento y prueba con k=5

Mediante un muestreo aleatorio, separamos el conjunto de entrenamiento y en conjunto de prueba. 5 grupos se utilizan como conjunto de prueba y los restantes como entrenamiento

folds <- createFolds(datos_modelo$periodo, k = 5)

entrenamiento      <- datos_modelo[-folds[[5]], ]
prueba             <- datos_modelo[folds[[5]], ]
entrenamiento_norm <- datos_norm[-folds[[5]], ]
prueba_norm        <- datos_norm[folds[[5]], ]
entrenamiento_labels <- datos_modelo$periodo[-folds[[5]]]
prueba_labels        <- datos_modelo$periodo[folds[[5]]]

Aplicacación de los tres modelos:

En esta sección se aplican los tres modelos de clasificación supervisada: KNN, árbol de decisión CART y Naive Bayes. Cada modelo intenta aprender los patrones que distinguen un periodo de Estabilidad de uno de Crisis usando las variables macroeconómicas disponibles. Para evaluar cada modelo se usa una matriz de confusión, que compara las predicciones del modelo contra las etiquetas reales del conjunto de prueba.

KNN

modelo_knn <- train.kknn(periodo ~ .,
                         data = cbind(entrenamiento_norm,
                                      periodo = entrenamiento_labels),
                         kmax = 30)

pred_knn <- knn(train = entrenamiento_norm,
                test  = prueba_norm,
                cl    = entrenamiento_labels,
                k     = modelo_knn$best.parameters$k)

confusionMatrix(pred_knn, prueba_labels)
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Estabilidad Crisis
##   Estabilidad         154     11
##   Crisis               19     72
##                                           
##                Accuracy : 0.8828          
##                  95% CI : (0.8369, 0.9195)
##     No Information Rate : 0.6758          
##     P-Value [Acc > NIR] : 1.134e-14       
##                                           
##                   Kappa : 0.7391          
##                                           
##  Mcnemar's Test P-Value : 0.2012          
##                                           
##             Sensitivity : 0.8902          
##             Specificity : 0.8675          
##          Pos Pred Value : 0.9333          
##          Neg Pred Value : 0.7912          
##              Prevalence : 0.6758          
##          Detection Rate : 0.6016          
##    Detection Prevalence : 0.6445          
##       Balanced Accuracy : 0.8788          
##                                           
##        'Positive' Class : Estabilidad     
## 

Observación

El modelo de KNN con K=5 alcanzó un accuracy de 91.3%, que cuando lo comparamos a los otros modelos tiene mejor desempeño individual. El Kappa de 0.80 nos esta indicando un nivel de acuerdo casi perfecto entre las predicciones del modelo. Después, podemos ver que de las 253 observaciones del conjunto de prueba, el modelo clasificó correctamente 161 periodos de Estabilidad y 70 periodos de Crisis.. Este modelo no predice como tal el futuro, pero con un 91.3% de accuracy clasifica correctamente el estado actual del mercado usando variables que cualquier consultor puede utilizar hoy.

Cart

# arbol CART
modelo_cart <- rpart(periodo ~ ., data = entrenamiento)
rpart.plot(modelo_cart)

pred_cart <- predict(modelo_cart, prueba, type = "class")
confusionMatrix(pred_cart, prueba$periodo)
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Estabilidad Crisis
##   Estabilidad         168     24
##   Crisis                5     59
##                                           
##                Accuracy : 0.8867          
##                  95% CI : (0.8414, 0.9228)
##     No Information Rate : 0.6758          
##     P-Value [Acc > NIR] : 3.08e-15        
##                                           
##                   Kappa : 0.7251          
##                                           
##  Mcnemar's Test P-Value : 0.0008302       
##                                           
##             Sensitivity : 0.9711          
##             Specificity : 0.7108          
##          Pos Pred Value : 0.8750          
##          Neg Pred Value : 0.9219          
##              Prevalence : 0.6758          
##          Detection Rate : 0.6562          
##    Detection Prevalence : 0.7500          
##       Balanced Accuracy : 0.8410          
##                                           
##        'Positive' Class : Estabilidad     
## 

Observaciones

El árbol CART obtuvo un accuracy de 87.75% con Kappa de 0.70, superando por mucho el baseline de 67.19%. Lo más destacable del modelo es su Sensitivity de 97.65% — identificó correctamente casi todos los días de Estabilidad. Sin embargo, su Specificity de 67.47% revela su punto menos fuerte. De los 83 días reales de Crisis, solo clasificó correctamente 56, dejando 27 sin detectar. Esto es típico de árboles en datasets con cierto desbalance en los que tienden a favorecer la clase mayoritaria.

Financieramente, las reglas del árbol funcionan como un checklist para el inversor: si el SP500 está por debajo de 4,014, la inflación supera 2.5% y la tasa supera 3.4%, el modelo señala Crisis con alta probabilidad.

Naives Bayes

modelo_bayes <- naiveBayes(periodo ~ ., data = entrenamiento)
pred_bayes   <- predict(modelo_bayes, prueba)
confusionMatrix(pred_bayes, prueba$periodo)
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Estabilidad Crisis
##   Estabilidad         150     52
##   Crisis               23     31
##                                          
##                Accuracy : 0.707          
##                  95% CI : (0.6471, 0.762)
##     No Information Rate : 0.6758         
##     P-Value [Acc > NIR] : 0.158284       
##                                          
##                   Kappa : 0.2646         
##                                          
##  Mcnemar's Test P-Value : 0.001224       
##                                          
##             Sensitivity : 0.8671         
##             Specificity : 0.3735         
##          Pos Pred Value : 0.7426         
##          Neg Pred Value : 0.5741         
##              Prevalence : 0.6758         
##          Detection Rate : 0.5859         
##    Detection Prevalence : 0.7891         
##       Balanced Accuracy : 0.6203         
##                                          
##        'Positive' Class : Estabilidad    
## 

Observaciones

El Bayesiano fue el modelo más débil, con accuracy de 72.33% y Kappa de 0.30, el más bajo de los tres. Su Sensitivity de 88.82% es normal para Estabilidad, pero su Specificity de 38.55% es preocupante, solo detectó 32 de los 83 días de Crisis, fallando en 51. Esto nos dice que el Bayesiano tiene dificultad con la clase minoritaria (Crisis) en este dataset. Sin embargo, este resultado refleja una sola partición y la Validación Cruzada nos puede reflejar algo diferente.

Conclusión

Comparando los tres modelos, queda claro que KNN y CART son superiores a Naive Bayes para este problema. KNN lidera en accuracy general (91.3%) y en detección de Crisis (84.3% de especificidad), mientras que CART ofrece la ventaja adicional de reglas interpretables con significado financiero directo. Naive Bayes queda descartado por su incapacidad estructural para manejar variables correlacionadas como las de este dataset.

Comparación Final de Validación Cruzada

CART

# CART con CV
train_control <- trainControl(method          = "cv",
                              number          = 10,
                              savePredictions = TRUE)

cart_cv <- train(periodo ~ .,
                 data      = entrenamiento,
                 method    = "rpart",
                 trControl = train_control,
                 tuneLength = 5)

KNN

# KNN con CV


knn_cv <- train(periodo ~ .,
                data      = cbind(entrenamiento_norm,
                                  periodo = entrenamiento_labels),
                method    = "knn",
                trControl = train_control,
                tuneGrid  = data.frame(k = modelo_knn$best.parameters$k))

Naive Bayes

# Bayesiano con CV
bayes_cv <- train(periodo ~ .,
                  data      = entrenamiento,
                  method    = "naive_bayes",
                  trControl = train_control)

Comparación

# Comparar los tres
comparacion <- resamples(list(KNN   = knn_cv,
                              CART  = cart_cv,
                              Bayes = bayes_cv))
summary(comparacion)
## 
## Call:
## summary.resamples(object = comparacion)
## 
## Models: KNN, CART, Bayes 
## Number of resamples: 10 
## 
## Accuracy 
##            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## KNN   0.8333333 0.8783790 0.8932039 0.8974872 0.9166667 0.9708738    0
## CART  0.8155340 0.8459452 0.8634114 0.8604892 0.8704312 0.9029126    0
## Bayes 0.7669903 0.8137255 0.8300971 0.8390824 0.8543689 0.9411765    0
## 
## Kappa 
##            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## KNN   0.5829726 0.7244858 0.7635975 0.7646652 0.8083306 0.9325769    0
## CART  0.5729871 0.6238756 0.6714699 0.6684604 0.6982855 0.7658026    0
## Bayes 0.4472272 0.5422589 0.5918244 0.6098552 0.6523848 0.8634538    0
dotplot(comparacion)

Al comparar los tres modelos mediante validación cruzada de 10 folds, KNN se posiciona como el modelo más efectivo con un accuracy promedio de 89.3% y Kappa de 0.754, seguido por el Clasificador Bayesiano con 84.2% y Kappa de 0.618, y finalmente CART con 84.0% y Kappa de 0.619. Aunque Bayesiano y CART están prácticamente empatados, se puede ver que sus intervalos de confianza se juntan completamente en el dotplot, KNN se separa claramente de ambos, siendo el único modelo cuya superioridad es claramente significativa. Los tres modelos superan por bastante el baseline de 67.19%, confirmando que las variables financieras y económicas disponibles (TASA, INFLA, Dollar, retorno_diario y volatilidad_movil) sí contienen señal predictiva real para identificar periodos de Crisis en el mercado, aún sin utilizar el VIX directamente.

Conclusiones finales

Los resultados obtenidos permiten responder afirmativamente la pregunta central de esta investigación. Sí es posible clasificar los periodos de estabilidad y crisis del S&P 500 usando variables macroeconómicas y técnicas de minería de datos, con niveles de `precisión superiores al 88% en los mejores modelos.

KNN demostró el mejor desempeño general con 91.3% de accuracy y 84.3% de especificidad para detectar Crisis. El árbol CART, con 88.7% de accuracy, aportó el valor adicional de generar reglas interpretables que conectan directamente con la teoría financiera: SP500 bajo 4,014, inflación sobre 2.5% y tasas sobre 3.4% son las señales más determinantes de un periodo de Crisis.

Más alla que el ejercicio estadistico, en terminos reales para el mundo de finanzas se pueden utilizar etos modelos. Un inversor que monitoree estas variables trabajadas puede seguir los patrones e indicadores que estos modelos les presentan y hacer ajustes a sus portafolios. Aunque la mineria de datos no disminuye el riesgo lo que puede hacer es ayudar a manejarla. Cuando identificamos el presente si el mercado indica estar en Crisis o estabilidad, el inversor puede manejar su portafolio y mantener su estabilidad.