## 2.1 Precio del oro
precio <- read.csv("gold_2010_2025.csv", na.strings = c("", "NA"))
## 2.2 Índice del dólar
indice_dolar <- read.csv("Indice_Dolar.csv", na.strings = c("", "NA"))
## 2.3 Inflación
inflacion <- read.csv("Inflacion.csv", na.strings = c("", "NA"))
## 2.4 Tasa de interés
tasa_interes <- read.csv("Tasa_Interes.csv", na.strings = c("", "NA"))
head(precio)
## Date Open High Low Close Volume
## 1 2010-01 1117.7 1161.2 1073.2 1083.0 320664
## 2 2010-02 1081.0 1127.4 1045.2 1118.3 18209
## 3 2010-03 1119.3 1145.0 1085.5 1113.3 222583
## 4 2010-04 1125.1 1181.3 1111.3 1180.1 10917
## 5 2010-05 1178.6 1246.5 1159.2 1212.2 361661
## 6 2010-06 1224.8 1264.8 1196.9 1245.5 16703
Esta base de datos nos permite identificar tendencias y determinar cuándo ocurren oportunidades de compra o venta.
head(indice_dolar)
## Date Open High Low Close
## 1 2010-01 77.93 79.50 76.60 79.46
## 2 2010-02 79.43 81.34 78.68 80.36
## 3 2010-03 80.33 82.24 79.51 81.07
## 4 2010-04 81.04 82.71 80.03 81.87
## 5 2010-05 81.80 87.46 81.78 86.59
## 6 2010-06 86.58 88.71 85.09 86.02
Nos ayuda a comparar el comportamiento del dólar con el precio del oro y a visualizar cuándo puede ser mejor vender o comprar. Esto se debe a que el oro y el dólar suelen tener una relación inversa: cuando el dólar sube, el precio del oro tiende a bajar.
head(inflacion)
## Date Inflacion
## 1 2010-01 217.488
## 2 2010-02 217.281
## 3 2010-03 217.353
## 4 2010-04 217.403
## 5 2010-05 217.290
## 6 2010-06 217.199
El oro se considera un activo refugio, por lo que suele aumentar cuando hay alta inflación.
head(tasa_interes)
## Date Interes
## 1 2010-01 0.11
## 2 2010-02 0.13
## 3 2010-03 0.16
## 4 2010-04 0.20
## 5 2010-05 0.20
## 6 2010-06 0.18
Cuando las tasas suben, el oro suele bajar porque los inversionistas prefieren activos que generan rendimiento.
library(naniar)
missing_table <- data.frame(
Base = c("Precio del oro", "Indice Dolar (DXY)",
"Inflacion (CPI)", "Tasa de interes"),
Meses_faltantes = c(sum(is.na(precio$Close)),
sum(is.na(indice_dolar$Close)),
sum(is.na(inflacion$Inflacion)),
sum(is.na(tasa_interes$Interes)))
)
# Porcentaje sobre todas las celdas de cada base (igual que pct_miss)
missing_table$Porcentaje <- round(c(pct_miss(precio),
pct_miss(indice_dolar),
pct_miss(inflacion),
pct_miss(tasa_interes)), 2)
missing_table
## Base Meses_faltantes Porcentaje
## 1 Precio del oro 27 11.72
## 2 Indice Dolar (DXY) 0 0.00
## 3 Inflacion (CPI) 1 0.26
## 4 Tasa de interes 0 0.00
Se detecta que el precio del oro tiene un 11.7% de datos faltantes (135 celdas, equivalentes a 27 meses). El CPI tiene solo 1 valor faltante. El dólar y la tasa están completos. Esto indica que hay que imputar antes de modelar.
library(dplyr)
library(zoo)
precio1 <- precio %>%
arrange(Date) %>%
mutate(across(where(is.numeric),
~ na.approx(., na.rm = FALSE)))
sum(is.na(precio1))
## [1] 0
Se rellenan los valores faltantes mediante interpolación lineal (na.approx). El resultado 0 confirmando que ya no quedan NA.
library(dplyr)
oro_df <- precio1 %>%
rename(oro_open = Open, oro_high = High, oro_low = Low,
oro_close = Close, oro_volume = Volume)
dxy_df <- indice_dolar %>%
rename(dxy_open = Open, dxy_high = High, dxy_low = Low, dxy_close = Close)
cpi_df <- inflacion %>%
rename(cpi = Inflacion)
fed_df <- tasa_interes %>%
rename(fedfunds = Interes)
# Imputamos el único NA de inflación
cpi_df <- cpi_df %>%
arrange(Date) %>%
mutate(cpi = zoo::na.approx(cpi, na.rm = FALSE))
# Unimos las 4 tablas por la columna Date
datos <- oro_df %>%
inner_join(dxy_df, by = "Date") %>%
inner_join(cpi_df, by = "Date") %>%
inner_join(fed_df, by = "Date") %>%
arrange(Date)
# Verificación
head(datos)
## Date oro_open oro_high oro_low oro_close oro_volume dxy_open dxy_high
## 1 2010-01 1117.7 1161.2 1073.2 1083.0 320664 77.93 79.50
## 2 2010-02 1081.0 1127.4 1045.2 1118.3 18209 79.43 81.34
## 3 2010-03 1119.3 1145.0 1085.5 1113.3 222583 80.33 82.24
## 4 2010-04 1125.1 1181.3 1111.3 1180.1 10917 81.04 82.71
## 5 2010-05 1178.6 1246.5 1159.2 1212.2 361661 81.80 87.46
## 6 2010-06 1224.8 1264.8 1196.9 1245.5 16703 86.58 88.71
## dxy_low dxy_close cpi fedfunds
## 1 76.60 79.46 217.488 0.11
## 2 78.68 80.36 217.281 0.13
## 3 79.51 81.07 217.353 0.16
## 4 80.03 81.87 217.403 0.20
## 5 81.78 86.59 217.290 0.20
## 6 85.09 86.02 217.199 0.18
Las cuatro bases de datos se combinan en una sola tabla por la columna de fecha.
datos <- datos %>%
mutate(
ret_oro = c(NA, diff(log(oro_close))),
ret_dxy = c(NA, diff(log(dxy_close))),
inflacion_yoy = (cpi / lag(cpi, 12) - 1) * 100,
tasa_real = fedfunds - inflacion_yoy,
ma12_oro = rollmean(oro_close, k = 12, fill = NA, align = "right"),
tend_oro = oro_close / ma12_oro - 1,
vol_oro = rollapply(ret_oro, width = 6, FUN = sd,
fill = NA, align = "right"),
amplitud_oro = (oro_high - oro_low) / oro_open,
ret_oro_lag1 = lag(ret_oro, 1),
ret_dxy_lag1 = lag(ret_dxy, 1)
)
# Verificación
dim(datos)
## [1] 192 22
head(datos[, c("Date", "oro_close", "ret_oro", "ma12_oro", "tend_oro")])
## Date oro_close ret_oro ma12_oro tend_oro
## 1 2010-01 1083.0 NA NA NA
## 2 2010-02 1118.3 0.032074707 NA NA
## 3 2010-03 1113.3 -0.004481097 NA NA
## 4 2010-04 1180.1 0.058270603 NA NA
## 5 2010-05 1212.2 0.026837710 NA NA
## 6 2010-06 1245.5 0.027100165 NA NA
tail(datos[, c("Date", "ret_oro", "tend_oro", "vol_oro", "tasa_real")])
## Date ret_oro tend_oro vol_oro tasa_real
## 187 2025-07 0.0006530736 0.1229961 0.04096628 1.587382
## 188 2025-08 0.0533605719 0.1524562 0.04117427 1.391408
## 189 2025-09 0.1004603699 0.2325136 0.04252496 1.197428
## 190 2025-10 0.0361537522 0.2367497 0.04118430 1.231282
## 191 2025-11 0.0575977729 0.2591940 0.03805686 1.183556
## 192 2025-12 0.0251186558 0.2420709 0.03389031 1.066696
Se construyen las 7 variables predictoras (retornos, tendencia, volatilidad, inflación interanual, tasa real, amplitud). Estas variables capturan las dinámicas del mercado que los datos crudos no muestran directamente.
datos <- datos %>%
mutate(
ret_oro_futuro = lead(ret_oro, 1),
Signal = factor(ifelse(ret_oro_futuro > 0, "Compra", "Venta"),
levels = c("Venta", "Compra"))
)
datos_modelo <- datos %>%
tidyr::drop_na(Signal, ret_oro_lag1, tend_oro, vol_oro,
tasa_real, inflacion_yoy, ret_dxy_lag1)
dim(datos_modelo)
## [1] 179 24
cat("Período del dataset modelable:",
as.character(min(datos_modelo$Date)), "a",
as.character(max(datos_modelo$Date)))
## Período del dataset modelable: 2011-01 a 2025-11
table(datos_modelo$Signal)
##
## Venta Compra
## 80 99
round(prop.table(table(datos_modelo$Signal)) * 100, 1)
##
## Venta Compra
## 44.7 55.3
head(datos_modelo[, c("Date", "oro_close", "ret_oro_futuro", "Signal")])
## Date oro_close ret_oro_futuro Signal
## 1 2011-01 1333.80 0.05506112 Compra
## 2 2011-02 1409.30 0.02078581 Compra
## 3 2011-03 1438.90 0.07823949 Compra
## 4 2011-04 1556.00 -0.01740640 Venta
## 5 2011-05 1529.15 -0.01771476 Venta
## 6 2011-06 1502.30 0.08053926 Compra
Se crea la variable objetivo Signal: cada mes se etiqueta como Compra o Venta. Tras eliminar los NA estructurales, quedan 179 observaciones modelables. El balance es 55% Compra / 45% Venta.
library(ggplot2)
datos_modelo$Fecha <- as.Date(paste0(datos_modelo$Date, "-01"))
g1 <- ggplot(datos_modelo, aes(x = Fecha, y = oro_close)) +
geom_line(color = "grey40") +
geom_point(aes(color = Signal), size = 1.3, alpha = 0.85) +
scale_color_manual(values = c("Venta" = "blue", "Compra" = "green")) +
labs(title = "Precio del oro y señales mensuales (2011-2025)",
y = "Precio de cierre (USD)",
x = "Fecha",
color = "Señal") +
theme_minimal(base_size = 12)
print(g1)
Muestra la evolución del precio del oro 2011-2025, con cada mes
coloreado según su señal. Se observa un período lateral 2013-2018 y una
fuerte tendencia alcista desde 2020.
library(tidyr) # para pivot_longer
vars_box <- c("ret_oro_lag1", "ret_dxy_lag1", "tend_oro",
"vol_oro", "tasa_real", "inflacion_yoy")
g2 <- datos_modelo %>%
select(Signal, all_of(vars_box)) %>%
pivot_longer(-Signal, names_to = "Variable", values_to = "Valor") %>%
ggplot(aes(x = Signal, y = Valor, fill = Signal)) +
geom_boxplot(alpha = 0.7, outlier.size = 0.6) +
facet_wrap(~ Variable, scales = "free_y") +
scale_fill_manual(values = c("Venta" = "blue", "Compra" = "green")) +
labs(title = "Distribución de predictores por tipo de señal",
x = NULL, y = NULL) +
theme_minimal(base_size = 11) +
theme(legend.position = "none")
print(g2)
Compara la distribución de cada predictor entre los meses de Compra y de
Venta. Permite ver visualmente qué variables diferencian ambas
clases.
library(corrplot)
vars_corr <- c("ret_oro_lag1", "ret_dxy_lag1", "tend_oro",
"vol_oro", "amplitud_oro", "inflacion_yoy", "tasa_real")
mat_cor <- cor(datos_modelo[, vars_corr], use = "complete.obs")
round(mat_cor, 2)
## ret_oro_lag1 ret_dxy_lag1 tend_oro vol_oro amplitud_oro
## ret_oro_lag1 1.00 -0.43 0.49 0.03 0.23
## ret_dxy_lag1 -0.43 1.00 -0.22 -0.03 0.01
## tend_oro 0.49 -0.22 1.00 -0.03 0.20
## vol_oro 0.03 -0.03 -0.03 1.00 0.42
## amplitud_oro 0.23 0.01 0.20 0.42 1.00
## inflacion_yoy 0.01 0.08 0.01 -0.07 -0.05
## tasa_real 0.15 -0.12 0.32 -0.16 -0.02
## inflacion_yoy tasa_real
## ret_oro_lag1 0.01 0.15
## ret_dxy_lag1 0.08 -0.12
## tend_oro 0.01 0.32
## vol_oro -0.07 -0.16
## amplitud_oro -0.05 -0.02
## inflacion_yoy 1.00 -0.64
## tasa_real -0.64 1.00
corrplot(mat_cor,
method = "color",
type = "upper",
tl.col = "black",
tl.cex = 0.8,
addCoef.col = "black",
number.cex = 0.7,
title = "Correlación entre variables predictoras",
mar = c(0, 0, 1.5, 0))
Mide la relación lineal entre las variables predictoras. Destaca la
correlación inversa oro-dólar (−0.43) y la de inflación-tasa real
(−0.64).
set.seed(2025)
library(caret)
folds <- createFolds(datos_modelo$Signal, k = 5)
entrenamiento <- datos_modelo[c(folds$Fold1, folds$Fold2, folds$Fold3, folds$Fold4), ]
prueba <- datos_modelo[folds$Fold5, ]
dim(entrenamiento)
## [1] 143 25
dim(prueba)
## [1] 36 25
Los datos se divideron 143 meses de entrenamiento y 36 meses de prueba.
# Lista de variables predictoras
predictores <- c("ret_oro_lag1", "ret_dxy_lag1",
"tend_oro", "vol_oro", "amplitud_oro",
"inflacion_yoy", "tasa_real")
medias <- sapply(entrenamiento[, predictores], mean)
desvi <- sapply(entrenamiento[, predictores], sd)
# Aplicamos la estandarización a ambos conjuntos
entrenamiento_z <- as.data.frame(scale(entrenamiento[, predictores],
center = medias, scale = desvi))
prueba_z <- as.data.frame(scale(prueba[, predictores],
center = medias, scale = desvi))
# Verificación
head(round(entrenamiento_z, 2))
## ret_oro_lag1 ret_dxy_lag1 tend_oro vol_oro amplitud_oro inflacion_yoy
## 4 0.34 -0.70 1.51 0.60 1.26 0.14
## 7 -0.58 -0.26 1.30 0.41 1.08 0.39
## 8 1.77 -0.30 2.45 1.10 3.72 0.48
## 13 -2.76 1.08 -0.08 3.52 1.08 0.11
## 18 -0.96 2.57 -0.76 -0.01 -0.28 -0.55
## 19 0.46 -0.87 -0.48 -0.23 -0.18 -0.67
## tasa_real
## 4 -0.70
## 7 -0.92
## 8 -0.98
## 13 -0.68
## 18 -0.09
## 19 0.01
Se estandarizan los predictores.
library(class)
modelo_knn <- knn(train = entrenamiento_z,
test = prueba_z,
cl = entrenamiento$Signal,
k = 5)
# Ver las primeras predicciones
head(modelo_knn)
## [1] Venta Compra Compra Venta Compra Compra
## Levels: Venta Compra
Se aplica por primera vez el algoritmo KNN con un valor inicial de k = 5, a modo de prueba. La función head() muestra las primeras seis predicciones del modelo sobre el conjunto de prueba, confirmando que el algoritmo clasifica correctamente cada mes como Compra o Venta.
k_valores <- seq(3, 21, by = 2)
exactitud_k <- numeric(length(k_valores))
for (i in seq_along(k_valores)) {
pred <- knn(train = entrenamiento_z,
test = prueba_z,
cl = entrenamiento$Signal,
k = k_valores[i])
exactitud_k[i] <- mean(pred == prueba$Signal)
cat("k =", k_valores[i], "- Exactitud:", round(exactitud_k[i], 4), "\n")
}
## k = 3 - Exactitud: 0.5833
## k = 5 - Exactitud: 0.5
## k = 7 - Exactitud: 0.5
## k = 9 - Exactitud: 0.5278
## k = 11 - Exactitud: 0.5556
## k = 13 - Exactitud: 0.5833
## k = 15 - Exactitud: 0.5556
## k = 17 - Exactitud: 0.5833
## k = 19 - Exactitud: 0.5833
## k = 21 - Exactitud: 0.5833
# El mejor k
k_optimo <- k_valores[which.max(exactitud_k)]
cat("Mejor k:", k_optimo, "con exactitud de",
round(max(exactitud_k), 4), "\n")
## Mejor k: 3 con exactitud de 0.5833
Se prueban valores de K del 3 al 21. Varios empatan en 58.3% de exactitud; se elige K=3 por ser el más simple..
library(caret)
# Modelo final con el k óptimo
modelo_knn <- knn(train = entrenamiento_z,
test = prueba_z,
cl = entrenamiento$Signal,
k = k_optimo)
# Matriz de confusión
cm_knn <- confusionMatrix(modelo_knn, prueba$Signal, positive = "Compra")
cm_knn
## Confusion Matrix and Statistics
##
## Reference
## Prediction Venta Compra
## Venta 9 8
## Compra 7 12
##
## Accuracy : 0.5833
## 95% CI : (0.4076, 0.7449)
## No Information Rate : 0.5556
## P-Value [Acc > NIR] : 0.436
##
## Kappa : 0.1615
##
## Mcnemar's Test P-Value : 1.000
##
## Sensitivity : 0.6000
## Specificity : 0.5625
## Pos Pred Value : 0.6316
## Neg Pred Value : 0.5294
## Prevalence : 0.5556
## Detection Rate : 0.3333
## Detection Prevalence : 0.5278
## Balanced Accuracy : 0.5813
##
## 'Positive' Class : Compra
##
El KNN acertó 21 de 36 casos de prueba (9 Venta, 12 Compra). Obtuvo una exactitud de 58.3%, sensibilidad de 60% y Kappa de 0.162, resultados casi idénticos a los del Árbol.
library(rpart)
library(rpart.plot)
modelo_arbol <- rpart(Signal ~ .,
data = entrenamiento[, c(predictores, "Signal")],
method = "class")
rpart.plot(modelo_arbol)
pred_arbol <- predict(modelo_arbol, prueba, type = "class")
El árbol construye reglas jerárquicas. Su nodo raíz es inflacion_yoy >= 8.1, lo que identifica la inflación interanual como el predictor más decisivo. Alcanza la misma exactitud que KNN (58.3%).
cm_arbol <- confusionMatrix(pred_arbol, prueba$Signal, positive = "Compra")
cm_arbol
## Confusion Matrix and Statistics
##
## Reference
## Prediction Venta Compra
## Venta 8 7
## Compra 8 13
##
## Accuracy : 0.5833
## 95% CI : (0.4076, 0.7449)
## No Information Rate : 0.5556
## P-Value [Acc > NIR] : 0.436
##
## Kappa : 0.1509
##
## Mcnemar's Test P-Value : 1.000
##
## Sensitivity : 0.6500
## Specificity : 0.5000
## Pos Pred Value : 0.6190
## Neg Pred Value : 0.5333
## Prevalence : 0.5556
## Detection Rate : 0.3611
## Detection Prevalence : 0.5833
## Balanced Accuracy : 0.5750
##
## 'Positive' Class : Compra
##
El Árbol acertó 21 de 36 casos de prueba (8 Venta, 13 Compra). Obtuvo una exactitud de 58.3%, sensibilidad de 65% y Kappa de 0.151, resultados casi idénticos a los del KNN.
library(caret)
# Métricas KNN
cm_knn <- confusionMatrix(modelo_knn, prueba$Signal, positive = "Compra")
# Métricas Árbol
cm_arbol <- confusionMatrix(pred_arbol, prueba$Signal, positive = "Compra")
# Tabla comparativa
comparacion <- data.frame(
Metrica = c("Exactitud", "Sensibilidad", "Especificidad",
"VPP", "VPN", "Kappa"),
KNN = c(
cm_knn$overall["Accuracy"],
cm_knn$byClass["Sensitivity"],
cm_knn$byClass["Specificity"],
cm_knn$byClass["Pos Pred Value"],
cm_knn$byClass["Neg Pred Value"],
cm_knn$overall["Kappa"]
),
Arbol = c(
cm_arbol$overall["Accuracy"],
cm_arbol$byClass["Sensitivity"],
cm_arbol$byClass["Specificity"],
cm_arbol$byClass["Pos Pred Value"],
cm_arbol$byClass["Neg Pred Value"],
cm_arbol$overall["Kappa"]
)
)
round(comparacion[, c("KNN", "Arbol")], 4)
## KNN Arbol
## Accuracy 0.5833 0.5833
## Sensitivity 0.6000 0.6500
## Specificity 0.5625 0.5000
## Pos Pred Value 0.6316 0.6190
## Neg Pred Value 0.5294 0.5333
## Kappa 0.1615 0.1509
Ambos modelos alcanzan la misma exactitud (58.3%) y un Kappa muy similar (~0.15). Las diferencias son mínimas: el Árbol tiene mayor sensibilidad (65% vs 60%) y el KNN mayor especificidad (56% vs 50%). Dado el empate en desempeño, se elige el Árbol de Decisión como modelo final por su mayor interpretabilidad.
# Extraer el mes calendario (1-12) de cada fila
datos_modelo$Mes <- as.numeric(substr(datos_modelo$Date, 6, 7))
# Nombres en español para las gráficas
nombres_meses <- c("Enero","Febrero","Marzo","Abril","Mayo","Junio",
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre")
# Tabla de clasificación estacional simplificada
tabla_estacional <- datos_modelo %>%
group_by(Mes) %>%
summarise(
Compras = sum(Signal == "Compra"),
Total = n(),
Oro_clasi = round(Compras / Total * 100, 1),
.groups = "drop"
) %>%
mutate(
Mes = nombres_meses[Mes],
Clasificacion = case_when(
Oro_clasi >= 60 ~ "Compra",
Oro_clasi <= 40 ~ "Venta",
TRUE ~ "Neutro"
)
) %>%
select(
Mes,
Clasificacion,
Oro_clasi
)
tabla_estacional
## # A tibble: 12 × 3
## Mes Clasificacion Oro_clasi
## <chr> <chr> <dbl>
## 1 Enero Neutro 53.3
## 2 Febrero Compra 60
## 3 Marzo Compra 60
## 4 Abril Neutro 46.7
## 5 Mayo Neutro 46.7
## 6 Junio Compra 80
## 7 Julio Compra 66.7
## 8 Agosto Venta 26.7
## 9 Septiembre Neutro 46.7
## 10 Octubre Neutro 46.7
## 11 Noviembre Compra 66.7
## 12 Diciembre Compra 64.3
# Asegurar el orden cronológico en la gráfica
tabla_estacional$Mes <- factor(tabla_estacional$Mes,
levels = nombres_meses)
ggplot(tabla_estacional, aes(x = Mes, y = Oro_clasi,
fill = Clasificacion)) +
geom_bar(stat = "identity", alpha = 0.85) +
geom_hline(yintercept = 50, linetype = "dashed", color = "grey40") +
geom_text(aes(label = paste0(Oro_clasi, "%")),
vjust = -0.4, size = 3.3) +
scale_fill_manual(values = c("Compra" = "darkgreen",
"Venta" = "darkred",
"Neutro" = "darkgoldenrod")) +
labs(title = "Porcentaje de meses con señal de Compra por mes calendario",
x = NULL, y = "% Compra", fill = "Clasificación") +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Agrupa los 179 meses por mes calendario. Junio resulta el mejor mes de
Compra (80%), agosto el único de Venta (26.7%). Revela un patrón
estacional en el comportamiento del oro.