1 .Objetivos

Al finalizar este taller, el estudiante podrá:

  • Descargar series financieras desde Yahoo Finance utilizando quantmod.
  • Analizar el tipo de cambio PEN/USD como una serie temporal.
  • Diferenciar entre nivel del tipo de cambio y rendimiento.
  • Evaluar estacionariedad mediante la prueba ADF.
  • Interpretar ACF y PACF.
  • Estimar modelos AR, MA, ARMA y ARIMA.
  • Generar pronósticos del tipo de cambio.
  • Incorporar variables exógenas mediante un modelo ARIMAX.
  • Modelar y pronosticar la volatilidad mediante GARCH.
  • Evaluar modelos mediante RMSE y MAE.
  • Separar muestra de estimación y muestra de evaluación para evitar evaluar el pronóstico sobre los mismos datos utilizados para estimar el modelo.

2 .Marco económico: ¿qué es el tipo de cambio?

El tipo de cambio que utilizaremos se expresa como:

\[ E_t = \frac{\text{PEN}}{\text{USD}} \]

Por tanto, un aumento de \(E_t\) significa que se necesitan más soles para comprar un dólar. En términos convencionales, el sol se deprecia frente al dólar.

Una disminución de \(E_t\) implica una apreciación del sol.

En mercados cambiarios, el tipo de cambio puede estar relacionado con variables como:

  • tasas de interés domésticas e internacionales;
  • riesgo país;
  • condiciones financieras internacionales;
  • precio de materias primas;
  • fortaleza global del dólar;
  • expectativas de los agentes;
  • flujos de capital;
  • intervenciones del banco central.

Pregunta económica: ¿el comportamiento pasado del tipo de cambio contiene información útil para pronosticar su comportamiento futuro?

3 .Paquetes

packages <- c(
  "quantmod",
  "forecast",
  "tseries",
  "ggplot2",
  "dplyr",
  "tidyr",
  "rugarch",
  "knitr"
)

new_packages <- packages[!(packages %in% installed.packages()[, "Package"])]

if(length(new_packages) > 0){
  install.packages(new_packages)
}

library(quantmod)
library(forecast)
library(tseries)
library(ggplot2)
library(dplyr)
library(tidyr)
library(rugarch)
library(knitr)

4 .Descarga del tipo de cambio PEN/USD

Yahoo Finance utiliza el ticker PEN=X para el tipo de cambio del sol peruano frente al dólar.

getSymbols(
  "PEN=X",
  src = "yahoo",
  from = "2010-01-01",
  to = Sys.Date(),
  auto.assign = TRUE
)
## [1] "PEN=X"
tc <- `PEN=X`

head(tc)
##            PEN=X.Open PEN=X.High PEN=X.Low PEN=X.Close PEN=X.Volume
## 2010-01-04     2.8553     2.8553    2.8175      2.8176            0
## 2010-01-05     2.8188     2.8730    2.8188      2.8730            0
## 2010-01-06     2.8222     2.8730    2.8194      2.8730            0
## 2010-01-07     2.8052     2.8103    2.8052      2.8085            0
## 2010-01-08     2.8072     2.8072    2.8072      2.8072            0
## 2010-01-11     2.7939     2.7939    2.7939      2.7939            0
##            PEN=X.Adjusted
## 2010-01-04         2.8176
## 2010-01-05         2.8730
## 2010-01-06         2.8730
## 2010-01-07         2.8085
## 2010-01-08         2.8072
## 2010-01-11         2.7939
tail(tc)
##            PEN=X.Open PEN=X.High PEN=X.Low PEN=X.Close PEN=X.Volume
## 2026-09-27   3.294997   3.295007  3.294997    3.292842            0
## 2026-09-28   3.296602   3.336991  3.296602    3.437500            0
## 2026-09-29   3.339198   3.440000  3.339198    3.336991            0
## 2026-09-30   3.347142   3.448000  3.347142    3.340760            0
## 2026-10-01   3.364013   3.464000  3.360060    3.349078            0
## 2026-10-05   3.433500   3.455600  3.433300    3.448100            0
##            PEN=X.Adjusted
## 2026-09-27       3.292842
## 2026-09-28       3.437500
## 2026-09-29       3.336991
## 2026-09-30       3.340760
## 2026-10-01       3.349078
## 2026-10-05       3.448100

4.1 .1 Visualización

chartSeries(
  tc,
  theme = chartTheme("white"),
  name = "Tipo de cambio PEN/USD"
)

También podemos trabajar específicamente con el precio de cierre:

tc_cierre <- Cl(tc)

plot(
  tc_cierre,
  main = "Tipo de cambio PEN/USD",
  ylab = "Soles por dólar",
  xlab = ""
)

5 .Primer análisis estadístico

summary(tc_cierre)
##      Index             PEN=X.Close    
##  Min.   :2010-01-04   Min.   :0.7844  
##  1st Qu.:2014-03-12   1st Qu.:2.8150  
##  Median :2018-05-20   Median :3.3022  
##  Mean   :2018-05-19   Mean   :3.2559  
##  3rd Qu.:2022-07-26   3rd Qu.:3.6135  
##  Max.   :2026-10-05   Max.   :4.1360  
##                       NA's   :10

Podemos calcular algunos estadísticos:

media <- mean(tc_cierre, na.rm = TRUE)
mediana <- median(tc_cierre, na.rm = TRUE)
desv <- sd(tc_cierre, na.rm = TRUE)
minimo <- min(tc_cierre, na.rm = TRUE)
maximo <- max(tc_cierre, na.rm = TRUE)

data.frame(
  Media = media,
  Mediana = mediana,
  Desviacion_Estandar = desv,
  Minimo = minimo,
  Maximo = maximo
)
##      Media Mediana Desviacion_Estandar Minimo Maximo
## 1 3.255892  3.3022           0.4157707 0.7844  4.136

6 .Nivel del tipo de cambio versus rendimiento

En finanzas es habitual trabajar con rendimientos en lugar de niveles.

El rendimiento logarítmico se define como:

\[ r_t = 100\ln\left(\frac{E_t}{E_{t-1}}\right) \]

Equivalentemente:

\[ r_t = 100[\ln(E_t)-\ln(E_{t-1})] \]

En R:

retornos <- 100 * diff(log(tc_cierre))
retornos <- na.omit(retornos)

head(retornos)
##            PEN=X.Close
## 2010-01-05  1.94712845
## 2010-01-06  0.00000000
## 2010-01-07 -2.27061961
## 2010-01-08 -0.04630221
## 2010-01-11 -0.47490554
## 2010-01-12  0.04294009

6.1 .1 Gráfico de rendimientos

plot(
  retornos,
  main = "Rendimientos diarios del PEN/USD",
  ylab = "% de Rendimientos diarios",
  xlab = ""
)
abline(h = 0, lty = 2)

6.1.1 Interpretación financiera

Si:

\[ r_t > 0 \]

el dólar aumentó su valor frente al sol durante el período.

Si:

\[ r_t < 0 \]

el dólar disminuyó su valor frente al sol.

El uso de logaritmos también permite aproximar el cambio porcentual cuando los movimientos son pequeños.

7 . Estacionariedad

Una serie temporal es estacionaria, de manera simplificada, cuando sus propiedades probabilísticas fundamentales no dependen sistemáticamente del tiempo.

Para una serie estacionaria podemos pensar en:

\[ E(Y_t)=\mu \]

\[ Var(Y_t)=\sigma^2 \]

y:

\[ Cov(Y_t,Y_{t-k}) \]

depende de \(k\), pero no de \(t\).

Muchos modelos ARIMA requieren trabajar con una serie estacionaria o transformar la serie para conseguir estacionariedad.

7.1 .1 Prueba ADF

La prueba Augmented Dickey-Fuller contrasta, de forma simplificada:

\[ H_0: \text{la serie tiene raíz unitaria es decir, no estacionaria} \]

frente a:

\[ H_1: \text{la serie es estacionaria} \]

Aplicamos la prueba al nivel:

adf.test(na.omit(as.numeric(tc_cierre)))
## 
##  Augmented Dickey-Fuller Test
## 
## data:  na.omit(as.numeric(tc_cierre))
## Dickey-Fuller = -1.1669, Lag order = 16, p-value = 0.9119
## alternative hypothesis: stationary

Como p-value = 0.9162 > 0.05, no se rechaza \(H_0\). Es decir, no hay evidencia suficiente para concluir que la serie sea estacionaria.

Y a los rendimientos:

adf.test(as.numeric(retornos))
## 
##  Augmented Dickey-Fuller Test
## 
## data:  as.numeric(retornos)
## Dickey-Fuller = -22.461, Lag order = 16, p-value = 0.01
## alternative hypothesis: stationary

7.1.1 Interpretación

  • Si el p-valor es pequeño, existe evidencia para rechazar \(H_0\).
  • Si no se rechaza \(H_0\), no podemos afirmar que la serie sea estacionaria.

Importante: la prueba ADF no “demuestra” estacionariedad en sentido absoluto. Es una prueba estadística con una hipótesis nula específica y debe interpretarse junto con gráficos y otras herramientas.

8 . Autocorrelación

La autocorrelación mide la relación entre una variable y sus propios valores pasados.

Para rezago \(k\):

\[ \rho_k = Corr(Y_t,Y_{t-k}) \]

En R:

acf(
  na.omit(as.numeric(tc_cierre)),
  main = "ACF del nivel del tipo de cambio"
)

El gráfico muestra una fuerte persistencia del tipo de cambio y es consistente con una serie no estacionaria en niveles.

Para rendimientos:

acf(
  as.numeric(retornos),
  main = "ACF de los rendimientos"
)

  • Rezago 1: presenta una autocorrelación negativa cercana a −0.5, que supera ampliamente las bandas azules. Los rendimientos consecutivos tienden a moverse en sentidos opuestos.
  • Rezagos siguientes: la mayoría de las autocorrelaciones están cerca de cero. La persistencia prolongada que aparecía en el nivel del tipo de cambio desaparece. Este comportamiento es compatible con una serie estacionaria

La PACF muestra la correlación parcial después de controlar por los rezagos intermedios:

pacf(
  as.numeric(retornos),
  main = "PACF de los rendimientos"
)

La PACF mide esa relación después de descontar la influencia de los rezagos intermedios. En el primer rezago coincide con la ACF:

\[ \alpha_1 = \rho_1 \]

Para el segundo rezago, la autocorrelación parcial se calcula como:

\[ \alpha_2 = \frac{\rho_2-\rho_1^2}{1-\rho_1^2} \]

Por ejemplo, si \(\rho_1 \approx -0.5\) y \(\rho_2 \approx 0\), entonces:

\[ \alpha_2 \approx \frac{0-(-0.5)^2}{1-(-0.5)^2} = \frac{-0.25}{0.75} \approx -0.33 \]

Este resultado ayuda a entender por qué la PACF puede mostrar un segundo rezago negativo aunque la ACF de ese rezago sea cercana a cero.

En nuestro gráfico, las autocorrelaciones parciales son inicialmente negativas y se aproximan gradualmente a cero. La combinación de una ACF con un pico dominante en el primer rezago y una PACF que disminuye gradualmente sugiere un MA(1) como candidato:

\[ r_t = \mu+\varepsilon_t+\theta_1\varepsilon_{t-1} \]

Donde:

  • \(\mu\) es el rendimiento medio.
  • \(\varepsilon_t\) es el choque aleatorio actual.
  • \(\varepsilon_{t-1}\) es el choque de la observación anterior.
  • \(\theta_1\) mide cómo influye el choque anterior en el rendimiento actual.

Como los rendimientos ya son estacionarios, este modelo corresponde a un ARIMA(0,0,1) aplicado a los rendimientos. Su adecuación debe comprobarse mediante la estimación y el análisis de los residuos.

En términos sencillos: La PACF mide la relación entre el dato de hoy y un dato pasado, después de descontar lo que se explica por los datos que están en medio. Por ejemplo: - Rezago 1: relaciona el rendimiento de hoy con el de ayer. - Rezago 2: relaciona el rendimiento de hoy con el de anteayer, después de tomar en cuenta el rendimiento de ayer. Si la PACF del rezago 2 está cerca de cero, significa que, una vez considerado ayer, anteayer aporta poca información adicional sobre el rendimiento de hoy.

9 . Modelo Random Walk

Un punto de referencia fundamental en mercados financieros es el paseo aleatorio:

\[ E_t = E_{t-1}+\varepsilon_t \]

donde:

\[ E(\varepsilon_t)=0 \]

Una forma equivalente es:

\[ \Delta E_t=\varepsilon_t \]

El pronóstico del tipo de cambio futuro es entonces, esencialmente, el último valor observado.

Esto es importante porque un modelo sofisticado no debería considerarse útil simplemente porque reproduce bien la muestra histórica. Debe compararse con un benchmark sencillo.

En R:

modelo_rw <- naive(tc_cierre)

modelo_rw
##      Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## 4372         3.4481 3.369084 3.527116 3.327256 3.568944
## 4373         3.4481 3.336355 3.559845 3.277200 3.619000
## 4374         3.4481 3.311241 3.584960 3.238792 3.657409
## 4375         3.4481 3.290068 3.606132 3.206411 3.689789
## 4376         3.4481 3.271415 3.624785 3.177884 3.718316
## 4377         3.4481 3.254552 3.641649 3.152093 3.744107
## 4378         3.4481 3.239044 3.657156 3.128376 3.767824
## 4379         3.4481 3.224610 3.671591 3.106301 3.789899
## 4380         3.4481 3.211053 3.685148 3.085567 3.810633
## 4381         3.4481 3.198230 3.697970 3.065957 3.830243

El modelo de caminata aleatoria sin deriva, aplicado mediante naive(tc_cierre), utiliza el último valor observado del tipo de cambio, 3.4475, como pronóstico para las siguientes diez observaciones. Por ello, el pronóstico central permanece constante, mientras que los intervalos de predicción se amplían conforme aumenta el horizonte. Para la primera observación futura, el intervalo al 80 % se encuentra entre 3.3685 y 3.5265 (es decir, hay un 80% de probabilidad de que el próximo valor esté dentro de ese rango.), mientras que el intervalo al 95 % va de 3.3266 a 3.5684. Este último es más amplio porque proporciona una mayor cobertura bajo los supuestos del modelo. En la novena observación futura, el intervalo al 95 % se extiende desde 3.0849 hasta 3.8101. La ampliación de estos intervalos refleja una mayor incertidumbre al pronosticar observaciones más lejanas, aunque la estimación central continúe siendo 3.4475.

10 . Modelos AR

Un modelo autorregresivo de orden \(p\) se representa como:

\[ Y_t = c+ \phi_1Y_{t-1} +\phi_2Y_{t-2} +\cdots+ \phi_pY_{t-p} +\varepsilon_t \]

Por ejemplo, un AR(1):

\[ Y_t=c+\phi_1Y_{t-1}+\varepsilon_t \]

La idea económica/estadística es que el comportamiento actual depende de sus propios valores pasados.

Podemos estimar un AR sobre los rendimientos:

modelo_ar1 <- Arima(
  retornos,
  order = c(1, 0, 0)
)

summary(modelo_ar1)
## Series: retornos 
## ARIMA(1,0,0) with non-zero mean 
## 
## Coefficients:
##           ar1    mean
##       -0.4944  0.0051
## s.e.   0.0132  0.0269
## 
## sigma^2 = 7.009:  log likelihood = -10409.01
## AIC=20824.03   AICc=20824.03   BIC=20843.16
## 
## Training set error measures:
##                         ME     RMSE       MAE MPE MAPE      MASE       ACF1
## Training set -0.0002792375 2.646812 0.9269048 NaN  Inf 0.5438562 -0.1535984

El modelo sugiere que los movimientos del tipo de cambio tienden a corregirse parcialmente en el corto plazo. Cuando el retorno de un período supera su promedio, el siguiente tiende a situarse por debajo de ese promedio, y viceversa. Desde una lectura financiera, esto refleja una reversión parcial de los movimientos recientes. Además, la media estimada no ofrece evidencia firme de un rendimiento promedio positivo sistemático durante el período analizado.

11 . Modelos MA

Un modelo MA(q) representa el proceso mediante errores pasados:

\[ Y_t = \mu+ \varepsilon_t+ \theta_1\varepsilon_{t-1} +\cdots+ \theta_q\varepsilon_{t-q} \]

Por ejemplo:

\[ Y_t=\mu+\varepsilon_t+\theta_1\varepsilon_{t-1} \]

Estimación:

modelo_ma1 <- Arima(
  retornos,
  order = c(0, 0, 1)
)

summary(modelo_ma1)
## Series: retornos 
## ARIMA(0,0,1) with non-zero mean 
## 
## Coefficients:
##           ma1    mean
##       -0.8513  0.0045
## s.e.   0.0083  0.0052
## 
## sigma^2 = 5.396:  log likelihood = -9840.73
## AIC=19687.46   AICc=19687.46   BIC=19706.59
## 
## Training set error measures:
##                         ME     RMSE       MAE MPE MAPE      MASE        ACF1
## Training set -0.0005199145 2.322466 0.9463594 NaN  Inf 0.5552711 0.003185128

El MA(1) sugiere que los movimientos inesperados del tipo de cambio tienden a corregirse parcialmente en el período siguiente. El coeficiente −0.8514 indica que aproximadamente el 85 % del shock inesperado se contrarresta en el retorno esperado del siguiente período. Financieramente, esto refleja una corrección intensa de las sorpresas recientes. La media estimada de 0.0044 tampoco ofrece evidencia firme de un rendimiento promedio positivo sistemático durante el período analizado.

El MA(1) presenta un mejor ajuste global y un menor RMSE, aunque su error absoluto promedio es ligeramente mayor. Además, los errores consecutivos prácticamente dejan de estar relacionados, lo que sugiere que recoge mejor la dinámica inmediata de los retornos.

12 . ARMA

Un ARMA(p,q) combina ambos componentes:

\[ Y_t = c+ \sum_{i=1}^{p}\phi_iY_{t-i} + \varepsilon_t + \sum_{j=1}^{q}\theta_j\varepsilon_{t-j} \]

Por ejemplo, ARMA(1,1):

modelo_arma11 <- Arima(
  retornos,
  order = c(1, 0, 1)
)

summary(modelo_arma11)
## Series: retornos 
## ARIMA(1,0,1) with non-zero mean 
## 
## Coefficients:
##          ar1      ma1    mean
##       0.0047  -0.8527  0.0045
## s.e.  0.0180   0.0098  0.0052
## 
## sigma^2 = 5.397:  log likelihood = -9840.69
## AIC=19689.39   AICc=19689.4   BIC=19714.9
## 
## Training set error measures:
##                         ME     RMSE       MAE MPE MAPE      MASE          ACF1
## Training set -0.0005398852 2.322447 0.9462747 NaN  Inf 0.5552214 -0.0001799457

Este ARMA(1,1) aporta muy poco frente al MA(1) anterior. Desde una perspectiva financiera, mantiene la idea de que los movimientos inesperados del tipo de cambio tienden a experimentar una corrección parcial en el período siguiente. El efecto del retorno anterior, ar1 = 0.0048, es muy pequeño y no estadísticamente significativo. Esto sugiere que añadirlo aporta poca información adicional para anticipar el retorno actual. La dinámica continúa dominada por las sorpresas recientes, reflejadas en ma1 = −0.8528. La media tampoco ofrece evidencia firme de un rendimiento promedio positivo sistemático.

13 . ARIMA

Cuando la serie presenta no estacionariedad, podemos diferenciarla.

Un ARIMA(p,d,q) combina:

  • \(p\): componente autorregresivo;
  • \(d\): número de diferenciaciones;
  • \(q\): componente de medias móviles.

Se puede escribir como:

\[ \phi(B)(1-B)^dY_t = \theta(B)\varepsilon_t \]

donde \(B\) es el operador rezago:

\[ BY_t=Y_{t-1} \]

13.1 .1 Selección automática

modelo_arima <- auto.arima(
  tc_cierre,
  seasonal = FALSE,
  stepwise = FALSE,
  approximation = FALSE
)

summary(modelo_arima)
## Series: tc_cierre 
## ARIMA(0,1,1) 
## 
## Coefficients:
##           ma1
##       -0.7558
## s.e.   0.0103
## 
## sigma^2 = 0.002452:  log likelihood = 6916.79
## AIC=-13829.58   AICc=-13829.58   BIC=-13816.81
## 
## Training set error measures:
##                        ME       RMSE        MAE         MPE      MAPE      MASE
## Training set 0.0005001646 0.04950325 0.02855863 -0.03696337 0.9181985 0.9661327
##                    ACF1
## Training set 0.01327115

Este modelo pronostica el nivel del tipo de cambio, utilizando sus cambios entre períodos y los movimientos inesperados recientes. El coeficiente ma1 = −0.7561 sugiere que un movimiento inesperado tiende a corregirse parcialmente en el período siguiente: aproximadamente el 75.6 % de esa sorpresa se contrarresta en el cambio esperado posterior. Esto refleja una corrección de corto plazo del impacto inicial.

La función selecciona una especificación utilizando criterios de información y procedimientos de búsqueda.

14 . Diagnóstico del ARIMA

Un buen modelo debería dejar residuos que se comporten aproximadamente como ruido blanco.

Podemos revisar:

checkresiduals(modelo_arima)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(0,1,1)
## Q* = 77.674, df = 9, p-value = 4.675e-13
## 
## Model df: 1.   Total lags used: 10

La prueba de Ljung-Box evalúa si existe autocorrelación conjunta en los residuos.

De manera simplificada:

\[ H_0: \rho_1=\rho_2=\cdots=\rho_k=0 \]

Si no rechazamos \(H_0\), no encontramos evidencia de autocorrelación residual significativa a los rezagos evaluados.

Interpretación econométrica: Los residuos del ARIMA(0,1,1) se concentran alrededor de cero, aunque presentan valores atípicos importantes, especialmente uno cercano a −2. La ACF muestra numerosas barras que superan las bandas azules y picos repetidos aproximadamente en los rezagos 5, 10, 15 y siguientes. Esto sugiere que permanece dependencia temporal y posiblemente un patrón periódico que el modelo todavía no recoge. Por tanto, los residuos no parecen comportarse como ruido blanco, conclusión que debe contrastarse con Ljung–Box. El histograma también muestra una fuerte concentración central y observaciones extremas, lo que pone en duda la normalidad.

Interpretación financiera: La mayoría de las diferencias entre el tipo de cambio observado y el estimado son pequeñas, pero existen episodios en los que el modelo se desvía considerablemente. Además, los errores mantienen patrones repetidos: todavía queda información del comportamiento cambiario que podría aprovecharse para mejorar los pronósticos. Los errores extremos merecen revisar las fechas y los datos originales antes de atribuirlos a acontecimientos del mercado. Esta distribución también puede reducir la fiabilidad de los intervalos de predicción calculados suponiendo normalidad

15 . Pronóstico ARIMA

Generamos pronósticos para los siguientes 30 períodos:

pronostico_arima <- forecast(
  modelo_arima,
  h = 30,
  level = c(80, 95)
)

plot(
  pronostico_arima,
  main = "Pronóstico ARIMA del PEN/USD"
)

Interpretación econométrica: La línea negra representa la evolución histórica del tipo de cambio y el segmento azul muestra el pronóstico. La previsión central se mantiene cerca del último valor observado, sin una tendencia marcada en el horizonte presentado. El pronóstico ocupa una porción muy pequeña del gráfico porque se compara con más de 4 000 observaciones históricas; por eso, los intervalos de predicción apenas se distinguen. Su tamaño visual no permite concluir que exista poca incertidumbre. Conviene mostrar un tramo más corto de la historia para examinar mejor la proyección.

Interpretación financiera: Si la cotización está expresada en soles por dólar, la caída desde los máximos de la muestra representa una apreciación del sol frente al dólar: se necesitan menos soles para comprar un dólar. El pronóstico sugiere una estabilidad relativa alrededor del nivel reciente durante las próximas observaciones. Para una empresa que realiza pagos en dólares, esa trayectoria central implicaría un costo cambiario relativamente estable en el corto plazo, sujeto a los movimientos que efectivamente ocurran.

El pronóstico puntual puede acompañarse de intervalos de predicción:

\[ \hat{Y}_{t+h} \pm z_{\alpha/2} SE(\hat{Y}_{t+h}) \]

Estos intervalos representan incertidumbre asociada al pronóstico.

16 . Incorporando variables económicas: ARIMAX

El modelo ARIMAX extiende ARIMA incorporando variables exógenas:

\[ Y_t = \beta_0+ \beta_1X_{1t} +\cdots+ \beta_kX_{kt} + n_t \]

donde \(n_t\) sigue un proceso ARIMA.

Para Finanzas Internacionales podemos utilizar variables como:

  • DXY: índice del dólar frente a principales monedas;
  • precio del cobre;
  • riesgo país;
  • tasa de interés internacional;
  • tasa de política monetaria doméstica;
  • VIX.

En este taller utilizaremos DXY y cobre como ejemplo de variables financieras internacionales.

17 . Descarga de variables explicativas

getSymbols(
  "DX-Y.NYB",
  src = "yahoo",
  from = "2010-01-01",
  to = Sys.Date()
)
## [1] "DX-Y.NYB"
getSymbols(
  "HG=F",
  src = "yahoo",
  from = "2010-01-01",
  to = Sys.Date()
)
## [1] "HG=F"
dxy <- Cl(`DX-Y.NYB`)
cobre <- Cl(`HG=F`)

17.1 .1 Unificación de las series

datos <- na.omit(
  merge(
    tc_cierre,
    dxy,
    cobre
  )
)

colnames(datos) <- c(
  "PEN_USD",
  "DXY",
  "COBRE"
)

head(datos)
##            PEN_USD   DXY  COBRE
## 2010-01-04  2.8176 77.53 3.3880
## 2010-01-05  2.8730 77.62 3.3960
## 2010-01-06  2.8730 77.49 3.4775
## 2010-01-07  2.8085 77.91 3.4115
## 2010-01-08  2.8072 77.47 3.3880
## 2010-01-11  2.7939 77.00 3.4285

18 . Transformación a rendimientos

Una alternativa económicamente razonable es modelar rendimientos:

\[ r^{PEN}_t = \beta_0+ \beta_1r^{DXY}_t+ \beta_2r^{COBRE}_t+ u_t \]

Calculamos:

rend_datos <- na.omit(
  100 * diff(log(datos))
)

head(rend_datos)
##                PEN_USD         DXY      COBRE
## 2010-01-05  1.94712845  0.11602188  0.2358461
## 2010-01-06  0.00000000 -0.16762931  2.3715392
## 2010-01-07 -2.27061961  0.54054931 -1.9161563
## 2010-01-08 -0.04630221 -0.56635809 -0.6912290
## 2010-01-11 -0.47490554 -0.60853585  1.1883049
## 2010-01-12  0.04294009 -0.06496012 -2.6900777

19 . Modelo econométrico MCO

Podemos estimar:

\[ r^{PEN}_t = \beta_0+ \beta_1r^{DXY}_t+ \beta_2r^{COBRE}_t+ u_t \]

En R:

modelo_ols <- lm(
  PEN_USD ~ DXY + COBRE,
  data = as.data.frame(rend_datos)
)

summary(modelo_ols)
## 
## Call:
## lm(formula = PEN_USD ~ DXY + COBRE, data = as.data.frame(rend_datos))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -126.758   -0.358   -0.001    0.362  126.972 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)  
## (Intercept)  0.005972   0.052959   0.113   0.9102  
## DXY          0.022385   0.117775   0.190   0.8493  
## COBRE       -0.084493   0.033953  -2.489   0.0129 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.224 on 3705 degrees of freedom
## Multiple R-squared:  0.001948,   Adjusted R-squared:  0.001409 
## F-statistic: 3.616 on 2 and 3705 DF,  p-value: 0.02698

19.1 .1 Interpretación

El coeficiente:

\[ \beta_1 \]

mide la asociación entre el rendimiento del DXY y el rendimiento del PEN/USD, manteniendo constante el rendimiento del cobre.

El coeficiente:

\[ \beta_2 \]

mide la asociación entre el rendimiento del cobre y el rendimiento del PEN/USD, manteniendo constante el DXY.

La interpretación como efecto causal requiere supuestos adicionales. Una regresión contemporánea no establece por sí sola causalidad.

20 . ARIMAX

Podemos combinar la dinámica temporal con variables externas.

Primero creamos la matriz de regresores:

X <- as.matrix(
  rend_datos[, c("DXY", "COBRE")]
)

Y <- rend_datos$PEN_USD

Estimamos:

modelo_arimax <- auto.arima(
  Y,
  xreg = X,
  seasonal = FALSE,
  stepwise = FALSE,
  approximation = FALSE
)

summary(modelo_arimax)
## Series: Y 
## Regression with ARIMA(0,0,1) errors 
## 
## Coefficients:
##           ma1     DXY    COBRE
##       -0.8592  0.1479  -0.0569
## s.e.   0.0086  0.0475   0.0145
## 
## sigma^2 = 5.964:  log likelihood = -8571.37
## AIC=17150.74   AICc=17150.75   BIC=17175.61
## 
## Training set error measures:
##                      ME    RMSE       MAE MPE MAPE      MASE         ACF1
## Training set 0.03117142 2.44115 0.9090695 NaN  Inf 0.5711688 -0.003201627

Interpretación financiera: Un fortalecimiento del dólar en los mercados internacionales se relaciona con una subida de la cotización en soles por dólar y, por tanto, con una depreciación del sol. En cambio, una subida del precio del cobre se relaciona con una reducción de esa cotización y una apreciación del sol. Los resultados son compatibles con un mercado cambiario peruano influido tanto por la fortaleza internacional del dólar como por el comportamiento del cobre.

El modelo tiene la estructura general:

\[ r_t = \beta'X_t+n_t \]

donde \(n_t\) sigue un proceso ARIMA.

21 . Pronóstico ARIMAX

Para pronosticar \(h\) períodos necesitamos conocer o pronosticar también las variables exógenas futuras.

Por ejemplo, si dispusiéramos de valores futuros:

# Número de observaciones futuras
h <- 10

# Recuperar los rendimientos usados en ARIMAX
X_hist <- modelo_arimax$xreg[
  , c("DXY", "COBRE"), drop = FALSE
]

# Estimar modelos separados
modelo_DXY <- forecast::auto.arima(
  X_hist[, "DXY"],
  seasonal = FALSE,
  approximation = FALSE
)

modelo_COBRE <- forecast::auto.arima(
  X_hist[, "COBRE"],
  seasonal = FALSE,
  approximation = FALSE
)

# Pronosticar DXY y cobre
pronostico_DXY <- forecast::forecast(modelo_DXY, h = h)
pronostico_COBRE <- forecast::forecast(modelo_COBRE, h = h)

# Construir la matriz con los valores calculados
X_futuro <- cbind(
  DXY = as.numeric(pronostico_DXY$mean),
  COBRE = as.numeric(pronostico_COBRE$mean)
)

# Mostrar los rendimientos futuros estimados
X_futuro

# Pronosticar el rendimiento del tipo de cambio
pronostico_arimax <- forecast::forecast(
  modelo_arimax,
  xreg = X_futuro
)

pronostico_arimax

plot(
  pronostico_arimax,
  include = 100,
  main = "Pronóstico del rendimiento del tipo de cambio",
  ylab = "Rendimiento porcentual"
)

Los códigos han pronosticado el rendimiento porcentual del tipo de cambio para las siguientes cinco observaciones, usando primero los pronósticos de los rendimientos del DXY y del cobre.

Estos valores no son el precio del dólar en soles. Por ejemplo, el primer resultado significa que el modelo estima una caída del tipo de cambio de aproximadamente 0.172 % respecto al último valor observado. Si el tipo de cambio está expresado en soles por dólar, esa caída representa una ligera apreciación del sol.

Las columnas Lo80 y Hi80 muestran los límites del intervalo de predicción al 80 %. Para el primer periodo, van aproximadamente de −3.30 % a +2.96 %: el modelo admite tanto una caída como un aumento.

Por tanto, el pronóstico central indica movimientos pequeños, pero existe bastante incertidumbre. Además, depende de que los rendimientos futuros del DXY y del cobre se comporten como los estimaron sus respectivos modelos.

  • Línea negra: rendimientos históricos del tipo de cambio.
  • Línea azul: rendimientos pronosticados para las siguientes diez observaciones.
  • Franja oscura: intervalo de predicción al 80 %.
  • Franja clara: intervalo de predicción al 95 %. La línea azul está cerca de cero porque el modelo espera variaciones porcentuales pequeñas del tipo de cambio. Las franjas son amplias y abarcan valores positivos y negativos: existe incertidumbre sobre si el dólar subirá o bajará. Por eso, un pronóstico cercano a cero no garantiza que el tipo de cambio permanezca estable.

Punto importante: ARIMAX no permite “magia”. Para pronosticar el tipo de cambio futuro condicionado a variables externas, necesitamos valores futuros de esas variables o pronósticos de ellas.

22 . Volatilidad

En los mercados financieros, no solamente interesa:

\[ E(r_t) \]

sino también:

\[ Var(r_t)=\sigma_t^2 \]

La volatilidad puede variar en el tiempo.

Una característica frecuente de las series financieras es el clustering de volatilidad: períodos de grandes movimientos tienden a agruparse y períodos tranquilos también.

Podemos observar los rendimientos:

plot(
  retornos,
  main = "Rendimientos y volatilidad del PEN/USD",
  ylab = "%",
  xlab = ""
)

También podemos observar los rendimientos al cuadrado:

plot(
  retornos^2,
  main = "Rendimientos al cuadrado",
  ylab = "r_t^2",
  xlab = ""
)

23 . Modelo ARCH/GARCH

Un modelo GARCH(1,1) puede escribirse como:

\[ r_t=\mu+\varepsilon_t \]

con:

\[ \varepsilon_t=\sigma_t z_t \]

y:

\[ \sigma_t^2 = \omega+ \alpha\varepsilon_{t-1}^2+ \beta\sigma_{t-1}^2 \]

donde:

  • \(\omega>0\);
  • \(\alpha\geq0\);
  • \(\beta\geq0\).

Una condición habitual para estabilidad es:

\[ \alpha+\beta<1 \]

La interpretación es que la volatilidad actual depende de:

  1. un componente constante;
  2. shocks recientes;
  3. la volatilidad pasada.

24 . Estimación GARCH(1,1)

spec <- ugarchspec(
  variance.model = list(
    model = "sGARCH",
    garchOrder = c(1, 1)
  ),
  mean.model = list(
    armaOrder = c(1, 1),
    include.mean = TRUE
  ),
  distribution.model = "std"
)

modelo_garch <- ugarchfit(
  spec = spec,
  data = as.numeric(retornos)
)

show(modelo_garch)
## 
## *---------------------------------*
## *          GARCH Model Fit        *
## *---------------------------------*
## 
## Conditional Variance Dynamics    
## -----------------------------------
## GARCH Model  : sGARCH(1,1)
## Mean Model   : ARFIMA(1,0,1)
## Distribution : std 
## 
## Optimal Parameters
## ------------------------------------
##         Estimate  Std. Error  t value Pr(>|t|)
## mu      0.026713    0.005039   5.3010 0.000000
## ar1     0.097507    0.025116   3.8823 0.000103
## ma1    -0.660401    0.019014 -34.7329 0.000000
## omega   0.229877    0.057896   3.9705 0.000072
## alpha1  0.373027    0.063436   5.8804 0.000000
## beta1   0.625973    0.044710  14.0007 0.000000
## shape   3.095448    0.206631  14.9805 0.000000
## 
## Robust Standard Errors:
##         Estimate  Std. Error  t value Pr(>|t|)
## mu      0.026713    0.006927   3.8561 0.000115
## ar1     0.097507    0.026367   3.6981 0.000217
## ma1    -0.660401    0.026306 -25.1043 0.000000
## omega   0.229877    0.185972   1.2361 0.216429
## alpha1  0.373027    0.159760   2.3349 0.019548
## beta1   0.625973    0.142476   4.3935 0.000011
## shape   3.095448    0.492308   6.2876 0.000000
## 
## LogLikelihood : -6471.792 
## 
## Information Criteria
## ------------------------------------
##                    
## Akaike       2.9781
## Bayes        2.9883
## Shibata      2.9781
## Hannan-Quinn 2.9817
## 
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
##                         statistic   p-value
## Lag[1]                      4.782 2.876e-02
## Lag[2*(p+q)+(p+q)-1][5]     7.833 2.158e-08
## Lag[4*(p+q)+(p+q)-1][9]    13.823 1.511e-04
## d.o.f=2
## H0 : No serial correlation
## 
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
##                         statistic p-value
## Lag[1]                  0.0006912   0.979
## Lag[2*(p+q)+(p+q)-1][5] 0.0021481   1.000
## Lag[4*(p+q)+(p+q)-1][9] 0.0039707   1.000
## d.o.f=2
## 
## Weighted ARCH LM Tests
## ------------------------------------
##             Statistic Shape Scale P-Value
## ARCH Lag[3]  0.001035 0.500 2.000  0.9743
## ARCH Lag[5]  0.002206 1.440 1.667  0.9999
## ARCH Lag[7]  0.003222 2.315 1.543  1.0000
## 
## Nyblom stability test
## ------------------------------------
## Joint Statistic:  10.7546
## Individual Statistics:             
## mu     0.2904
## ar1    1.1293
## ma1    1.2492
## omega  2.5116
## alpha1 2.6531
## beta1  3.6909
## shape  5.3402
## 
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic:          1.69 1.9 2.35
## Individual Statistic:     0.35 0.47 0.75
## 
## Sign Bias Test
## ------------------------------------
##                    t-value   prob sig
## Sign Bias          0.75110 0.4526    
## Negative Sign Bias 0.04237 0.9662    
## Positive Sign Bias 0.36043 0.7185    
## Joint Effect       1.04816 0.7896    
## 
## 
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
##   group statistic p-value(g-1)
## 1    20     272.5    8.387e-47
## 2    30     281.4    3.697e-43
## 3    40     285.6    2.865e-39
## 4    50     311.9    5.884e-40
## 
## 
## Elapsed time : 0.133091

El riesgo cambiario es muy persistente. La suma \(\alpha_1+\beta_1=0.999\) indica que, después de un movimiento inesperado fuerte del dólar, la volatilidad tarda mucho en disminuir. Para una empresa con obligaciones en dólares, un episodio de incertidumbre puede mantener elevado el riesgo durante varios periodos.

La volatilidad responde a las sorpresas del mercado. El coeficiente \(\alpha_1=0.3776\) es significativo incluso con errores robustos (\(p=0.0237\)): los movimientos inesperados recientes influyen en el riesgo del siguiente periodo. Asimismo, \(\beta_1=0.6214\) confirma que el riesgo acumulado tiene una influencia importante.

El modelo contempla movimientos extremos. La distribución t de Student, con aproximadamente 3.09 grados de libertad, supone colas gruesas: admite cambios grandes del tipo de cambio con mayor frecuencia que una distribución normal. Sin embargo, la prueba de ajuste rechaza esta distribución, por lo que la estimación de esos riesgos extremos requiere revisión.

No se encuentra evidencia de una respuesta diferente ante subidas y bajadas inesperadas. Las pruebas de sesgo de signo tienen valores \(p>0.05\). Esto permite mantener provisionalmente una respuesta simétrica de la volatilidad, aunque no demuestra que ambos movimientos tengan efectos idénticos.

El modelo necesita mejoras antes de utilizarlo para decisiones financieras. Las pruebas ARCH sugieren que capturó la dependencia de la volatilidad, pero todavía queda correlación en los errores del rendimiento. Además, la prueba de Nyblom rechaza la estabilidad de los parámetros: la relación estimada cambia durante la muestra, lo que limita su fiabilidad para pronosticar y definir coberturas cambiarias. El dato extremo observado en el gráfico anterior también merece revisión, porque podría influir en estos resultados.

El modelo GARCH(1,1), con una ecuación de media ARMA(1,1), presenta coeficientes ARCH y GARCH significativos con errores estándar robustos (\(p=0.0237\) y \(p<0.001\)). La suma \(\alpha_1+\beta_1=0.999\) indica una volatilidad muy persistente, cercana al límite de estacionariedad. El parámetro \(\omega\) no es significativo con errores robustos (\(p=0.2343\)).

Las pruebas sobre los residuos al cuadrado y ARCH-LM no detectan efectos ARCH remanentes, lo que sugiere que la ecuación de varianza captura la dependencia de la volatilidad. Sin embargo, Ljung-Box detecta autocorrelación en los residuos estandarizados, por lo que la ecuación de media necesita revisión. Además, Nyblom rechaza la estabilidad de los parámetros y Pearson rechaza el ajuste de la distribución t de Student.

25 . Pronóstico de volatilidad

pronostico_garch <- ugarchforecast(
  modelo_garch,
  n.ahead = 50
)

sigma_garch <- sigma(pronostico_garch)

plot(
  sigma_garch,
  main = "Pronóstico de volatilidad GARCH",
  ylab = "Volatilidad condicional",
  xlab = "Horizonte",
  pch = 22,
  col = "red"
)

26 . Evaluación fuera de muestra

Una cuestión fundamental en pronósticos es:

¿El modelo funciona fuera de la muestra?

Dividimos los datos:

  • muestra de entrenamiento;
  • muestra de prueba.

Por ejemplo, utilizamos 80% para estimar y 20% para evaluar.

n <- nrow(tc_cierre)

n_train <- floor(0.80 * n)

train <- tc_cierre[1:n_train]
test <- tc_cierre[(n_train + 1):n]

length(train)
## [1] 3496
length(test)
## [1] 875

27 . ARIMA fuera de muestra

modelo_train <- auto.arima(
  train,
  seasonal = FALSE,
  stepwise = FALSE,
  approximation = FALSE
)

pred_train <- forecast(
  modelo_train,
  h = length(test)
)

plot(
  pred_train,
  main = "Pronóstico fuera de muestra"
)

lines(
  test,
  lwd = 2
)

28 . Medidas de precisión

El error de pronóstico es:

\[ e_t=Y_t-\hat{Y}_t \]

28.1 MAE

\[ MAE= \frac{1}{n} \sum_{t=1}^{n}|e_t| \]

28.2 RMSE

\[ RMSE= \sqrt{ \frac{1}{n} \sum_{t=1}^{n}e_t^2 } \]

El RMSE penaliza con mayor intensidad los errores grandes.

En R:

accuracy(
  pred_train,
  test
)
##                        ME       RMSE        MAE         MPE      MAPE      MASE
## Training set  0.001033396 0.05062871 0.02654724 -0.03113793 0.8913436 0.9820886
## Test set     -0.081629182 0.18384565 0.14208861 -2.50062681 4.1051382 5.2564262
##                    ACF1
## Training set 0.00839106
## Test set             NA
  1. Comparación
# Comparación de modelos fuera de muestra

# Fechas comunes del tipo de cambio, DXY y cobre
base_cmp <- datos[, c("PEN_USD", "DXY", "COBRE")]

stopifnot(
  all(is.finite(as.matrix(base_cmp))),
  all(as.matrix(base_cmp) > 0)
)

# Rendimientos porcentuales sobre las mismas fechas
rend_cmp <- na.omit(100 * diff(log(base_cmp)))

# Recuperar los niveles correspondientes a esas fechas
niveles_cmp <- base_cmp[
  zoo::index(rend_cmp), "PEN_USD"
]

N_cmp <- nrow(rend_cmp)
nt_cmp <- floor(0.80 * N_cmp)
h_cmp <- N_cmp - nt_cmp

stopifnot(nt_cmp > 30, h_cmp > 0)

# Entrenamiento y prueba
r_train_cmp <- as.numeric(rend_cmp[1:nt_cmp, "PEN_USD"])

x_train_cmp <- as.matrix(
  rend_cmp[1:nt_cmp, c("DXY", "COBRE")]
)

e_train_cmp <- as.numeric(niveles_cmp[1:nt_cmp])
e_test_cmp <- as.numeric(
  niveles_cmp[(nt_cmp + 1):N_cmp]
)

fechas_cmp <- as.Date(
  zoo::index(niveles_cmp)[(nt_cmp + 1):N_cmp]
)

ultimo_tc_cmp <- tail(e_train_cmp, 1)

# Modelos sobre rendimientos
ar_cmp <- forecast::Arima(
  r_train_cmp, order = c(1, 0, 0)
)

ma_cmp <- forecast::Arima(
  r_train_cmp, order = c(0, 0, 1)
)

arma_cmp <- forecast::Arima(
  r_train_cmp, order = c(1, 0, 1)
)

df_train_cmp <- data.frame(
  PEN_USD = r_train_cmp,
  DXY = x_train_cmp[, "DXY"],
  COBRE = x_train_cmp[, "COBRE"]
)

ols_cmp <- lm(
  PEN_USD ~ DXY + COBRE,
  data = df_train_cmp
)

arimax_cmp <- forecast::auto.arima(
  r_train_cmp,
  xreg = x_train_cmp,
  seasonal = FALSE,
  stepwise = FALSE,
  approximation = FALSE
)

# Modelos sobre el nivel del tipo de cambio
arima_cmp <- forecast::auto.arima(
  e_train_cmp,
  seasonal = FALSE,
  stepwise = FALSE,
  approximation = FALSE
)

# Pronosticar regresores sin utilizar datos del test
x_fut_cmp <- sapply(
  c("DXY", "COBRE"),
  function(variable) {
    ajuste <- forecast::auto.arima(
      x_train_cmp[, variable],
      seasonal = FALSE,
      approximation = FALSE
    )

    as.numeric(
      forecast::forecast(ajuste, h = h_cmp)$mean
    )
  }
)

x_fut_cmp <- matrix(
  x_fut_cmp,
  nrow = h_cmp,
  ncol = 2,
  dimnames = list(NULL, c("DXY", "COBRE"))
)

# GARCH: misma especificación aplicada en el taller
garch_cmp <- rugarch::ugarchfit(
  spec = spec,
  data = r_train_cmp,
  solver = "hybrid"
)

if (garch_cmp@fit$convergence != 0) {
  stop("El GARCH no convergió. Revisar antes de comparar.")
}

garch_fc_cmp <- rugarch::ugarchforecast(
  garch_cmp,
  n.ahead = h_cmp
)

# Convertir rendimientos logarítmicos a trayectoria en niveles
# TC futuro = último TC * exp(rendimiento acumulado / 100)
a_nivel_cmp <- function(r_pronosticado) {
  ultimo_tc_cmp * exp(
    cumsum(as.numeric(r_pronosticado)) / 100
  )
}

# Pronósticos en soles por dólar
pred_cmp <- list(
  "Random Walk" = as.numeric(
    forecast::naive(e_train_cmp, h = h_cmp)$mean
  ),

  "AR(1)" = a_nivel_cmp(
    forecast::forecast(ar_cmp, h = h_cmp)$mean
  ),

  "MA(1)" = a_nivel_cmp(
    forecast::forecast(ma_cmp, h = h_cmp)$mean
  ),

  "ARMA(1,1)" = a_nivel_cmp(
    forecast::forecast(arma_cmp, h = h_cmp)$mean
  ),

  "ARIMA automático" = as.numeric(
    forecast::forecast(arima_cmp, h = h_cmp)$mean
  ),

  "MCO: DXY y cobre" = a_nivel_cmp(
    predict(ols_cmp, newdata = as.data.frame(x_fut_cmp))
  ),

  "ARIMAX: DXY y cobre" = a_nivel_cmp(
    forecast::forecast(
      arimax_cmp,
      xreg = x_fut_cmp
    )$mean
  ),

  "ARMA-GARCH(1,1)" = a_nivel_cmp(
    fitted(garch_fc_cmp)
  )
)
cuadro_cmp <- do.call(
  rbind,
  lapply(names(pred_cmp), function(nombre) {

    error <- e_test_cmp - pred_cmp[[nombre]]

    data.frame(
      Modelo = nombre,
      RMSE = sqrt(mean(error^2)),
      MAE = mean(abs(error)),
      Sesgo = mean(error)
    )
  })
)

# Mejora frente al Random Walk:
# positiva = menor RMSE; negativa = mayor RMSE
rmse_rw_cmp <- cuadro_cmp$RMSE[
  cuadro_cmp$Modelo == "Random Walk"
]

cuadro_cmp$Mejora_RMSE_pct <- if (rmse_rw_cmp > 0) {
  100 * (1 - cuadro_cmp$RMSE / rmse_rw_cmp)
} else {
  NA_real_
}

# Ordenar desde el menor RMSE
cuadro_cmp <- cuadro_cmp[
  order(cuadro_cmp$RMSE), 
]

rownames(cuadro_cmp) <- NULL

knitr::kable(
  cuadro_cmp,
  digits = 4,
  col.names = c(
    "Modelo",
    "RMSE (S/)",
    "MAE (S/)",
    "Sesgo (S/)",
    "Mejora frente al RW (%)"
  ),
  caption = "Precisión fuera de muestra: mismos datos y horizonte"
)
Precisión fuera de muestra: mismos datos y horizonte
Modelo RMSE (S/) MAE (S/) Sesgo (S/) Mejora frente al RW (%)
ARIMA automático 0.1893 0.1436 -0.0906 0.3201
Random Walk 0.1899 0.1438 -0.0919 0.0000
ARIMAX: DXY y cobre 0.2052 0.1524 -0.1202 -8.0059
AR(1) 0.3075 0.2253 -0.2056 -61.9066
MCO: DXY y cobre 0.3217 0.2380 -0.2224 -69.3831
ARMA(1,1) 0.3306 0.2479 -0.2362 -74.0253
MA(1) 0.3307 0.2480 -0.2363 -74.0843
ARMA-GARCH(1,1) 0.6216 0.4901 -0.4857 -227.2623
cat(
  "\n\nEl modelo con menor RMSE es **",
  cuadro_cmp$Modelo[1],
  "**. El modelo con menor MAE es **",
  cuadro_cmp$Modelo[which.min(cuadro_cmp$MAE)],
  "**.\n",
  sep = ""
)
## 
## 
## El modelo con menor RMSE es **ARIMA automático**. El modelo con menor MAE es **ARIMA automático**.
grafico_cmp <- do.call(
  rbind,
  lapply(names(pred_cmp), function(nombre) {
    data.frame(
      Fecha = fechas_cmp,
      Modelo = nombre,
      Observado = e_test_cmp,
      Pronosticado = pred_cmp[[nombre]]
    )
  })
)

grafico_cmp$Modelo <- factor(
  grafico_cmp$Modelo,
  levels = cuadro_cmp$Modelo
)

ggplot(grafico_cmp, aes(x = Fecha)) +
  geom_line(
    aes(y = Observado, colour = "Observado"),
    linewidth = 0.5
  ) +
  geom_line(
    aes(y = Pronosticado, colour = "Pronosticado"),
    linewidth = 0.6
  ) +
  facet_wrap(~ Modelo, ncol = 2) +
  scale_colour_manual(
    values = c(
      "Observado" = "black",
      "Pronosticado" = "blue"
    )
  ) +
  labs(
    title = "Tipo de cambio observado frente a pronosticado",
    subtitle = "Periodo de prueba: modelos ordenados por RMSE",
    x = NULL,
    y = "Soles por dólar",
    colour = NULL
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

errores_cmp <- tidyr::pivot_longer(
  cuadro_cmp[, c("Modelo", "RMSE", "MAE")],
  cols = c("RMSE", "MAE"),
  names_to = "Medida",
  values_to = "Error"
)

errores_cmp$Modelo <- factor(
  errores_cmp$Modelo,
  levels = rev(cuadro_cmp$Modelo)
)

ggplot(errores_cmp, aes(x = Modelo, y = Error)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  facet_wrap(~ Medida, scales = "free_x") +
  labs(
    title = "Comparación de errores fuera de muestra",
    subtitle = "Barras más cortas indican menores errores",
    x = NULL,
    y = "Error en soles por dólar"
  ) +
  theme_minimal()

ggplot(errores_cmp, aes(x = Modelo, y = Error)) +
  geom_col(fill = "steelblue") +
  geom_text(
    aes(label = sprintf("%.4f", Error)),
    hjust = -0.15,
    size = 3.5
  ) +
  scale_y_continuous(
    expand = expansion(mult = c(0, 0.25))
  ) +
  coord_flip() +
  facet_wrap(~ Medida, scales = "free_x") +
  labs(
    title = "Comparación de errores fuera de muestra",
    subtitle = "Barras más cortas indican menores errores",
    x = NULL,
    y = "Error en soles por dólar"
  ) +
  theme_minimal()

vol_cmp <- data.frame(
  Fecha = fechas_cmp,
  Volatilidad = as.numeric(sigma(garch_fc_cmp))
)

ggplot(vol_cmp, aes(Fecha, Volatilidad)) +
  geom_line(colour = "red", linewidth = 0.7) +
  labs(
    title = "Volatilidad pronosticada fuera de muestra",
    subtitle = "GARCH estimado únicamente con entrenamiento",
    x = NULL,
    y = "Desviación estándar del rendimiento (%)"
  ) +
  theme_minimal()

29 . Comparación conceptual

Podemos comparar:

Modelo Pregunta
Random Walk ¿El mejor pronóstico es simplemente el último valor observado?
AR ¿El tipo de cambio depende de sus propios rezagos?
MA ¿Los shocks pasados ayudan a explicar el comportamiento actual?
ARIMA ¿Podemos modelar la dinámica temporal y la no estacionariedad?
ARIMAX ¿Las variables externas aportan información adicional?
GARCH ¿La volatilidad es dinámica y predecible?

30 . Interpretación económica final

Una aplicación empírica del tipo de cambio permite diferenciar tres preguntas:

30.0.1 Pregunta 1: Nivel

\[ E_t \]

¿Cómo se comporta el tipo de cambio?

30.0.2 Pregunta 2: Rendimiento

\[ r_t = 100\ln(E_t/E_{t-1}) \]

¿Qué factores están relacionados con los movimientos diarios?

30.0.3 Pregunta 3: Volatilidad

\[ \sigma_t^2 \]

¿La incertidumbre del mercado cambiario presenta persistencia?

Estas tres preguntas corresponden a problemas econométricos diferentes.

31 . Actividad para los estudiantes

31.1 Ejercicio 1

Descargue el tipo de cambio PEN/USD desde 2015.

  1. Grafique el nivel.
  2. Calcule rendimientos logarítmicos.
  3. Realice la prueba ADF.
  4. Grafique ACF y PACF.
  5. Seleccione un modelo ARIMA.
  6. Analice los residuos.
  7. Genere un pronóstico de 30 días.

31.2 Ejercicio 2

Agregue el índice DXY y el precio del cobre.

Estime:

\[ r^{PEN}_t = \beta_0+ \beta_1r^{DXY}_t+ \beta_2r^{COBRE}_t+ u_t \]

Interprete los coeficientes.

31.3 Ejercicio 3

Estime un ARIMAX y compare su capacidad predictiva con ARIMA.

31.4 Ejercicio 4

Estime un GARCH(1,1).

Analice:

\[ \alpha+\beta \]

y discuta qué implica sobre la persistencia de la volatilidad.

31.5 Ejercicio 5

Compare los modelos utilizando:

  • MAE;
  • RMSE;
  • AIC;
  • BIC;
  • diagnóstico de residuos;
  • desempeño fuera de muestra.

32 . Preguntas de discusión

  1. ¿El tipo de cambio PEN/USD es estacionario en niveles?
  2. ¿Los rendimientos presentan autocorrelación significativa?
  3. ¿El modelo ARIMA supera al Random Walk?
  4. ¿El DXY aporta información para explicar el PEN/USD?
  5. ¿El precio del cobre tiene una relación estadísticamente significativa con el tipo de cambio?
  6. ¿Los residuos presentan autocorrelación?
  7. ¿Existe evidencia de heterocedasticidad?
  8. ¿La volatilidad presenta persistencia?
  9. ¿Un modelo con mayor complejidad necesariamente genera mejores pronósticos?
  10. ¿Qué modelo tiene mejor desempeño fuera de muestra?

33 . Conclusión

El análisis del tipo de cambio permite conectar la teoría de Finanzas Internacionales con la econometría de series temporales.

La secuencia lógica es:

\[ \text{Tipo de cambio} \rightarrow \text{Rendimientos} \rightarrow \text{Estacionariedad} \rightarrow \text{ARIMA} \rightarrow \text{Variables externas} \rightarrow \text{ARIMAX} \rightarrow \text{Volatilidad} \rightarrow \text{GARCH} \rightarrow \text{Pronóstico fuera de muestra} \]

El objetivo no debe ser únicamente encontrar el modelo con mayor cantidad de parámetros, sino determinar si la información adicional mejora de manera consistente la capacidad explicativa y predictiva.