true

Introducción

El Banco Central del Paraguay (BCP) publica diariamente las cotizaciones referenciales del dólar americano (USD), real brasileño (BRL) y peso argentino (ARS), entre otras divisas. Este documento muestra cómo automatizar la descarga histórica, limpiar y consolidar los datos para el período 2018‑2025 y luego proyectar los próximos 90 días utilizando un modelo Holt‑Winters con tendencia amortiguada. Finalmente, se presenta una figura interactiva que permite explorar las series históricas y los pronósticos.

Librerías necesarias

library(httr)          # manejo de sesiones y POST/GET
library(rvest)         # parseo HTML
library(xml2)          # backend XML
library(dplyr)         # manipulación de datos
library(tidyr)         # pivot_longer / complete
library(lubridate)     # fechas
library(readr)         # lectura y escritura CSV
library(forecast)      # HoltWinters + forecast
library(plotly)        # figura interactiva
library(purrr)         # set_names, map_dfr
library(stringr)       # regex util

Fuente de datos

Las cotizaciones se extraen del formulario público del BCP (https://www.bcp.gov.py/webapps/web/cotizacion/monedas-historica). Para cada año (anho) y moneda (moneda) se envía una solicitud POST junto con los tokens ocultos generados por la página ASP.NET.

Parámetros de descarga

base_url   <- "https://www.bcp.gov.py/webapps/web/cotizacion/monedas-historica"
output_dir <- "G:/Mi unidad/BCP_COTIZACIONES"
years      <- 2018:2025
currencies <- c("USD", "BRL", "ARS")
dir.create(output_dir, showWarnings = FALSE)

Función de scraping y descarga

# Sesión inicial
session     <- session(base_url)
form        <- session %>% html_node("form#form-fecha")
hidden_vals <- form %>% html_nodes("input[type=hidden]") %>%
  {purrr::set_names(html_attr(., "value"), html_attr(., "name"))}

# Descarga de un (año, moneda)
download_year_currency <- function(year, curr) {
  csv_path <- file.path(output_dir, sprintf("cotizacion_%s_%d.csv", curr, year))
  if (file.exists(csv_path)) return(invisible())
  
  res <- session %>% session_submit(
    form,
    body   = c(hidden_vals, anho = as.character(year), moneda = curr),
    submit = "CONSULTAR"
  )
  # refrescar tokens para el siguiente envío
  hidden_vals <<- res %>% html_nodes("form#form-fecha input[type=hidden]") %>%
    {purrr::set_names(html_attr(., "value"), html_attr(., "name"))}
  
  tbls <- res %>% html_nodes("table#cotizacion-interbancaria")
  if (length(tbls) < 2) return(invisible())
  tbls[[2]] %>% html_table(fill = TRUE) %>% write_csv(csv_path)
}

# Bucle de descarga
for (y in years) for (c in currencies) download_year_currency(y, c)

Compilación y limpieza de la serie histórica

csv_files <- list.files(output_dir, pattern = "cotizacion_.*\\.csv$", full.names = TRUE)

read_clean <- function(path) {
  meta <- stringr::str_match(basename(path), "cotizacion_(.*)_([0-9]{4})\\.csv")
  tibble_arch <- read_csv(path, show_col_types = FALSE) %>%
    rename_with(~ gsub("^#", "DIA", .x)) %>%
    mutate(ANHO   = as.integer(meta[3]),
           MONEDA = meta[2])
  tibble_arch
}

df_all <- map_dfr(csv_files, read_clean) %>%
  pivot_longer(cols = ENE:DIC, names_to = "MES_ABR", values_to = "COTIZACION") %>%
  mutate(
    MES        = recode(MES_ABR,
                        "ENE" = 1, "FEB" = 2, "MAR" = 3, "ABR" = 4, "MAY" = 5, "JUN" = 6,
                        "JUL" = 7, "AGO" = 8, "SEP" = 9, "OCT" = 10, "NOV" = 11, "DIC" = 12,
                        .default = NA_real_),
    FECHA      = ymd(sprintf("%s-%02d-%02d", ANHO, MES, DIA)),
    COTIZACION = parse_number(COTIZACION, locale = locale(decimal_mark = ","))
  ) %>%
  filter(!is.na(FECHA) & !is.na(COTIZACION))
## Warning: There were 2 warnings in `mutate()`.
## The first warning was:
## ℹ In argument: `FECHA = ymd(sprintf("%s-%02d-%02d", ANHO, MES, DIA))`.
## Caused by warning:
## !  488 failed to parse.
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.

Pronósticos a 30 días

forecast_horizon <- 15
fc_list <- list()

for (curr in unique(df_all$MONEDA)) {
  ts_full <- df_all %>% filter(MONEDA == curr) %>%
    arrange(FECHA) %>%
    complete(FECHA = seq(min(FECHA), max(FECHA), by = "day")) %>%
    fill(COTIZACION)
  last_date <- max(ts_full$FECHA)
  train     <- ts_full %>% filter(FECHA >= last_date - years(1)) %>% pull(COTIZACION)
  names(train) <- seq.Date(last_date - years(1), last_date, by = "day")
  
  hw_fit <- HoltWinters(train, beta = TRUE, gamma = FALSE)
  fc     <- forecast(hw_fit, h = forecast_horizon)
  
  fc_list[[curr]] <- tibble(
    FECHA      = seq(last_date + days(1), by = "day", length.out = forecast_horizon),
    COTIZAPROY = as.numeric(fc$mean),
    MONEDA     = curr
  ) %>% mutate(
    ANHO = year(FECHA), MES = month(FECHA), DIA = day(FECHA), COTIZACION = NA_real_
  )
}

df_proj <- bind_rows(fc_list)

Consolidación final

df_final <- bind_rows(
  df_all %>% mutate(COTIZAPROY = NA_real_),
  df_proj
) %>%
  arrange(MONEDA, FECHA) %>%
  group_by(MONEDA) %>%
  mutate(
    last_obs = max(FECHA[!is.na(COTIZACION)]),
    FLAG     = if_else(FECHA >= last_obs - years(1) | !is.na(COTIZAPROY), 1, 0)
  ) %>%
  ungroup()
df_final
## # A tibble: 17,492 × 10
##      DIA  ANHO MONEDA MES_ABR COTIZACION   MES FECHA      COTIZAPROY last_obs  
##    <dbl> <dbl> <chr>  <chr>        <dbl> <dbl> <date>          <dbl> <date>    
##  1     2  2006 ARS    ENE          2012.     1 2006-01-02         NA 2025-05-12
##  2     3  2006 ARS    ENE          2020.     1 2006-01-03         NA 2025-05-12
##  3     4  2006 ARS    ENE          2016.     1 2006-01-04         NA 2025-05-12
##  4     5  2006 ARS    ENE          2018.     1 2006-01-05         NA 2025-05-12
##  5     6  2006 ARS    ENE          2018.     1 2006-01-06         NA 2025-05-12
##  6     9  2006 ARS    ENE          2011.     1 2006-01-09         NA 2025-05-12
##  7    10  2006 ARS    ENE          2002.     1 2006-01-10         NA 2025-05-12
##  8    11  2006 ARS    ENE          2002.     1 2006-01-11         NA 2025-05-12
##  9    12  2006 ARS    ENE          2008.     1 2006-01-12         NA 2025-05-12
## 10    13  2006 ARS    ENE          2015.     1 2006-01-13         NA 2025-05-12
## # ℹ 17,482 more rows
## # ℹ 1 more variable: FLAG <dbl>

Figura interactiva

La figura siguiente permite comparar el histórico del último año con las proyecciones para los próximos 90 días. Selecciona/deselecciona cada moneda en la leyenda.

Resultados clave

Conclusiones y recomendaciones

  1. El USD/ PYG muestra una tendencia más estable que BRL y ARS.
  2. El modelo Holt‑Winters captura adecuadamente la dinámica reciente y genera intervalos prudenciales.
  3. Se recomienda automatizar esta rutina a diario, de modo que las proyecciones se reemplacen automáticamente por los valores observados y el horizonte pronosticado se desplace.

Referencias