Introducción

Bases de datos utilizadas

## 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"))

Precio del oro

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.

Índice del dólar (DXY)

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.

Inflación (CPI)

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.

Tasa de interés

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.

Manejo de datos faltantes

Diagnóstico

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.

Imputación de datos faltantes

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.

Preparación de los datos

Unión de las bases

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.

Crear variables nuevas

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.

Crear la variable objetivo

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.

Análisis Exploratorio

Gráfica de serie de tiempo

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.

Diagrama de caja y bigotes por grupos

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.

Matriz de correlación

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

Dividir los datos en entrenamiento y prueba

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")

KNN

Normalización de los predictores

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.

Aplicar el método K-NN

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.

Buscar el k óptimo

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

Modelo final y matriz de confusión

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.

Árbol de decisión

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

Matriz de confusión

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.

Comparación de modelos

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.

Análisis

¿Qué meses del año son mejores para comprar o vender oro?

# 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

Visualización de la clasificación estacional

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