Este informe desarrolla, sobre cinco activos financieros y dos series
de precios no financieras, los siguientes puntos: la serie de
rendimientos compuestos, los gráficos de precios y rendimientos, los
correlogramas ACF y PACF con su interpretación, los tests de raíz
unitaria ADF y ur.df, el test de autocorrelación de
Ljung-Box, y el contraste final entre precios financieros y no
financieros.
Activos financieros (serie diaria de 5 años, fuente
Yahoo Finance, archivo Serie_Precios_5Y.xlsx):
| Clase | Activo | Serie usada |
|---|---|---|
| Renta variable | Coca-Cola (KO) | Cierre ajustado |
| Renta fija | Bono del Tesoro EE. UU. 10 años | Precio teórico construido desde el yield (^TNX) |
| Moneda | USD/COP | Cierre |
| Cripto | Bitcoin (BTC) | Cierre |
| ETF | SPY (S&P 500) | Cierre ajustado |
Series no financieras (trimestrales, Índice de
Precios de Vivienda Nueva del DANE): el total nacional
(IPVN_NAL) y el área urbana de Cali
(IPVN_CALI). Se incluyen las dos a propósito, porque
comparar el agregado con una sola ciudad permite distinguir una
propiedad económica real de un artefacto de medición.
El bono a 10 años no tiene un precio de mercado único y continuo, porque el bono on-the-run rota. Por eso se reporta el yield observado y se construye el precio de un bono homogéneo a 10 años con cupón del 4 % semestral y valor facial 100:
\[P_t = \sum_{k=1}^{20} \frac{2}{(1 + y_t/2)^k} + \frac{100}{(1 + y_t/2)^{20}}\]
tabla(data.frame(
Serie = sapply(S, `[[`, "nombre"),
Clase = sapply(S, `[[`, "clase"),
n = sapply(S, function(s) length(s$precio)),
Inicio = sapply(S, function(s) format(min(s$fecha))),
Fin = sapply(S, function(s) format(max(s$fecha))),
Unidad = sapply(S, `[[`, "unidad")
))
| Serie | Clase | n | Inicio | Fin | Unidad |
|---|---|---|---|---|---|
| KO | Financiera | 1254 | 2021-09-16 | 2026-09-15 | USD por accion |
| BONO10 | Financiera | 1254 | 2021-09-16 | 2026-09-15 | USD por 100 facial |
| USDCOP | Financiera | 1253 | 2021-09-16 | 2026-09-15 | COP por USD |
| BTC | Financiera | 1254 | 2021-09-16 | 2026-09-15 | USD por BTC |
| SPY | Financiera | 1254 | 2021-09-16 | 2026-09-15 | USD por participacion |
| IPVN_NAL | No financiera | 117 | 1997-04-01 | 2026-04-01 | Indice (1997-I = 100) |
| IPVN_CALI | No financiera | 117 | 1997-04-01 | 2026-04-01 | Indice (1997-I = 100) |
La serie de nivel del IPVN se reconstruyó encadenando las variaciones trimestrales oficiales con base 1997-I = 100. La correlación con el índice publicado por el DANE para 2016 en adelante es 1, lo que valida el encadenamiento.
El rendimiento compuesto continuo, o log-rendimiento, se define como
\[r_t = \ln(P_t) - \ln(P_{t-1}) = \ln\!\left(\frac{P_t}{P_{t-1}}\right)\]
y se relaciona con el rendimiento simple mediante \(r_t = \ln(1 + R_t)\). Se usa esta transformación por tres razones: es aditiva en el tiempo, de modo que el rendimiento acumulado es la suma de los rendimientos; es simétrica, así que una caída y una subida de igual magnitud se cancelan; y se aproxima mejor a la normalidad que el rendimiento simple.
rendimiento_compuesto <- function(p) 100 * diff(log(p))
tabla(tabla_desc)
| Serie | Clase | n | Media | DesvEst | Minimo | Maximo | Asimetria | Curtosis | Media_anual | Volat_anual | Acum_compuesto |
|---|---|---|---|---|---|---|---|---|---|---|---|
| KO | Financiera | 1253 | 0.0488 | 1.0509 | -7.2169 | 4.8750 | -0.1341 | 6.6869 | 12.3101 | 16.6828 | 61.2088 |
| BONO10 | Financiera | 1253 | -0.0242 | 0.5137 | -1.7355 | 2.6330 | 0.1627 | 3.9589 | -6.0994 | 8.1555 | -30.3273 |
| USDCOP | Financiera | 1252 | -0.0163 | 0.9697 | -4.4781 | 4.9141 | 0.3943 | 4.6902 | -4.1065 | 15.3935 | -20.4023 |
| BTC | Financiera | 1253 | 0.0368 | 3.3173 | -25.7227 | 18.1200 | -0.2850 | 8.7631 | 9.2743 | 52.6608 | 46.1138 |
| SPY | Financiera | 1253 | 0.0475 | 1.0828 | -6.0327 | 9.9863 | 0.1516 | 10.8292 | 11.9586 | 17.1887 | 59.4609 |
| IPVN_NAL | No financiera | 116 | 1.9104 | 1.2329 | -3.1181 | 5.5529 | -0.4550 | 5.2828 | 7.6417 | 2.4658 | 221.6104 |
| IPVN_CALI | No financiera | 116 | 1.9352 | 2.7915 | -8.7848 | 7.9088 | -0.6844 | 5.0325 | 7.7408 | 5.5830 | 224.4835 |
La volatilidad y el rendimiento medio se anualizan con 252 días hábiles en las series diarias y con 4 trimestres en las series de vivienda. Nótese la curtosis: en una distribución normal vale 3, y todas las series financieras la superan ampliamente, lo que indica colas pesadas.
Series de precios en niveles. Todas muestran trayectorias sin reversión a una media fija.
Precios financieros indexados a 100 en el primer día, para comparar en una sola escala.
Rendimientos compuestos continuos. Las líneas rojas marcan +/- 2 desviaciones típicas. En los activos financieros se aprecia el agrupamiento de volatilidad.
El contraste visual entre los dos gráficos anteriores es el punto de partida de todo el análisis: los precios vagan sin volver a un nivel de referencia, mientras que los rendimientos oscilan alrededor de cero con una amplitud aproximadamente estable.
Una autocorrelación se considera significativa si su barra supera la banda de \(\pm 1.96/\sqrt{n}\), la línea azul punteada de los gráficos.
tabla(tabla_acf, digitos = 3)
| Serie | Clase | n | Banda_95 | ACF1_precio | ACFmedio_precio | ACFmax_precio | Signif_ACF_precio | Signif_PACF_precio | Firma_precio | ACF1_rend | Signif_ACF_rend | Signif_PACF_rend | Firma_rend |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KO | Financiera | 1254 | 0.055 | 0.994 | 0.872 | 0.762 | 20/20 | 1/20 | raiz_unitaria | -0.006 | 1/20 | 1/20 | ruido_blanco |
| BONO10 | Financiera | 1254 | 0.055 | 0.994 | 0.888 | 0.780 | 20/20 | 1/20 | raiz_unitaria | 0.001 | 2/20 | 2/20 | ruido_blanco |
| USDCOP | Financiera | 1253 | 0.055 | 0.991 | 0.849 | 0.735 | 20/20 | 4/20 | raiz_unitaria | -0.051 | 6/20 | 7/20 | memoria_corta |
| BTC | Financiera | 1254 | 0.055 | 0.998 | 0.953 | 0.910 | 20/20 | 5/20 | raiz_unitaria | -0.010 | 0/20 | 0/20 | ruido_blanco |
| SPY | Financiera | 1254 | 0.055 | 0.997 | 0.945 | 0.893 | 20/20 | 1/20 | raiz_unitaria | -0.025 | 3/20 | 3/20 | memoria_corta |
| IPVN_NAL | No financiera | 117 | 0.181 | 0.968 | 0.653 | 0.383 | 20/20 | 1/20 | raiz_unitaria | 0.534 | 5/20 | 2/20 | memoria_corta |
| IPVN_CALI | No financiera | 117 | 0.181 | 0.964 | 0.613 | 0.350 | 20/20 | 1/20 | raiz_unitaria | -0.056 | 2/20 | 1/20 | ruido_blanco |
Correlogramas de KO
Correlogramas de BONO10
Correlogramas de USDCOP
Correlogramas de BTC
Correlogramas de SPY
Correlogramas de IPVN_NAL
Correlogramas de IPVN_CALI
1. INTERPRETACION DE LOS CORRELOGRAMAS (ACF y PACF)
------------------------------------------------------------------------------
Regla de lectura: una autocorrelacion es significativa si su barra supera
la banda de +/- 1.96/raiz(n), la linea azul punteada del grafico.
* Coca-Cola (KO) - Renta variable (banda 95% = +/-0.055)
PRECIOS: ACF(1) = 0.994, ACF(20) = 0.872, ACF(40) = 0.762.
Rezagos significativos: ACF 20/20, PACF 1/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.994).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = -0.006, rezagos significativos ACF 1/20, PACF 1/20.
Casi todas las barras caen dentro de la banda: el rendimiento se
comporta como ruido blanco. Al tomar la primera diferencia del
logaritmo desaparecio toda la persistencia, es decir, el precio era
integrado de orden 1, I(1), y el rendimiento es I(0).
* Bono Tesoro EE.UU. 10Y - Renta fija (banda 95% = +/-0.055)
PRECIOS: ACF(1) = 0.994, ACF(20) = 0.888, ACF(40) = 0.78.
Rezagos significativos: ACF 20/20, PACF 1/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.994).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = 0.001, rezagos significativos ACF 2/20, PACF 2/20.
Casi todas las barras caen dentro de la banda: el rendimiento se
comporta como ruido blanco. Al tomar la primera diferencia del
logaritmo desaparecio toda la persistencia, es decir, el precio era
integrado de orden 1, I(1), y el rendimiento es I(0).
* USD/COP - Moneda (banda 95% = +/-0.055)
PRECIOS: ACF(1) = 0.991, ACF(20) = 0.849, ACF(40) = 0.735.
Rezagos significativos: ACF 20/20, PACF 4/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.991).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = -0.051, rezagos significativos ACF 6/20, PACF 7/20.
Quedan 6 rezagos fuera de la banda: la diferencia
elimino la raiz unitaria pero podria sobrevivir algo de estructura
lineal. Hay que ser prudente al leer barras aisladas: al contrastar
20 rezagos al 5% se espera 1 exceso por puro azar.
El test conjunto de Ljung-Box con 10 rezagos da p = 0.00000644 y SI
rechaza el ruido blanco, de modo que la estructura es real y un
modelo ARMA de orden bajo podria recogerla.
* Bitcoin (BTC) - Cripto (banda 95% = +/-0.055)
PRECIOS: ACF(1) = 0.998, ACF(20) = 0.953, ACF(40) = 0.91.
Rezagos significativos: ACF 20/20, PACF 5/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.998).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = -0.01, rezagos significativos ACF 0/20, PACF 0/20.
Casi todas las barras caen dentro de la banda: el rendimiento se
comporta como ruido blanco. Al tomar la primera diferencia del
logaritmo desaparecio toda la persistencia, es decir, el precio era
integrado de orden 1, I(1), y el rendimiento es I(0).
* SPY S&P 500 - ETF (banda 95% = +/-0.055)
PRECIOS: ACF(1) = 0.997, ACF(20) = 0.945, ACF(40) = 0.893.
Rezagos significativos: ACF 20/20, PACF 1/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.997).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = -0.025, rezagos significativos ACF 3/20, PACF 3/20.
Quedan 3 rezagos fuera de la banda: la diferencia
elimino la raiz unitaria pero podria sobrevivir algo de estructura
lineal. Hay que ser prudente al leer barras aisladas: al contrastar
20 rezagos al 5% se espera 1 exceso por puro azar.
El test conjunto de Ljung-Box con 10 rezagos da p = 0.593 y NO
rechaza la hipotesis de ruido blanco. Es decir, las barras que
sobresalen no forman un patron: se tratan como falsos positivos y
la conclusion practica es la misma que en el caso anterior, el
rendimiento es impredecible en media.
* IPVN total nacional - NO financiera (banda 95% = +/-0.181)
PRECIOS: ACF(1) = 0.968, ACF(12) = 0.653, ACF(24) = 0.383.
Rezagos significativos: ACF 20/20, PACF 1/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.968).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = 0.534, rezagos significativos ACF 5/20, PACF 2/20.
Quedan 5 rezagos fuera de la banda: la diferencia
elimino la raiz unitaria pero podria sobrevivir algo de estructura
lineal. Hay que ser prudente al leer barras aisladas: al contrastar
20 rezagos al 5% se espera 1 exceso por puro azar.
El test conjunto de Ljung-Box con 8 rezagos da p = <0.0000000000000001 y SI
rechaza el ruido blanco, de modo que la estructura es real y un
modelo ARMA de orden bajo podria recogerla.
* IPVN Cali AU - NO financiera (banda 95% = +/-0.181)
PRECIOS: ACF(1) = 0.964, ACF(12) = 0.613, ACF(24) = 0.35.
Rezagos significativos: ACF 20/20, PACF 1/20.
La ACF arranca practicamente en 1 y decae muy despacio, casi de
forma lineal, sin volver dentro de la banda en ningun rezago. La
PACF en cambio se corta tras el primer rezago (PACF(1) = 0.964).
Esa combinacion es la firma de un proceso NO estacionario con raiz
unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:
el nivel de hoy es el mejor pronostico del nivel de manana y los
choques no se olvidan.
RENDIMIENTOS: ACF(1) = -0.056, rezagos significativos ACF 2/20, PACF 1/20.
Casi todas las barras caen dentro de la banda: el rendimiento se
comporta como ruido blanco. Al tomar la primera diferencia del
logaritmo desaparecio toda la persistencia, es decir, el precio era
integrado de orden 1, I(1), y el rendimiento es I(0).
Sintesis de los correlogramas:
- En las 7 series el correlograma del PRECIO tiene la
misma forma: ACF cercana a 1 que decae con lentitud y PACF que se corta
en el primer rezago. Ninguna serie de precios es estacionaria.
- El correlograma del RENDIMIENTO es radicalmente distinto: las barras
colapsan dentro de la banda. Diferenciar el logaritmo es lo que
convierte una serie no estacionaria en una utilizable para modelar.
- El correlograma es un diagnostico visual: sugiere la raiz unitaria pero
no la contrasta formalmente. Para eso estan el ADF y el ur.df.
tseries::adf.test)\(H_0\): la serie tiene raíz unitaria, es decir, no es estacionaria. Se rechaza \(H_0\) cuando el p-valor es menor que 0.05.
tabla(tabla_adf)
| Serie | Tipo | n | Rezagos | Estad_DF | p_valor | Decision |
|---|---|---|---|---|---|---|
| KO | Precio | 1254 | 10 | -1.4556 | 0.8088 | No rechaza H0: NO ESTACIONARIA |
| KO | Rendimiento | 1253 | 10 | -11.2657 | 0.0100 | Rechaza H0: ESTACIONARIA |
| BONO10 | Precio | 1254 | 10 | -2.5243 | 0.3564 | No rechaza H0: NO ESTACIONARIA |
| BONO10 | Rendimiento | 1253 | 10 | -10.4320 | 0.0100 | Rechaza H0: ESTACIONARIA |
| USDCOP | Precio | 1253 | 10 | -2.0032 | 0.5770 | No rechaza H0: NO ESTACIONARIA |
| USDCOP | Rendimiento | 1252 | 10 | -9.6767 | 0.0100 | Rechaza H0: ESTACIONARIA |
| BTC | Precio | 1254 | 10 | -1.7774 | 0.6726 | No rechaza H0: NO ESTACIONARIA |
| BTC | Rendimiento | 1253 | 10 | -10.3583 | 0.0100 | Rechaza H0: ESTACIONARIA |
| SPY | Precio | 1254 | 10 | -2.1771 | 0.5033 | No rechaza H0: NO ESTACIONARIA |
| SPY | Rendimiento | 1253 | 10 | -10.6350 | 0.0100 | Rechaza H0: ESTACIONARIA |
| IPVN_NAL | Precio | 117 | 4 | 0.4437 | 0.9900 | No rechaza H0: NO ESTACIONARIA |
| IPVN_NAL | Rendimiento | 116 | 4 | -2.7542 | 0.2635 | No rechaza H0: NO ESTACIONARIA |
| IPVN_CALI | Precio | 117 | 4 | 0.7917 | 0.9900 | No rechaza H0: NO ESTACIONARIA |
| IPVN_CALI | Rendimiento | 116 | 4 | -3.7998 | 0.0214 | Rechaza H0: ESTACIONARIA |
Conviene recordar dos limitaciones de adf.test(): impone
siempre constante y tendencia, y fija los rezagos en \(\lfloor (n-1)^{1/3} \rfloor\). Además su
p-valor está truncado en el intervalo \([0.01,
0.99]\), por lo que un valor de exactamente 0.01 es el piso de la
tabla y no el p-valor verdadero.
ur.df (urca)Mismo \(H_0\), pero el estadístico \(\tau\) se compara con los valores críticos de Dickey-Fuller, no con la normal ni con la \(t\). Se rechaza \(H_0\) si \(\tau <\) valor crítico al 5 %. Las tres especificaciones del componente determinista son:
none: \(\Delta y_t = \gamma
y_{t-1} + \dots\)drift: \(\Delta y_t = \alpha
+ \gamma y_{t-1} + \dots\)trend: \(\Delta y_t = \alpha
+ \beta t + \gamma y_{t-1} + \dots\)tabla(tabla_urdf)
| Serie | Tipo | Especif | Estad_tau | VC_1pct | VC_5pct | VC_10pct | Decision |
|---|---|---|---|---|---|---|---|
| KO | Precio | none | 1.7937 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| KO | Precio | drift | -0.1005 | -3.43 | -2.86 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| KO | Precio | trend | -1.8472 | -3.96 | -3.41 | -3.12 | No rechaza H0 al 5%: NO ESTACIONARIA |
| KO | Rendimiento | none | -25.0904 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| KO | Rendimiento | drift | -25.1760 | -3.43 | -2.86 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| KO | Rendimiento | trend | -25.1724 | -3.96 | -3.41 | -3.12 | Rechaza H0 al 5%: ESTACIONARIA |
| BONO10 | Precio | none | -1.9736 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| BONO10 | Precio | drift | -2.5853 | -3.43 | -2.86 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| BONO10 | Precio | trend | -2.5819 | -3.96 | -3.41 | -3.12 | No rechaza H0 al 5%: NO ESTACIONARIA |
| BONO10 | Rendimiento | none | -26.7044 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| BONO10 | Rendimiento | drift | -26.7784 | -3.43 | -2.86 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| BONO10 | Rendimiento | trend | -26.7954 | -3.96 | -3.41 | -3.12 | Rechaza H0 al 5%: ESTACIONARIA |
| USDCOP | Precio | none | -0.5805 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| USDCOP | Precio | drift | -1.1405 | -3.43 | -2.86 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| USDCOP | Precio | trend | -2.0716 | -3.96 | -3.41 | -3.12 | No rechaza H0 al 5%: NO ESTACIONARIA |
| USDCOP | Rendimiento | none | -9.5339 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| USDCOP | Rendimiento | drift | -9.5515 | -3.43 | -2.86 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| USDCOP | Rendimiento | trend | -9.7305 | -3.96 | -3.41 | -3.12 | Rechaza H0 al 5%: ESTACIONARIA |
| BTC | Precio | none | -0.2351 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| BTC | Precio | drift | -1.1920 | -3.43 | -2.86 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| BTC | Precio | trend | -1.9769 | -3.96 | -3.41 | -3.12 | No rechaza H0 al 5%: NO ESTACIONARIA |
| BTC | Rendimiento | none | -19.5362 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| BTC | Rendimiento | drift | -19.5314 | -3.43 | -2.86 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| BTC | Rendimiento | trend | -19.5368 | -3.96 | -3.41 | -3.12 | Rechaza H0 al 5%: ESTACIONARIA |
| SPY | Precio | none | 2.0112 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| SPY | Precio | drift | 0.3290 | -3.43 | -2.86 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| SPY | Precio | trend | -2.3245 | -3.96 | -3.41 | -3.12 | No rechaza H0 al 5%: NO ESTACIONARIA |
| SPY | Rendimiento | none | -21.5359 | -2.58 | -1.95 | -1.62 | Rechaza H0 al 5%: ESTACIONARIA |
| SPY | Rendimiento | drift | -21.6270 | -3.43 | -2.86 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| SPY | Rendimiento | trend | -21.6499 | -3.96 | -3.41 | -3.12 | Rechaza H0 al 5%: ESTACIONARIA |
| IPVN_NAL | Precio | none | 2.7023 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_NAL | Precio | drift | 2.2513 | -3.46 | -2.88 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_NAL | Precio | trend | 1.0568 | -3.99 | -3.43 | -3.13 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_NAL | Rendimiento | none | -0.9368 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_NAL | Rendimiento | drift | -5.5157 | -3.46 | -2.88 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| IPVN_NAL | Rendimiento | trend | -5.4426 | -3.99 | -3.43 | -3.13 | Rechaza H0 al 5%: ESTACIONARIA |
| IPVN_CALI | Precio | none | 4.3556 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_CALI | Precio | drift | 3.8433 | -3.46 | -2.88 | -2.57 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_CALI | Precio | trend | 1.8295 | -3.99 | -3.43 | -3.13 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_CALI | Rendimiento | none | -1.4340 | -2.58 | -1.95 | -1.62 | No rechaza H0 al 5%: NO ESTACIONARIA |
| IPVN_CALI | Rendimiento | drift | -7.1176 | -3.46 | -2.88 | -2.57 | Rechaza H0 al 5%: ESTACIONARIA |
| IPVN_CALI | Rendimiento | trend | -7.0598 | -3.99 | -3.43 | -3.13 | Rechaza H0 al 5%: ESTACIONARIA |
Las regresiones completas, con los rezagos elegidos por AIC y los
estadísticos conjuntos \(\phi_1\),
\(\phi_2\) y \(\phi_3\), están en
resultados/04b_urdf_salidas_completas.txt.
2. INTERPRETACION DE LOS TESTS DE ESTACIONARIEDAD (ADF y ur.df)
------------------------------------------------------------------------------
ADF (tseries::adf.test): H0 = la serie tiene raiz unitaria (NO
estacionaria). Se rechaza H0 si el p-valor < 0.05.
ur.df (urca::ur.df): mismo H0, pero el estadistico tau se compara con los
valores criticos de Dickey-Fuller, no con la normal ni con la t. Se
rechaza H0 si tau < valor critico al 5% (la region critica esta en la
cola izquierda). Permite elegir la parte determinista del modelo.
* Coca-Cola (KO) - Renta variable
ADF precio : DF = -1.456, 10 rezagos, p = 0.809 -> NO estacionaria
ADF rendimiento : DF = -11.266, 10 rezagos, p = 0.01 -> ESTACIONARIA
ur.df precio (none ) : tau = 1.794 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = -0.100 VC5% = -2.86 -> NO estacionaria
ur.df precio (trend) : tau = -1.847 VC5% = -3.41 -> NO estacionaria
ur.df rendim. (none ) : tau = -25.090 VC5% = -1.95 -> ESTACIONARIA
ur.df rendim. (drift) : tau = -25.176 VC5% = -2.86 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -25.172 VC5% = -3.41 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
* Bono Tesoro EE.UU. 10Y - Renta fija
ADF precio : DF = -2.524, 10 rezagos, p = 0.356 -> NO estacionaria
ADF rendimiento : DF = -10.432, 10 rezagos, p = 0.01 -> ESTACIONARIA
ur.df precio (none ) : tau = -1.974 VC5% = -1.95 -> ESTACIONARIA
ur.df precio (drift) : tau = -2.585 VC5% = -2.86 -> NO estacionaria
ur.df precio (trend) : tau = -2.582 VC5% = -3.41 -> NO estacionaria
ur.df rendim. (none ) : tau = -26.704 VC5% = -1.95 -> ESTACIONARIA
ur.df rendim. (drift) : tau = -26.778 VC5% = -2.86 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -26.795 VC5% = -3.41 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
* USD/COP - Moneda
ADF precio : DF = -2.003, 10 rezagos, p = 0.577 -> NO estacionaria
ADF rendimiento : DF = -9.677, 10 rezagos, p = 0.01 -> ESTACIONARIA
ur.df precio (none ) : tau = -0.581 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = -1.140 VC5% = -2.86 -> NO estacionaria
ur.df precio (trend) : tau = -2.072 VC5% = -3.41 -> NO estacionaria
ur.df rendim. (none ) : tau = -9.534 VC5% = -1.95 -> ESTACIONARIA
ur.df rendim. (drift) : tau = -9.552 VC5% = -2.86 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -9.731 VC5% = -3.41 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
* Bitcoin (BTC) - Cripto
ADF precio : DF = -1.777, 10 rezagos, p = 0.673 -> NO estacionaria
ADF rendimiento : DF = -10.358, 10 rezagos, p = 0.01 -> ESTACIONARIA
ur.df precio (none ) : tau = -0.235 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = -1.192 VC5% = -2.86 -> NO estacionaria
ur.df precio (trend) : tau = -1.977 VC5% = -3.41 -> NO estacionaria
ur.df rendim. (none ) : tau = -19.536 VC5% = -1.95 -> ESTACIONARIA
ur.df rendim. (drift) : tau = -19.531 VC5% = -2.86 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -19.537 VC5% = -3.41 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
* SPY S&P 500 - ETF
ADF precio : DF = -2.177, 10 rezagos, p = 0.503 -> NO estacionaria
ADF rendimiento : DF = -10.635, 10 rezagos, p = 0.01 -> ESTACIONARIA
ur.df precio (none ) : tau = 2.011 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = 0.329 VC5% = -2.86 -> NO estacionaria
ur.df precio (trend) : tau = -2.324 VC5% = -3.41 -> NO estacionaria
ur.df rendim. (none ) : tau = -21.536 VC5% = -1.95 -> ESTACIONARIA
ur.df rendim. (drift) : tau = -21.627 VC5% = -2.86 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -21.650 VC5% = -3.41 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
* IPVN total nacional - NO financiera
ADF precio : DF = 0.444, 4 rezagos, p = 0.99 -> NO estacionaria
ADF rendimiento : DF = -2.754, 4 rezagos, p = 0.263 -> NO estacionaria
ur.df precio (none ) : tau = 2.702 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = 2.251 VC5% = -2.88 -> NO estacionaria
ur.df precio (trend) : tau = 1.057 VC5% = -3.43 -> NO estacionaria
ur.df rendim. (none ) : tau = -0.937 VC5% = -1.95 -> NO estacionaria
ur.df rendim. (drift) : tau = -5.516 VC5% = -2.88 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -5.443 VC5% = -3.43 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario segun ur.df (-5.52 < -2.88) pero NO segun ADF
(p = 0.263). Los tests DISCREPAN: ver la nota al final de esta seccion.
* IPVN Cali AU - NO financiera
ADF precio : DF = 0.792, 4 rezagos, p = 0.99 -> NO estacionaria
ADF rendimiento : DF = -3.8, 4 rezagos, p = 0.0214 -> ESTACIONARIA
ur.df precio (none ) : tau = 4.356 VC5% = -1.95 -> NO estacionaria
ur.df precio (drift) : tau = 3.843 VC5% = -2.88 -> NO estacionaria
ur.df precio (trend) : tau = 1.830 VC5% = -3.43 -> NO estacionaria
ur.df rendim. (none ) : tau = -1.434 VC5% = -1.95 -> NO estacionaria
ur.df rendim. (drift) : tau = -7.118 VC5% = -2.88 -> ESTACIONARIA
ur.df rendim. (trend) : tau = -7.060 VC5% = -3.43 -> ESTACIONARIA
Conclusion: el precio es NO estacionario, integrado de orden 1, I(1) y el rendimiento
compuesto es estacionario, I(0). Los dos tests coinciden.
Lectura conjunta de ambos tests:
- Coincidencia: 13 de 14 diagnosticos son iguales entre ADF y ur.df
(usando 'trend' para el precio y 'drift' para el rendimiento). Que dos
tests con implementaciones distintas lleguen al mismo sitio da robustez.
- DISCREPANCIA en: IPVN_NAL (rendimiento).
Vale la pena detenerse aqui, porque es el caso que muestra por que no
basta con un solo test.
* IPVN_NAL - Rendimiento: el ADF no concluye lo mismo que el ur.df.
ADF : DF = -2.754, p = 0.263 -> NO estacionaria (n = 116, 4 rezagos fijos)
ur.df : tau = -5.516 vs VC5% = -2.88 -> estacionaria (especificacion drift)
Por que difieren. Son dos regresiones distintas sobre los mismos
datos. adf.test() incluye SIEMPRE tendencia y fija los rezagos en
trunc((n-1)^(1/3)) = 4; ur.df() aqui usa drift y elige los
rezagos por AIC. Meter una tendencia que la serie no necesita, y
arrastrar rezagos innecesarios, consume grados de libertad y
resta POTENCIA al test. Con una muestra corta (n = 116) esa
perdida de potencia basta para no rechazar una H0 que es falsa.
En este caso se debe preferir el ur.df bien especificado: la
serie es estacionaria y el ADF esta cometiendo error de tipo II.
Es tambien la razon por la que el enunciado pide los dos tests.
- Que aporta cada uno. adf.test() es una funcion de conveniencia: impone
constante y tendencia, fija los rezagos en trunc((n-1)^(1/3)) y entrega
un p-valor interpolado y truncado en [0.01, 0.99]. Por eso muchos
rendimientos muestran exactamente p = 0.01: es el piso de la tabla, no
el p-valor verdadero, que es mucho menor.
- ur.df() obliga a decidir la especificacion y muestra la regresion
completa, los rezagos elegidos por AIC y los estadisticos conjuntos
phi1, phi2 y phi3. Es el que se debe reportar en un trabajo formal.
- La especificacion importa y se ve en los resultados. Sobre un precio
con tendencia, 'none' omite la constante y la tendencia y sesga el test
hacia el no rechazo. Sobre un rendimiento con media distinta de cero,
'none' tambien esta mal especificado: por eso conviene 'drift'.
- Implicacion practica: no se debe modelar ni correr regresiones con los
precios en niveles, porque llevaria a regresiones espurias (Granger y
Newbold). Se trabaja con la primera diferencia del logaritmo, es decir,
con el rendimiento compuesto.
\(H_0\): \(\rho_1 = \rho_2 = \dots = \rho_m = 0\), la serie es ruido blanco. Se rechaza \(H_0\) si el p-valor es menor que 0.05, lo que indica que sí hay autocorrelación.
\[Q_{BP} = n \sum_{k=1}^{m} \hat\rho_k^2 \qquad\qquad Q_{LB} = n(n+2) \sum_{k=1}^{m} \frac{\hat\rho_k^2}{n-k} \sim \chi^2_m\]
tabla(tabla_lb)
| Serie | Tipo | Rezagos | Q_LjungBox | p_LjungBox | Q_BoxPierce | p_BoxPierce | Decision_LB |
|---|---|---|---|---|---|---|---|
| KO | Precio | 5 | 6059.1569 | 0.0000 | 6035.1507 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| KO | Precio | 10 | 11776.1903 | 0.0000 | 11706.7714 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| KO | Precio | 20 | 22162.1654 | 0.0000 | 21949.0107 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| KO | Rendimiento | 5 | 1.9394 | 0.8575 | 1.9303 | 0.8587 | No rechaza H0: sin autocorrelacion |
| KO | Rendimiento | 10 | 4.4047 | 0.9272 | 4.3774 | 0.9287 | No rechaza H0: sin autocorrelacion |
| KO | Rendimiento | 20 | 23.0780 | 0.2850 | 22.7913 | 0.2992 | No rechaza H0: sin autocorrelacion |
| BONO10 | Precio | 5 | 6061.4265 | 0.0000 | 6037.4091 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Precio | 10 | 11797.5750 | 0.0000 | 11727.9844 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Precio | 20 | 22382.4253 | 0.0000 | 22166.1011 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Rendimiento | 5 | 7.9264 | 0.1603 | 7.8986 | 0.1619 | No rechaza H0: sin autocorrelacion |
| BONO10 | Rendimiento | 10 | 9.9231 | 0.4473 | 9.8796 | 0.4511 | No rechaza H0: sin autocorrelacion |
| BONO10 | Rendimiento | 20 | 19.0507 | 0.5185 | 18.8793 | 0.5297 | No rechaza H0: sin autocorrelacion |
| USDCOP | Precio | 5 | 5981.3922 | 0.0000 | 5957.7081 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Precio | 10 | 11548.7412 | 0.0000 | 11480.8189 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Precio | 20 | 21410.0751 | 0.0000 | 21205.6886 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento | 5 | 21.2121 | 0.0007 | 21.1123 | 0.0008 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento | 10 | 42.3704 | 0.0000 | 42.0930 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento | 20 | 64.1817 | 0.0000 | 63.6044 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BTC | Precio | 5 | 6211.4142 | 0.0000 | 6186.7243 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BTC | Precio | 10 | 12306.0501 | 0.0000 | 12232.8735 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BTC | Precio | 20 | 24124.6539 | 0.0000 | 23887.1302 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BTC | Rendimiento | 5 | 3.8989 | 0.5641 | 3.8831 | 0.5664 | No rechaza H0: sin autocorrelacion |
| BTC | Rendimiento | 10 | 8.1461 | 0.6146 | 8.0949 | 0.6196 | No rechaza H0: sin autocorrelacion |
| BTC | Rendimiento | 20 | 18.2408 | 0.5716 | 18.0510 | 0.5841 | No rechaza H0: sin autocorrelacion |
| SPY | Precio | 5 | 6190.3323 | 0.0000 | 6165.7364 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| SPY | Precio | 10 | 12234.4857 | 0.0000 | 12161.8140 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| SPY | Precio | 20 | 23880.2382 | 0.0000 | 23645.6858 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| SPY | Rendimiento | 5 | 6.1811 | 0.2890 | 6.1566 | 0.2913 | No rechaza H0: sin autocorrelacion |
| SPY | Rendimiento | 10 | 8.3720 | 0.5926 | 8.3287 | 0.5968 | No rechaza H0: sin autocorrelacion |
| SPY | Rendimiento | 20 | 26.2018 | 0.1592 | 25.8824 | 0.1697 | No rechaza H0: sin autocorrelacion |
| IPVN_NAL | Precio | 4 | 413.9683 | 0.0000 | 398.5630 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Precio | 8 | 739.3987 | 0.0000 | 700.9617 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Precio | 12 | 990.2796 | 0.0000 | 925.6678 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento | 4 | 80.9938 | 0.0000 | 78.1877 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento | 8 | 96.6862 | 0.0000 | 92.8418 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento | 12 | 99.7535 | 0.0000 | 95.5592 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_CALI | Precio | 4 | 403.6770 | 0.0000 | 388.7040 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_CALI | Precio | 8 | 706.7469 | 0.0000 | 670.3524 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_CALI | Precio | 12 | 930.9332 | 0.0000 | 871.1700 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_CALI | Rendimiento | 4 | 3.6318 | 0.4581 | 3.4962 | 0.4785 | No rechaza H0: sin autocorrelacion |
| IPVN_CALI | Rendimiento | 8 | 8.2348 | 0.4109 | 7.8198 | 0.4513 | No rechaza H0: sin autocorrelacion |
| IPVN_CALI | Rendimiento | 12 | 15.2188 | 0.2297 | 14.0380 | 0.2983 | No rechaza H0: sin autocorrelacion |
Aplicar el mismo test a \(r_t^2\) detecta el agrupamiento de volatilidad, o efecto ARCH, uno de los hechos estilizados de las series financieras.
tabla(tabla_arch)
| Serie | Tipo | Rezagos | Q_LjungBox | p_LjungBox | Q_BoxPierce | p_BoxPierce | Decision_LB |
|---|---|---|---|---|---|---|---|
| KO | Rendimiento^2 | 5 | 22.3116 | 0.0005 | 22.2391 | 0.0005 | Rechaza H0: HAY autocorrelacion |
| KO | Rendimiento^2 | 10 | 44.9056 | 0.0000 | 44.6432 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| KO | Rendimiento^2 | 20 | 56.9683 | 0.0000 | 56.5520 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Rendimiento^2 | 5 | 92.8316 | 0.0000 | 92.4583 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Rendimiento^2 | 10 | 108.3346 | 0.0000 | 107.8475 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BONO10 | Rendimiento^2 | 20 | 167.1898 | 0.0000 | 165.8700 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento^2 | 5 | 43.2500 | 0.0000 | 43.1002 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento^2 | 10 | 45.3791 | 0.0000 | 45.2134 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| USDCOP | Rendimiento^2 | 20 | 61.3221 | 0.0000 | 60.9323 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| BTC | Rendimiento^2 | 5 | 16.4200 | 0.0057 | 16.3630 | 0.0059 | Rechaza H0: HAY autocorrelacion |
| BTC | Rendimiento^2 | 10 | 22.6844 | 0.0120 | 22.5734 | 0.0124 | Rechaza H0: HAY autocorrelacion |
| BTC | Rendimiento^2 | 20 | 29.2961 | 0.0821 | 29.0903 | 0.0860 | No rechaza H0: sin autocorrelacion |
| SPY | Rendimiento^2 | 5 | 209.2681 | 0.0000 | 208.4461 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| SPY | Rendimiento^2 | 10 | 238.9577 | 0.0000 | 237.8947 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| SPY | Rendimiento^2 | 20 | 257.2153 | 0.0000 | 255.9009 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento^2 | 4 | 37.2930 | 0.0000 | 36.0954 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento^2 | 8 | 39.7524 | 0.0000 | 38.3941 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_NAL | Rendimiento^2 | 12 | 45.1252 | 0.0000 | 43.1651 | 0.0000 | Rechaza H0: HAY autocorrelacion |
| IPVN_CALI | Rendimiento^2 | 4 | 3.1208 | 0.5378 | 3.0258 | 0.5535 | No rechaza H0: sin autocorrelacion |
| IPVN_CALI | Rendimiento^2 | 8 | 6.1032 | 0.6357 | 5.7674 | 0.6733 | No rechaza H0: sin autocorrelacion |
| IPVN_CALI | Rendimiento^2 | 12 | 9.3900 | 0.6693 | 8.7406 | 0.7249 | No rechaza H0: sin autocorrelacion |
Un índice de vivienda podría tener un patrón por trimestre. Se contrasta con un ANOVA de la variación trimestral contra el factor trimestre.
tabla(tabla_estac)
| Serie | Media_I | Media_II | Media_III | Media_IV | F_valor | p_valor | Decision |
|---|---|---|---|---|---|---|---|
| IPVN_NAL | 2.2963 | 1.9085 | 1.7714 | 1.6655 | 1.4691 | 0.2268 | No rechaza H0: sin estacionalidad |
| IPVN_CALI | 2.0765 | 1.8220 | 1.6468 | 2.1955 | 0.2233 | 0.8800 | No rechaza H0: sin estacionalidad |
3. INTERPRETACION DE LOS TESTS DE AUTOCORRELACION (Ljung-Box y Box-Pierce)
------------------------------------------------------------------------------
H0: rho_1 = ... = rho_m = 0, las primeras m autocorrelaciones son todas
cero, es decir, la serie es ruido blanco.
Se rechaza H0 si p-valor < 0.05: SI hay autocorrelacion.
* Coca-Cola (KO) - Renta variable
Precios Q(5/10/20) = 6059.2 / 11776.2 / 22162.2 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(5/10/20) = 1.9 / 4.4 / 23.1 p = 0.857 / 0.927 / 0.285
Rendim.^2 Q(5/10/20) = 22.3 / 44.9 / 57 p = 0.000457 / 0.00000226 / 0.0000208
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay
autocorrelacion lineal detectable.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* Bono Tesoro EE.UU. 10Y - Renta fija
Precios Q(5/10/20) = 6061.4 / 11797.6 / 22382.4 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(5/10/20) = 7.9 / 9.9 / 19.1 p = 0.16 / 0.447 / 0.519
Rendim.^2 Q(5/10/20) = 92.8 / 108.3 / 167.2 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay
autocorrelacion lineal detectable.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* USD/COP - Moneda
Precios Q(5/10/20) = 5981.4 / 11548.7 / 21410.1 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(5/10/20) = 21.2 / 42.4 / 64.2 p = 0.000739 / 0.00000644 / 0.00000157
Rendim.^2 Q(5/10/20) = 43.3 / 45.4 / 61.3 p = 0.0000000329 / 0.00000186 / 0.00000444
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS se rechaza H0 en 3 de 3 rezagos: queda
autocorrelacion lineal, con ACF(1) = -0.051.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* Bitcoin (BTC) - Cripto
Precios Q(5/10/20) = 6211.4 / 12306.1 / 24124.7 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(5/10/20) = 3.9 / 8.1 / 18.2 p = 0.564 / 0.615 / 0.572
Rendim.^2 Q(5/10/20) = 16.4 / 22.7 / 29.3 p = 0.00574 / 0.012 / 0.0821
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay
autocorrelacion lineal detectable.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* SPY S&P 500 - ETF
Precios Q(5/10/20) = 6190.3 / 12234.5 / 23880.2 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(5/10/20) = 6.2 / 8.4 / 26.2 p = 0.289 / 0.593 / 0.159
Rendim.^2 Q(5/10/20) = 209.3 / 239 / 257.2 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay
autocorrelacion lineal detectable.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* IPVN total nacional - NO financiera
Precios Q(4/8/12) = 414 / 739.4 / 990.3 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(4/8/12) = 81 / 96.7 / 99.8 p = 0.000000000000000111 / <0.0000000000000001 / 0.000000000000000666
Rendim.^2 Q(4/8/12) = 37.3 / 39.8 / 45.1 p = 0.000000157 / 0.00000356 / 0.00000981
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS se rechaza H0 en 3 de 3 rezagos: queda
autocorrelacion lineal, con ACF(1) = 0.534.
Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de
volatilidad (efecto ARCH). La magnitud del movimiento si es
predecible aunque el signo no lo sea, lo que justifica GARCH.
* IPVN Cali AU - NO financiera
Precios Q(4/8/12) = 403.7 / 706.7 / 930.9 p = <0.0000000000000001 / <0.0000000000000001 / <0.0000000000000001
Rendimientos Q(4/8/12) = 3.6 / 8.2 / 15.2 p = 0.458 / 0.411 / 0.23
Rendim.^2 Q(4/8/12) = 3.1 / 6.1 / 9.4 p = 0.538 / 0.636 / 0.669
En NIVELES se rechaza H0 de forma contundente en todos los rezagos.
El precio esta fuertemente autocorrelacionado, pero esto es una
consecuencia mecanica de la raiz unitaria y no aporta informacion
economica: cualquier camino aleatorio produce el mismo resultado.
En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay
autocorrelacion lineal detectable.
Sobre el rendimiento al cuadrado NO se rechaza H0: no se detecta
agrupamiento de volatilidad en esta serie.
Diferencia entre los dos estadisticos:
- Box-Pierce: Q = n * suma(rho_k^2), valido solo asintoticamente.
- Ljung-Box : Q = n(n+2) * suma(rho_k^2/(n-k)), corrige el sesgo de
muestra finita ponderando cada rho_k. Es el que se reporta.
- En las series diarias (n > 1200) las dos versiones dan practicamente el
mismo valor. En las series trimestrales (n ~ 116) Box-Pierce queda por
debajo de Ljung-Box, y ahi la correccion si cambia el numero.
Como se relacionan los dos bloques de tests:
- ADF y Ljung-Box contestan preguntas distintas. El ADF pregunta si el
nivel vuelve a una media (estacionariedad). Ljung-Box pregunta si los
valores pasados ayudan a predecir el siguiente (autocorrelacion).
- Sobre los PRECIOS ambos apuntan a lo mismo por construccion: hay raiz
unitaria y por tanto hay autocorrelacion masiva.
- Sobre los RENDIMIENTOS los dos resultados juntos son informativos: la
serie es estacionaria (ADF rechaza) Y ademas impredecible en media
(Ljung-Box no rechaza). Esa es la definicion operativa de ruido blanco
y el contenido empirico de la eficiencia en forma debil.
- Es importante que el rendimiento sea ruido blanco en MEDIA pero no en
VARIANZA: el Ljung-Box sobre r^2 lo detecta. Ruido blanco no implica
independencia, solo ausencia de correlacion lineal.
tabla(resumen, digitos = 3)
| Serie | Clase | ACF1_precio | ACF1_rend | ADF_p_precio | ADF_p_rend | urdf_tau_precio | urdf_VC5_precio | urdf_tau_rend | urdf_VC5_rend | LB_p_precio | LB_p_rend | LB_p_rend2 | Precio_estac | Rend_estac | Rend_ruido_blanco | Efecto_ARCH |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KO | Financiera | 0.994 | -0.006 | 0.809 | 0.010 | -1.847 | -3.41 | -25.176 | -2.86 | 0 | 0.927 | 0.000 | NO | SI | SI | SI |
| BONO10 | Financiera | 0.994 | 0.001 | 0.356 | 0.010 | -2.582 | -3.41 | -26.778 | -2.86 | 0 | 0.447 | 0.000 | NO | SI | SI | SI |
| USDCOP | Financiera | 0.991 | -0.051 | 0.577 | 0.010 | -2.072 | -3.41 | -9.552 | -2.86 | 0 | 0.000 | 0.000 | NO | SI | NO | SI |
| BTC | Financiera | 0.998 | -0.010 | 0.673 | 0.010 | -1.977 | -3.41 | -19.531 | -2.86 | 0 | 0.615 | 0.012 | NO | SI | SI | SI |
| SPY | Financiera | 0.997 | -0.025 | 0.503 | 0.010 | -2.324 | -3.41 | -21.627 | -2.86 | 0 | 0.593 | 0.000 | NO | SI | SI | SI |
| IPVN_NAL | No financiera | 0.968 | 0.534 | 0.990 | 0.263 | 1.057 | -3.43 | -5.516 | -2.88 | 0 | 0.000 | 0.000 | NO | NO | NO | SI |
| IPVN_CALI | No financiera | 0.964 | -0.056 | 0.990 | 0.021 | 1.830 | -3.43 | -7.118 | -2.88 | 0 | 0.411 | 0.636 | NO | SI | SI | NO |
ACF del rendimiento: un ETF frente al IPVN nacional y al IPVN de Cali. El panel central es el que rompe el patrón financiero.
4. CONTRASTE: ACTIVOS FINANCIEROS vs PRECIOS NO FINANCIEROS
------------------------------------------------------------------------------
Serie no financiera elegida: Indice de Precios de Vivienda Nueva (IPVN)
del DANE, que mide el precio de la vivienda nueva en Colombia.
Se analizan dos versiones:
- IPVN_NAL : total nacional, 117 trimestres (1997-04 a 2026-04)
- IPVN_CALI : area urbana de Cali (Cali, Jamundi, Palmira, Yumbo), 117 trimestres
El nivel se reconstruyo encadenando las variaciones trimestrales
oficiales con base 1997-I = 100. La correlacion con el indice publicado
por el DANE para 2016 en adelante es 1, lo que valida el encadenamiento.
Nota sobre la comparacion: las series financieras son diarias (n ~ 1254) y las de
vivienda son trimestrales (n ~ 116). La diferencia de
frecuencia y de potencia estadistica se tiene en cuenta al interpretar.
SIMILITUD: el nivel de precios es NO estacionario en todos los casos
..............................................................................
- Precios NO estacionarios: 7 de 7 series (coinciden ADF y ur.df).
- Rendimientos estacionarios: 7 de 7 segun ur.df con especificacion
'drift', y 6 de 7 segun ADF. La diferencia se explica en la seccion 2.
- La ACF del precio arranca en 0.964-0.998 en todas ellas, el ADF y el ur.df no
rechazan la raiz unitaria y el Ljung-Box en niveles rechaza H0 con
contundencia. El comportamiento I(1) es comun a cualquier serie de
precios, financiera o no, y por si solo NO distingue un mercado
financiero de otro tipo de mercado. Esta es la leccion mas importante
del contraste: la no estacionariedad no es un rasgo 'financiero'.
DIFERENCIA 1: autocorrelacion del rendimiento (la diferencia de fondo)
..............................................................................
- Financieras: 4 de 5 se comportan como ruido blanco. ACF(1) entre -0.051 y 0.001.
KO ACF(1) = -0.006 LB p = 0.927 ruido blanco: SI
BONO10 ACF(1) = 0.001 LB p = 0.447 ruido blanco: SI
USDCOP ACF(1) = -0.051 LB p = 0.00000644 ruido blanco: NO
BTC ACF(1) = -0.010 LB p = 0.615 ruido blanco: SI
SPY ACF(1) = -0.025 LB p = 0.593 ruido blanco: SI
- IPVN_NAL : ACF(1) = 0.534, LB p = <0.0000000000000001 -> SI hay autocorrelacion
- IPVN_CALI: ACF(1) = -0.056, LB p = 0.411 -> no se detecta autocorrelacion
Este es el resultado central y merece leerse con cuidado, porque las
dos series de vivienda NO dicen lo mismo.
En el TOTAL NACIONAL la variacion trimestral del precio de la vivienda
esta claramente autocorrelacionada: ACF(1) = 0.534 frente a una banda
de +/- 0.182, con 5/20 rezagos significativos. La inflacion de
vivienda tiene memoria (momentum): un trimestre de alza se prolonga en
el siguiente y el pasado SI ayuda a pronosticar. Esto es exactamente
lo contrario de lo que se observa en los activos financieros.
En CALI, en cambio, no se detecta autocorrelacion (ACF(1) = -0.056).
La explicacion no es que el mercado de vivienda de Cali sea eficiente,
sino que es un problema de MEDICION. El indice de una sola ciudad se
construye con pocos proyectos por trimestre, de modo que la
composicion de la muestra cambia de un trimestre a otro y agrega
ruido. Ese ruido de muestreo es aproximadamente independiente entre
trimestres y diluye la senal de persistencia: la desviacion tipica de
la variacion trimestral en Cali es 2.79% contra 1.23% en el
agregado nacional, es decir 2.3 veces mas volatil sin que el mercado
subyacente lo sea. Al promediar todas las ciudades el ruido se cancela
y la persistencia reaparece.
Conclusion metodologica: con una sola serie de ciudad se habria
concluido, por error, que el precio de la vivienda es tan
impredecible como una accion. Comparar el agregado con la ciudad es
lo que permite separar la propiedad economica del artefacto de
medicion. Es la razon por la que se incluyeron las dos series.
Por que cabe esperar memoria en el precio de la vivienda:
- la oferta es rigida y tarda anos en responder a la demanda;
- no hay subasta continua: los precios se fijan por listas, costos y
contratos, y se ajustan despacio;
- no existe venta en corto ni arbitraje que elimine la
predictibilidad, porque el activo es indivisible, iliquido y con
costos de transaccion altos;
- el indice es un promedio construido con rezago, lo que suaviza.
En un mercado financiero liquido cualquier patron predecible seria
explotado de inmediato y desapareceria: por eso el rendimiento diario
es ruido blanco.
DIFERENCIA 2: magnitud del riesgo y forma de la distribucion
..............................................................................
- Volatilidad anualizada financieras: 8.2% a 52.7%.
- Volatilidad anualizada IPVN_NAL: 2.5% IPVN_CALI: 5.6%.
- Curtosis financieras: 4 a 10.8; IPVN_NAL 5.3; IPVN_CALI 5.
Referencia: una distribucion normal tiene curtosis 3.
- Los activos financieros presentan colas pesadas: eventos extremos
mucho mas frecuentes de lo que predice la normal. El caso extremo es
Bitcoin, con volatilidad anualizada de 52.7% y un peor dia de -25.7%.
- El indice de vivienda nacional es la serie mas tranquila del conjunto.
Parte de esa calma es real (el mercado se mueve despacio) y parte es
artificial: al ser un promedio trimestral, cualquier movimiento brusco
dentro del trimestre queda suavizado antes de entrar en la serie.
DIFERENCIA 3: agrupamiento de volatilidad (efecto ARCH)
..............................................................................
- Financieras con efecto ARCH: 5 de 5. p-valores de Ljung-Box sobre r^2: 0.00000226, <0.0000000000000001, 0.00000186, 0.012, <0.0000000000000001.
- IPVN_NAL : p = 0.00000356 -> hay efecto ARCH
- IPVN_CALI: p = 0.636 -> sin efecto ARCH
- En los activos financieros la volatilidad viene en rachas: periodos de
calma seguidos de periodos de estres. Es visible a simple vista en el
grafico 03_rendimientos.png, donde las barras se agrupan.
Aqui hay que ser cuidadoso al comparar, porque el mismo p-valor no
significa lo mismo en las dos clases de serie. Lo caracteristico de un
activo financiero es la combinacion: el rendimiento NO esta
autocorrelacionado en media y SIN EMBARGO su cuadrado SI lo esta. Es
decir, el signo del proximo movimiento es impredecible pero su magnitud
no. Esa combinacion es la que exige un modelo GARCH.
Series con esa firma: KO, BONO10, BTC, SPY.
El IPVN nacional rechaza H0 sobre r^2 (p = 0.00000356), pero NO por la misma
razon. Como su rendimiento ya esta autocorrelacionado en media
(ACF(1) = 0.534), el cuadrado hereda
mecanicamente esa correlacion: el
test sobre r^2 no puede separar la persistencia de la media de la
persistencia de la varianza. No es evidencia limpia de volatilidad
por rachas; lo correcto seria contrastar ARCH sobre los residuales de
un modelo AR ya ajustado a la media.
En los activos financieros esa ambiguedad no existe, porque la media
ya es ruido blanco y todo lo que aparece en r^2 es varianza.
En el IPVN de Cali no se detecta ningun patron en r^2 (p = 0.636),
coherente con una serie dominada por ruido de muestreo independiente.
DIFERENCIA 4: estacionalidad
..............................................................................
- IPVN_NAL : ANOVA por trimestre F = 1.47, p = 0.227 -> sin estacionalidad
- IPVN_CALI: ANOVA por trimestre F = 0.22, p = 0.88 -> sin estacionalidad
- Era razonable esperar un patron por trimestre en un mercado de
vivienda, pero los datos no lo respaldan: las medias trimestrales son
parecidas entre si. La persistencia del IPVN nacional es memoria
genuina (momentum), no un ciclo de calendario. Conviene descartarlo
explicitamente antes de proponer un modelo con componente estacional.
CONCLUSION DEL CONTRASTE
..............................................................................
1. Lo que NO distingue a un activo financiero: la raiz unitaria. Todas las
series de precios analizadas, financieras y no financieras, son I(1).
Los precios suben y bajan acumulando choques y ninguno revierte a una
media fija. Encontrar una raiz unitaria en una serie de precios no
dice nada sobre la naturaleza del mercado.
2. Lo que SI distingue: la estructura de la PRIMERA DIFERENCIA.
- Activos financieros liquidos: el rendimiento es ruido blanco en
media pero con varianza condicional autocorrelacionada y colas
pesadas. Modelo natural: camino aleatorio con drift en el nivel mas
GARCH en la varianza.
- Precio de la vivienda (agregado nacional): la variacion es
persistente, con ACF(1) = 0.534, y por tanto parcialmente
predecible. Modelo natural: ARIMA con componente AR sobre la
variacion. No hace falta termino estacional (el ANOVA por trimestre
no lo respalda) y el aparente efecto ARCH deberia reevaluarse sobre
los residuales del AR antes de anadir una ecuacion de varianza.
3. La leccion economica: lo que separa a un activo financiero de otro
precio de la economia no es la no estacionariedad, es la EFICIENCIA
INFORMATIVA. En un mercado liquido, con arbitraje y venta en corto,
cualquier patron predecible se explota y desaparece, y el rendimiento
queda impredecible en media. En el mercado de vivienda, con oferta
rigida, activos indivisibles y sin arbitraje posible, la
predictibilidad sobrevive.
4. La leccion estadistica: antes de atribuir un resultado al
comportamiento del mercado hay que descartar que venga de la medicion.
El contraste entre el IPVN nacional y el de Cali lo muestra: la misma
variable, medida con mas o menos ruido, lleva a conclusiones opuestas
sobre la predictibilidad.
==============================================================================
El análisis se ejecuta desde un único script,
analisis_series.R, que genera todas las tablas y figuras de
este informe.
###############################################################################
# ANALISIS DE SERIES DE TIEMPO - FINANZAS CUANTITATIVAS
#
# Contenido:
# 1. Serie de rendimientos compuestos (log-rendimientos continuos)
# 2. Graficos de la serie de precios y de la serie de rendimientos
# 3. Correlogramas ACF y PACF, para precios y para rendimientos
# 4. Interpretacion de los correlogramas
# 5. Test ADF (tseries::adf.test)
# 6. Test ur.df (urca::ur.df, especificaciones none/drift/trend)
# 7. Interpretacion de ambos tests de estacionariedad
# 8. Test de autocorrelacion Ljung-Box y Box-Pierce
# 9. Interpretacion de ambos tests de autocorrelacion
# 10. Contraste con series de precios NO financieras (vivienda nueva, DANE)
#
# Series financieras (Serie_Precios_5Y.xlsx, 5 anos diarios):
# Renta variable : Coca-Cola (KO)
# Renta fija : Bono del Tesoro EE.UU. 10 anos (precio teorico via ^TNX)
# Moneda : USD/COP
# Cripto : Bitcoin (BTC-USD)
# ETF : SPY (S&P 500)
#
# Series NO financieras (IPVN del DANE, trimestral 1997-2026):
# IPVN_NAL : Indice de Precios de Vivienda Nueva, total nacional
# IPVN_CALI : Indice de Precios de Vivienda Nueva, area urbana de Cali
#
# Se incluyen dos series no financieras a proposito: el total nacional y una
# sola ciudad. La comparacion entre ambas muestra que el ruido de muestreo de
# un indice de ciudad puede borrar la persistencia que si aparece en el
# agregado, y evita concluir de mas a partir de una sola serie.
#
# Requisitos: Serie_Precios_5Y.xlsx en la misma carpeta que este script.
# Salidas : carpetas graficos/ y resultados/
###############################################################################
## ---------------------------------------------------------------------------
## 0. CONFIGURACION
## ---------------------------------------------------------------------------
# Al ejecutar el script de forma normal se limpia el entorno. Cuando se llama
# desde un R Markdown con source(), no se debe limpiar ni cerrar el dispositivo
# grafico porque knitr esta usando el suyo. getOption() sobrevive a rm().
if (!isTRUE(getOption("fc.sourced"))) {
rm(list = ls())
graphics.off()
}
RUTA <- "C:/Users/samue/OneDrive/Escritorio/FINANZAS CUANTITATIVAS"
setwd(RUTA)
paquetes <- c("readxl", "tseries", "urca")
faltan <- paquetes[!sapply(paquetes, requireNamespace, quietly = TRUE)]
if (length(faltan) > 0) {
install.packages(faltan, repos = "https://cloud.r-project.org")
}
library(readxl)
library(tseries)
library(urca)
dir.create("graficos", showWarnings = FALSE)
dir.create("resultados", showWarnings = FALSE)
ALPHA <- 0.05 # nivel de significancia de todos los tests
options(scipen = 999)
linea <- function(ch = "=", n = 78) strrep(ch, n)
cat("\n", linea(), "\n",
"ANALISIS DE SERIES DE TIEMPO - ACTIVOS FINANCIEROS vs SERIE NO FINANCIERA\n",
linea(), "\n\n", sep = "")
## ---------------------------------------------------------------------------
## 1. CARGA DE LAS SERIES DE PRECIOS FINANCIEROS
## ---------------------------------------------------------------------------
archivo <- "Serie_Precios_5Y.xlsx"
if (!file.exists(archivo)) stop("No se encuentra ", archivo, " en ", RUTA)
datos <- as.data.frame(read_excel(archivo, sheet = "Precios_habiles"))
datos$Fecha <- as.Date(datos$Fecha)
# Nombre corto -> nombre de la columna en el Excel
mapa_fin <- c(
KO = "KO (USD adj.)",
BONO10 = "Bono 10Y precio teorico",
USDCOP = "USD/COP",
BTC = "Bitcoin (USD)",
SPY = "SPY (USD adj.)"
)
meta_fin <- list(
KO = list(etiqueta = "Coca-Cola (KO) - Renta variable",
unidad = "USD por accion", color = "firebrick"),
BONO10 = list(etiqueta = "Bono Tesoro EE.UU. 10Y - Renta fija",
unidad = "USD por 100 facial", color = "darkgreen"),
USDCOP = list(etiqueta = "USD/COP - Moneda",
unidad = "COP por USD", color = "goldenrod3"),
BTC = list(etiqueta = "Bitcoin (BTC) - Cripto",
unidad = "USD por BTC", color = "purple3"),
SPY = list(etiqueta = "SPY S&P 500 - ETF",
unidad = "USD por participacion", color = "steelblue4")
)
## ---------------------------------------------------------------------------
## 2. CARGA DE LAS SERIES NO FINANCIERAS (IPVN del DANE)
##
## El DANE publica la variacion trimestral del Indice de Precios de
## Vivienda Nueva. Se encadena para reconstruir un nivel de precios:
## I_t = I_{t-1} * (1 + var_t / 100), base = 100 en 1997-I
## El encadenamiento se valida mas abajo contra el indice publicado.
## ---------------------------------------------------------------------------
ARCH_IPVN <- file.path("datos_no_financieros",
"anex-IPVN-VarTrimAreaUrbaMetro-IItrim2026.xlsx")
URL_IPVN <- paste0("https://www.dane.gov.co/files/operaciones/IPVN/",
"anex-IPVN-VarTrimAreaUrbaMetro-IItrim2026.xlsx")
if (!file.exists(ARCH_IPVN)) {
dir.create("datos_no_financieros", showWarnings = FALSE)
cat("Descargando IPVN del DANE...\n")
try(download.file(URL_IPVN, ARCH_IPVN, mode = "wb", quiet = TRUE),
silent = TRUE)
}
# Lee una columna del anexo del DANE y devuelve el nivel encadenado.
cargar_ipvn <- function(columna) {
if (!file.exists(ARCH_IPVN)) return(NULL)
bruto <- suppressMessages(
read_excel(ARCH_IPVN, sheet = 1, col_names = FALSE,
.name_repair = "minimal"))
bruto <- as.data.frame(bruto)
# La fila 7 del anexo contiene los nombres de las areas urbanas.
encabezado <- trimws(as.character(unlist(bruto[7, ])))
col <- which(encabezado == columna)
if (length(col) == 0) return(NULL)
cuerpo <- bruto[8:nrow(bruto), ]
anio <- suppressWarnings(as.numeric(as.character(cuerpo[[1]])))
trim <- trimws(as.character(cuerpo[[2]]))
var <- suppressWarnings(as.numeric(as.character(cuerpo[[col]])))
# Descarta notas al pie y filas sin trimestre valido
ok <- trim %in% c("I", "II", "III", "IV") & !is.na(trim)
anio <- anio[ok]; trim <- trim[ok]; var <- var[ok]
# El anio solo aparece en la primera fila de cada bloque anual: se rellena
for (i in seq_along(anio)) if (is.na(anio[i]) && i > 1) anio[i] <- anio[i - 1]
ok <- !is.na(anio) & !is.na(var)
anio <- anio[ok]; trim <- trim[ok]; var <- var[ok]
nt <- match(trim, c("I", "II", "III", "IV"))
data.frame(
Fecha = as.Date(sprintf("%d-%02d-01", anio, nt * 3 - 2)),
Anio = anio,
Trim = factor(trim, levels = c("I", "II", "III", "IV")),
Var = var,
Precio = 100 * cumprod(1 + var / 100)
)
}
ipvn_nal <- cargar_ipvn("Total")
ipvn_cali <- cargar_ipvn("Cali AU")
if (is.null(ipvn_nal) || is.null(ipvn_cali)) {
stop("No se pudo cargar el IPVN del DANE. Verifique ", ARCH_IPVN)
}
# Validacion del encadenamiento contra el indice oficial publicado (2016+)
validar_encadenamiento <- function(niveles_encadenados, anios, columna) {
arch <- file.path("datos_no_financieros",
"anex-IPVN-IndicesAreaUrbaMetro-IItrim2026.xlsx")
if (!file.exists(arch)) return(NA_real_)
b <- try(as.data.frame(suppressMessages(
read_excel(arch, sheet = 1, col_names = FALSE,
.name_repair = "minimal"))), silent = TRUE)
if (inherits(b, "try-error")) return(NA_real_)
h <- trimws(as.character(unlist(b[7, ])))
j <- which(h == columna)
if (length(j) == 0) return(NA_real_)
lvl <- suppressWarnings(as.numeric(as.character(b[[j]][8:nrow(b)])))
lvl <- lvl[!is.na(lvl)]
ch <- niveles_encadenados[anios >= 2016]
n <- min(length(ch), length(lvl))
if (n < 8) return(NA_real_)
cor(ch[seq_len(n)], lvl[seq_len(n)])
}
cor_valid <- validar_encadenamiento(ipvn_cali$Precio, ipvn_cali$Anio, "Cali AU")
## ---------------------------------------------------------------------------
## 3. REGISTRO UNIFICADO DE SERIES Y RENDIMIENTOS COMPUESTOS
##
## Rendimiento compuesto continuo (log-rendimiento):
## r_t = ln(P_t) - ln(P_{t-1}) = ln(P_t / P_{t-1})
## Es el rendimiento de capitalizacion continua. Propiedades:
## - aditivo en el tiempo: r_{1:T} = suma de los r_t
## - simetrico: una caida y una subida de igual magnitud se cancelan
## - se acerca mas a la normalidad que el rendimiento simple
## Relacion con el rendimiento simple R_t: r_t = ln(1 + R_t)
##
## Se expresa en % multiplicando por 100.
## ---------------------------------------------------------------------------
rendimiento_compuesto <- function(p) 100 * diff(log(p))
# Constructor de una entrada del registro
nueva_serie <- function(nombre, etiqueta, unidad, clase, precio, fecha,
per, max_lag, lags_lb, color, trim = NULL) {
ok <- !is.na(precio)
precio <- precio[ok]; fecha <- fecha[ok]
if (!is.null(trim)) trim <- trim[ok]
list(
nombre = nombre, etiqueta = etiqueta, unidad = unidad, clase = clase,
per = per, max_lag = max_lag, lags_lb = lags_lb, color = color,
fecha = fecha, precio = precio,
fecha_r = fecha[-1], r = rendimiento_compuesto(precio),
trim_r = if (is.null(trim)) NULL else trim[-1]
)
}
S <- list()
# --- financieras: diarias, 252 periodos por anio ---
for (nm in names(mapa_fin)) {
m <- meta_fin[[nm]]
S[[nm]] <- nueva_serie(
nombre = nm, etiqueta = m$etiqueta, unidad = m$unidad,
clase = "Financiera",
precio = datos[[mapa_fin[nm]]], fecha = datos$Fecha,
per = 252, max_lag = 40, lags_lb = c(5, 10, 20), color = m$color
)
}
# --- no financieras: trimestrales, 4 periodos por anio ---
S[["IPVN_NAL"]] <- nueva_serie(
nombre = "IPVN_NAL", clase = "No financiera",
etiqueta = "IPVN total nacional - NO financiera",
unidad = "Indice (1997-I = 100)",
precio = ipvn_nal$Precio, fecha = ipvn_nal$Fecha, trim = ipvn_nal$Trim,
per = 4, max_lag = 24, lags_lb = c(4, 8, 12), color = "darkorange3"
)
S[["IPVN_CALI"]] <- nueva_serie(
nombre = "IPVN_CALI", clase = "No financiera",
etiqueta = "IPVN Cali AU - NO financiera",
unidad = "Indice (1997-I = 100)",
precio = ipvn_cali$Precio, fecha = ipvn_cali$Fecha, trim = ipvn_cali$Trim,
per = 4, max_lag = 24, lags_lb = c(4, 8, 12), color = "sienna"
)
NOMBRES <- names(S)
FIN <- NOMBRES[sapply(S, function(s) s$clase == "Financiera")]
NOFIN <- NOMBRES[sapply(S, function(s) s$clase == "No financiera")]
cat("--- 1-2. Series cargadas --------------------------------------------\n")
for (nm in NOMBRES) {
s <- S[[nm]]
cat(sprintf("%-10s %-14s n=%4d %s a %s\n", nm, s$clase, length(s$precio),
format(min(s$fecha)), format(max(s$fecha))))
}
cat(sprintf("\nValidacion del encadenamiento IPVN Cali contra el indice\n"))
cat(sprintf("oficial publicado (2016+): correlacion = %.4f\n\n",
cor_valid))
## ---------------------------------------------------------------------------
## 4. ESTADISTICOS DESCRIPTIVOS DE LOS RENDIMIENTOS COMPUESTOS
## ---------------------------------------------------------------------------
cat("--- 3. Rendimientos compuestos (log-rendimientos, en %) -------------\n")
tabla_desc <- do.call(rbind, lapply(NOMBRES, function(nm) {
s <- S[[nm]]; r <- s$r
data.frame(
Serie = nm,
Clase = s$clase,
n = length(r),
Media = mean(r),
DesvEst = sd(r),
Minimo = min(r),
Maximo = max(r),
Asimetria = mean((r - mean(r))^3) / sd(r)^3,
Curtosis = mean((r - mean(r))^4) / sd(r)^4,
Media_anual = mean(r) * s$per,
Volat_anual = sd(r) * sqrt(s$per),
Acum_compuesto = sum(r)
)
}))
print(format(tabla_desc, digits = 4), row.names = FALSE)
write.csv(tabla_desc, "resultados/01_estadisticos_rendimientos.csv",
row.names = FALSE)
cat("\n")
## ---------------------------------------------------------------------------
## 5. GRAFICOS DE LA SERIE DE PRECIOS Y DE LA SERIE DE RENDIMIENTOS
## ---------------------------------------------------------------------------
# 5a. Precios en niveles, un panel por serie
png("graficos/01_precios_niveles.png", width = 1500, height = 1250, res = 120)
par(mfrow = c(4, 2), mar = c(3.5, 4.4, 2.8, 1), oma = c(0, 0, 2.2, 0))
for (nm in NOMBRES) {
s <- S[[nm]]
plot(s$fecha, s$precio, type = "l", lwd = 1.4, col = s$color,
xlab = "", ylab = s$unidad, main = s$etiqueta)
grid(col = "grey88")
}
plot.new()
mtext("Series de precios en niveles", outer = TRUE, font = 2, cex = 1.1)
dev.off()
# 5b. Precios financieros indexados a 100 (comparacion en una sola escala)
png("graficos/02_precios_indexados100.png", width = 1300, height = 800, res = 120)
par(mar = c(4, 4.5, 3, 1))
idx <- function(p) 100 * p / p[1]
ymax <- max(sapply(FIN, function(nm) max(idx(S[[nm]]$precio))))
plot(S$SPY$fecha, idx(S$SPY$precio), type = "n", ylim = c(50, ymax * 1.02),
xlab = "Fecha", ylab = "Indice (primer dia = 100)",
main = "Precios financieros indexados a 100 - 5 anos")
grid(col = "grey88")
abline(h = 100, lty = 3, col = "grey40")
for (nm in FIN) lines(S[[nm]]$fecha, idx(S[[nm]]$precio),
col = S[[nm]]$color, lwd = 1.5)
legend("topleft", legend = FIN, col = sapply(FIN, function(nm) S[[nm]]$color),
lwd = 2, bty = "n", cex = 0.85)
dev.off()
# 5c. Rendimientos compuestos
png("graficos/03_rendimientos.png", width = 1500, height = 1250, res = 120)
par(mfrow = c(4, 2), mar = c(3.5, 4.4, 2.8, 1), oma = c(0, 0, 2.2, 0))
for (nm in NOMBRES) {
s <- S[[nm]]
plot(s$fecha_r, s$r, type = "h", col = "grey35",
xlab = "", ylab = "Rendimiento (%)", main = s$etiqueta)
abline(h = 0, col = "black")
abline(h = c(-2, 2) * sd(s$r), col = "red3", lty = 2)
grid(col = "grey90")
}
plot.new()
mtext("Rendimientos compuestos continuos (lineas rojas = +/- 2 sigma)",
outer = TRUE, font = 2, cex = 1.1)
dev.off()
cat("--- 4. Graficos de precios y rendimientos guardados en graficos/ ----\n\n")
## ---------------------------------------------------------------------------
## 6. CORRELOGRAMAS ACF Y PACF, PARA PRECIOS Y PARA RENDIMIENTOS
##
## Banda de no significancia bajo H0 de ruido blanco: +/- 1.96 / raiz(n).
## Es la linea azul punteada que dibuja acf() / pacf().
## ---------------------------------------------------------------------------
resumen_acf <- function(x, max_lag) {
n <- length(x)
banda <- 1.96 / sqrt(n)
a <- acf(x, lag.max = max_lag, plot = FALSE)$acf[-1]
p <- pacf(x, lag.max = max_lag, plot = FALSE)$acf
k <- min(20, max_lag)
list(n = n, banda = banda, k = k, max_lag = max_lag,
acf1 = a[1],
acf_medio = a[max(1, round(max_lag / 2))],
acf_max = a[max_lag],
pacf1 = p[1],
signif_acf = sum(abs(a[seq_len(k)]) > banda),
signif_pacf = sum(abs(p[seq_len(k)]) > banda))
}
# Clasifica la firma del correlograma a partir de los numeros, no a ojo.
firma_acf <- function(s) {
if (s$acf1 > 0.9 && s$signif_acf >= 0.9 * s$k) return("raiz_unitaria")
if (s$signif_acf <= 2) return("ruido_blanco")
"memoria_corta"
}
correlograma <- function(px, rx, titulo, max_lag, archivo) {
png(archivo, width = 1400, height = 950, res = 120)
par(mfrow = c(2, 2), mar = c(4, 4.2, 3.2, 1), oma = c(0, 0, 2.4, 0))
acf (px, lag.max = max_lag, main = "ACF - PRECIOS",
ylab = "ACF", xlab = "Rezago")
pacf(px, lag.max = max_lag, main = "PACF - PRECIOS",
ylab = "PACF", xlab = "Rezago")
acf (rx, lag.max = max_lag, main = "ACF - RENDIMIENTOS",
ylab = "ACF", xlab = "Rezago")
pacf(rx, lag.max = max_lag, main = "PACF - RENDIMIENTOS",
ylab = "PACF", xlab = "Rezago")
mtext(titulo, outer = TRUE, font = 2, cex = 1.1)
dev.off()
}
cat("--- 5-6. Correlogramas ACF y PACF ----------------------------------\n")
info_acf <- list()
for (nm in NOMBRES) {
s <- S[[nm]]
correlograma(s$precio, s$r, paste(s$etiqueta, "- correlogramas"),
s$max_lag, sprintf("graficos/04_correlograma_%s.png", nm))
info_acf[[nm]] <- list(precio = resumen_acf(s$precio, s$max_lag),
rend = resumen_acf(s$r, s$max_lag))
}
tabla_acf <- do.call(rbind, lapply(NOMBRES, function(nm) {
ip <- info_acf[[nm]]$precio; ir <- info_acf[[nm]]$rend
data.frame(
Serie = nm,
Clase = S[[nm]]$clase,
n = ip$n,
Banda_95 = ip$banda,
ACF1_precio = ip$acf1,
ACFmedio_precio = ip$acf_medio,
ACFmax_precio = ip$acf_max,
Signif_ACF_precio = sprintf("%d/%d", ip$signif_acf, ip$k),
Signif_PACF_precio = sprintf("%d/%d", ip$signif_pacf, ip$k),
Firma_precio = firma_acf(ip),
ACF1_rend = ir$acf1,
Signif_ACF_rend = sprintf("%d/%d", ir$signif_acf, ir$k),
Signif_PACF_rend = sprintf("%d/%d", ir$signif_pacf, ir$k),
Firma_rend = firma_acf(ir)
)
}))
print(format(tabla_acf, digits = 3), row.names = FALSE)
write.csv(tabla_acf, "resultados/02_resumen_correlogramas.csv", row.names = FALSE)
cat("\n")
## ---------------------------------------------------------------------------
## 7. TEST ADF (tseries::adf.test)
##
## H0: la serie tiene raiz unitaria -> NO es estacionaria
## H1: la serie es estacionaria
## Decision: p-valor < 0.05 => se rechaza H0 => estacionaria
##
## adf.test() usa siempre constante + tendencia y fija los rezagos en
## trunc((n-1)^(1/3)). El p-valor se interpola de la tabla de MacKinnon y
## esta truncado en [0.01, 0.99]. Por eso se complementa con ur.df().
## ---------------------------------------------------------------------------
adf_fila <- function(x, serie, tipo) {
t <- suppressWarnings(adf.test(x, alternative = "stationary"))
data.frame(
Serie = serie,
Tipo = tipo,
n = length(x),
Rezagos = as.numeric(t$parameter),
Estad_DF = as.numeric(t$statistic),
p_valor = as.numeric(t$p.value),
Decision = ifelse(t$p.value < ALPHA,
"Rechaza H0: ESTACIONARIA",
"No rechaza H0: NO ESTACIONARIA")
)
}
cat("--- 7. Test ADF (tseries::adf.test) ---------------------------------\n")
tabla_adf <- do.call(rbind, lapply(NOMBRES, function(nm) {
rbind(adf_fila(S[[nm]]$precio, nm, "Precio"),
adf_fila(S[[nm]]$r, nm, "Rendimiento"))
}))
print(format(tabla_adf, digits = 4), row.names = FALSE)
write.csv(tabla_adf, "resultados/03_test_adf.csv", row.names = FALSE)
cat("\n")
## ---------------------------------------------------------------------------
## 8. TEST ur.df (urca::ur.df)
##
## Mismo H0 (raiz unitaria), pero permite elegir el componente determinista:
## type = "none" : Dy_t = g*y_{t-1} + ... -> tau1
## type = "drift" : Dy_t = a + g*y_{t-1} + ... -> tau2, phi1
## type = "trend" : Dy_t = a + b*t + g*y_{t-1} + ... -> tau3, phi2, phi3
##
## El estadistico tau NO se compara con la normal ni con la t, sino con los
## valores criticos de Dickey-Fuller. Se rechaza H0 si tau < VC al 5%.
## Rezagos: maximo 12, seleccionados por AIC.
## ---------------------------------------------------------------------------
urdf_fila <- function(x, serie, tipo, especif) {
s <- summary(ur.df(x, type = especif, lags = 12, selectlags = "AIC"))
tau <- as.numeric(s@teststat[1])
cv <- s@cval[1, ]
data.frame(
Serie = serie,
Tipo = tipo,
Especif = especif,
Estad_tau = tau,
VC_1pct = as.numeric(cv["1pct"]),
VC_5pct = as.numeric(cv["5pct"]),
VC_10pct = as.numeric(cv["10pct"]),
Decision = ifelse(tau < as.numeric(cv["5pct"]),
"Rechaza H0 al 5%: ESTACIONARIA",
"No rechaza H0 al 5%: NO ESTACIONARIA")
)
}
cat("--- 8. Test ur.df (urca) --------------------------------------------\n")
tabla_urdf <- do.call(rbind, lapply(NOMBRES, function(nm) {
do.call(rbind, lapply(c("none", "drift", "trend"), function(esp) {
rbind(urdf_fila(S[[nm]]$precio, nm, "Precio", esp),
urdf_fila(S[[nm]]$r, nm, "Rendimiento", esp))
}))
}))
tabla_urdf <- tabla_urdf[order(match(tabla_urdf$Serie, NOMBRES),
tabla_urdf$Tipo), ]
print(format(tabla_urdf, digits = 4), row.names = FALSE)
write.csv(tabla_urdf, "resultados/04_test_urdf.csv", row.names = FALSE)
cat("\n")
# Salida completa de ur.df: regresion y los estadisticos tau y phi
sink("resultados/04b_urdf_salidas_completas.txt")
for (nm in NOMBRES) {
for (par in list(list("PRECIO", S[[nm]]$precio),
list("RENDIMIENTO", S[[nm]]$r))) {
for (esp in c("none", "drift", "trend")) {
cat("\n", linea(), "\n", nm, "-", par[[1]],
"- ur.df type =", esp, "\n", linea(), "\n")
print(summary(ur.df(par[[2]], type = esp, lags = 12,
selectlags = "AIC")))
}
}
}
sink()
## ---------------------------------------------------------------------------
## 9. TEST DE AUTOCORRELACION: LJUNG-BOX Y BOX-PIERCE
##
## H0: rho_1 = rho_2 = ... = rho_m = 0 -> no hay autocorrelacion
## (la serie es ruido blanco)
## H1: al menos una autocorrelacion es distinta de cero
## Decision: p-valor < 0.05 => se rechaza H0 => SI hay autocorrelacion
##
## Box-Pierce: Q = n * suma_{k=1..m} rho_k^2
## Ljung-Box : Q = n(n+2) * suma_{k=1..m} rho_k^2/(n-k) ~ Chi2(m)
## Ljung-Box corrige el sesgo de muestra finita de Box-Pierce.
## ---------------------------------------------------------------------------
lb_filas <- function(x, serie, tipo, lags) {
do.call(rbind, lapply(lags, function(m) {
lb <- Box.test(x, lag = m, type = "Ljung-Box")
bp <- Box.test(x, lag = m, type = "Box-Pierce")
data.frame(
Serie = serie,
Tipo = tipo,
Rezagos = m,
Q_LjungBox = as.numeric(lb$statistic),
p_LjungBox = as.numeric(lb$p.value),
Q_BoxPierce = as.numeric(bp$statistic),
p_BoxPierce = as.numeric(bp$p.value),
Decision_LB = ifelse(lb$p.value < ALPHA,
"Rechaza H0: HAY autocorrelacion",
"No rechaza H0: sin autocorrelacion")
)
}))
}
cat("--- 9. Test de autocorrelacion Ljung-Box y Box-Pierce ---------------\n")
tabla_lb <- do.call(rbind, lapply(NOMBRES, function(nm) {
s <- S[[nm]]
rbind(lb_filas(s$precio, nm, "Precio", s$lags_lb),
lb_filas(s$r, nm, "Rendimiento", s$lags_lb))
}))
print(format(tabla_lb, digits = 4), row.names = FALSE)
write.csv(tabla_lb, "resultados/05_test_ljungbox.csv", row.names = FALSE)
cat("\n")
# Ljung-Box sobre el rendimiento AL CUADRADO: detecta agrupamiento de
# volatilidad (efecto ARCH), un hecho estilizado de las series financieras.
cat("--- 9b. Ljung-Box sobre rendimientos AL CUADRADO (efecto ARCH) ------\n")
tabla_arch <- do.call(rbind, lapply(NOMBRES, function(nm) {
lb_filas(S[[nm]]$r^2, nm, "Rendimiento^2", S[[nm]]$lags_lb)
}))
print(format(tabla_arch, digits = 4), row.names = FALSE)
write.csv(tabla_arch, "resultados/06_ljungbox_rend2_arch.csv", row.names = FALSE)
cat("\n")
# Rezago de referencia para los resumenes: 10 en diarias, 8 en trimestrales
lag_ref <- function(nm) if (S[[nm]]$clase == "Financiera") 10 else 8
toma_lb <- function(tabla, nm, tipo) {
f <- tabla[tabla$Serie == nm & tabla$Rezagos == lag_ref(nm), ]
if (!is.null(tipo)) f <- f[f$Tipo == tipo, ]
f[1, ]
}
## ---------------------------------------------------------------------------
## 9c. ESTACIONALIDAD DE LAS SERIES TRIMESTRALES
## Un indice de vivienda podria tener patron por trimestre. Se contrasta
## con un ANOVA de la variacion trimestral contra el factor trimestre.
## H0: la media del rendimiento es igual en los cuatro trimestres.
## ---------------------------------------------------------------------------
cat("--- 9c. Estacionalidad trimestral (ANOVA) ---------------------------\n")
tabla_estac <- do.call(rbind, lapply(NOFIN, function(nm) {
s <- S[[nm]]
m <- anova(lm(s$r ~ s$trim_r))
medias <- tapply(s$r, s$trim_r, mean)
data.frame(
Serie = nm,
Media_I = medias[["I"]], Media_II = medias[["II"]],
Media_III = medias[["III"]], Media_IV = medias[["IV"]],
F_valor = m$`F value`[1],
p_valor = m$`Pr(>F)`[1],
Decision = ifelse(m$`Pr(>F)`[1] < ALPHA,
"Rechaza H0: HAY estacionalidad",
"No rechaza H0: sin estacionalidad")
)
}))
print(format(tabla_estac, digits = 4), row.names = FALSE)
write.csv(tabla_estac, "resultados/07_estacionalidad_trimestral.csv",
row.names = FALSE)
cat("\n")
## ---------------------------------------------------------------------------
## 10. GRAFICO DE CONTRASTE: ACF DE LOS RENDIMIENTOS
## Compara de forma directa financieras vs no financieras.
## ---------------------------------------------------------------------------
png("graficos/05_contraste_acf_rendimientos.png",
width = 1500, height = 700, res = 120)
par(mfrow = c(1, 3), mar = c(4.2, 4.4, 3.4, 1), oma = c(0, 0, 2.4, 0))
for (nm in c("SPY", "IPVN_NAL", "IPVN_CALI")) {
s <- S[[nm]]
acf(s$r, lag.max = if (s$clase == "Financiera") 20 else 12,
main = s$nombre, ylab = "ACF del rendimiento", xlab = "Rezago",
ylim = c(-0.3, 1))
}
mtext("Contraste de la ACF del rendimiento: financiera vs no financiera",
outer = TRUE, font = 2, cex = 1.05)
dev.off()
## ---------------------------------------------------------------------------
## 11. TABLA RESUMEN COMPARATIVA
## ---------------------------------------------------------------------------
resumen <- do.call(rbind, lapply(NOMBRES, function(nm) {
ap <- tabla_adf[tabla_adf$Serie == nm & tabla_adf$Tipo == "Precio", ]
ar <- tabla_adf[tabla_adf$Serie == nm & tabla_adf$Tipo == "Rendimiento", ]
# Especificacion adecuada: 'trend' para el precio (tiene tendencia),
# 'drift' para el rendimiento (media distinta de cero).
up <- tabla_urdf[tabla_urdf$Serie == nm & tabla_urdf$Tipo == "Precio" &
tabla_urdf$Especif == "trend", ]
ur <- tabla_urdf[tabla_urdf$Serie == nm & tabla_urdf$Tipo == "Rendimiento" &
tabla_urdf$Especif == "drift", ]
lp <- toma_lb(tabla_lb, nm, "Precio")
lr <- toma_lb(tabla_lb, nm, "Rendimiento")
la <- toma_lb(tabla_arch, nm, NULL)
data.frame(
Serie = nm,
Clase = S[[nm]]$clase,
ACF1_precio = info_acf[[nm]]$precio$acf1,
ACF1_rend = info_acf[[nm]]$rend$acf1,
ADF_p_precio = ap$p_valor,
ADF_p_rend = ar$p_valor,
urdf_tau_precio = up$Estad_tau,
urdf_VC5_precio = up$VC_5pct,
urdf_tau_rend = ur$Estad_tau,
urdf_VC5_rend = ur$VC_5pct,
LB_p_precio = lp$p_LjungBox,
LB_p_rend = lr$p_LjungBox,
LB_p_rend2 = la$p_LjungBox,
Precio_estac = ifelse(ap$p_valor < ALPHA, "SI", "NO"),
Rend_estac = ifelse(ar$p_valor < ALPHA, "SI", "NO"),
Rend_ruido_blanco = ifelse(lr$p_LjungBox >= ALPHA, "SI", "NO"),
Efecto_ARCH = ifelse(la$p_LjungBox < ALPHA, "SI", "NO")
)
}))
cat("--- 10. RESUMEN COMPARATIVO -----------------------------------------\n")
print(format(resumen, digits = 3), row.names = FALSE)
write.csv(resumen, "resultados/08_resumen_comparativo.csv", row.names = FALSE)
cat("\n")
## ---------------------------------------------------------------------------
## 12. INTERPRETACIONES
##
## Todo el texto de esta seccion se construye A PARTIR DE LOS RESULTADOS
## calculados arriba. No hay conclusiones escritas a mano que puedan
## contradecir los numeros si los datos se actualizan.
## ---------------------------------------------------------------------------
txt <- character(0)
add <- function(...) txt <<- c(txt, paste0(...))
pv <- function(x) format.pval(x, digits = 3, eps = 1e-16)
sn <- function(cond, si, no) ifelse(cond, si, no)
add(linea())
add("INTERPRETACION DE RESULTADOS")
add("Generado: ", format(Sys.time(), "%Y-%m-%d %H:%M"))
add(linea())
## --- 12.1 Correlogramas ----------------------------------------------------
add("")
add("1. INTERPRETACION DE LOS CORRELOGRAMAS (ACF y PACF)")
add(linea("-"))
add("Regla de lectura: una autocorrelacion es significativa si su barra supera")
add("la banda de +/- 1.96/raiz(n), la linea azul punteada del grafico.")
add("")
for (nm in NOMBRES) {
s <- S[[nm]]; ip <- info_acf[[nm]]$precio; ir <- info_acf[[nm]]$rend
add("* ", s$etiqueta, " (banda 95% = +/-", round(ip$banda, 3), ")")
add(" PRECIOS: ACF(1) = ", round(ip$acf1, 3),
", ACF(", round(ip$max_lag / 2), ") = ", round(ip$acf_medio, 3),
", ACF(", ip$max_lag, ") = ", round(ip$acf_max, 3), ".")
add(" Rezagos significativos: ACF ", ip$signif_acf, "/", ip$k,
", PACF ", ip$signif_pacf, "/", ip$k, ".")
if (firma_acf(ip) == "raiz_unitaria") {
add(" La ACF arranca practicamente en 1 y decae muy despacio, casi de")
add(" forma lineal, sin volver dentro de la banda en ningun rezago. La")
add(" PACF en cambio se corta tras el primer rezago (PACF(1) = ",
round(ip$pacf1, 3), ").")
add(" Esa combinacion es la firma de un proceso NO estacionario con raiz")
add(" unitaria, compatible con un camino aleatorio P_t = P_{t-1} + e_t:")
add(" el nivel de hoy es el mejor pronostico del nivel de manana y los")
add(" choques no se olvidan.")
} else {
add(" La ACF vuelve dentro de la banda en pocos rezagos: memoria corta.")
}
add(" RENDIMIENTOS: ACF(1) = ", round(ir$acf1, 3),
", rezagos significativos ACF ", ir$signif_acf, "/", ir$k,
", PACF ", ir$signif_pacf, "/", ir$k, ".")
if (firma_acf(ir) == "ruido_blanco") {
add(" Casi todas las barras caen dentro de la banda: el rendimiento se")
add(" comporta como ruido blanco. Al tomar la primera diferencia del")
add(" logaritmo desaparecio toda la persistencia, es decir, el precio era")
add(" integrado de orden 1, I(1), y el rendimiento es I(0).")
} else if (firma_acf(ir) == "memoria_corta") {
lb_ref <- toma_lb(tabla_lb, nm, "Rendimiento")
add(" Quedan ", ir$signif_acf, " rezagos fuera de la banda: la diferencia")
add(" elimino la raiz unitaria pero podria sobrevivir algo de estructura")
esp_falsos <- round(0.05 * ir$k, 1)
add(" lineal. Hay que ser prudente al leer barras aisladas: al contrastar")
add(" ", ir$k, " rezagos al 5% ",
sn(esp_falsos == 1, "se espera 1 exceso",
paste0("se esperan ", esp_falsos, " excesos")),
" por puro azar.")
if (lb_ref$p_LjungBox >= ALPHA) {
add(" El test conjunto de Ljung-Box con ", lb_ref$Rezagos,
" rezagos da p = ", pv(lb_ref$p_LjungBox), " y NO")
add(" rechaza la hipotesis de ruido blanco. Es decir, las barras que")
add(" sobresalen no forman un patron: se tratan como falsos positivos y")
add(" la conclusion practica es la misma que en el caso anterior, el")
add(" rendimiento es impredecible en media.")
} else {
add(" El test conjunto de Ljung-Box con ", lb_ref$Rezagos,
" rezagos da p = ", pv(lb_ref$p_LjungBox), " y SI")
add(" rechaza el ruido blanco, de modo que la estructura es real y un")
add(" modelo ARMA de orden bajo podria recogerla.")
}
} else {
add(" El rendimiento conserva una ACF muy persistente, lo que indicaria")
add(" que una sola diferencia no basta para inducir estacionariedad.")
}
add("")
}
add("Sintesis de los correlogramas:")
add(" - En las ", length(NOMBRES), " series el correlograma del PRECIO tiene la")
add(" misma forma: ACF cercana a 1 que decae con lentitud y PACF que se corta")
add(" en el primer rezago. Ninguna serie de precios es estacionaria.")
add(" - El correlograma del RENDIMIENTO es radicalmente distinto: las barras")
add(" colapsan dentro de la banda. Diferenciar el logaritmo es lo que")
add(" convierte una serie no estacionaria en una utilizable para modelar.")
add(" - El correlograma es un diagnostico visual: sugiere la raiz unitaria pero")
add(" no la contrasta formalmente. Para eso estan el ADF y el ur.df.")
## --- 12.2 Estacionariedad --------------------------------------------------
add("")
add("")
add("2. INTERPRETACION DE LOS TESTS DE ESTACIONARIEDAD (ADF y ur.df)")
add(linea("-"))
add("ADF (tseries::adf.test): H0 = la serie tiene raiz unitaria (NO")
add(" estacionaria). Se rechaza H0 si el p-valor < ", ALPHA, ".")
add("ur.df (urca::ur.df): mismo H0, pero el estadistico tau se compara con los")
add(" valores criticos de Dickey-Fuller, no con la normal ni con la t. Se")
add(" rechaza H0 si tau < valor critico al 5% (la region critica esta en la")
add(" cola izquierda). Permite elegir la parte determinista del modelo.")
add("")
for (nm in NOMBRES) {
s <- S[[nm]]
ap <- tabla_adf[tabla_adf$Serie == nm & tabla_adf$Tipo == "Precio", ]
ar <- tabla_adf[tabla_adf$Serie == nm & tabla_adf$Tipo == "Rendimiento", ]
up <- tabla_urdf[tabla_urdf$Serie == nm & tabla_urdf$Tipo == "Precio", ]
ur <- tabla_urdf[tabla_urdf$Serie == nm & tabla_urdf$Tipo == "Rendimiento", ]
add("* ", s$etiqueta)
add(" ADF precio : DF = ", round(ap$Estad_DF, 3),
", ", ap$Rezagos, " rezagos, p = ", pv(ap$p_valor), " -> ",
sn(ap$p_valor < ALPHA, "ESTACIONARIA", "NO estacionaria"))
add(" ADF rendimiento : DF = ", round(ar$Estad_DF, 3),
", ", ar$Rezagos, " rezagos, p = ", pv(ar$p_valor), " -> ",
sn(ar$p_valor < ALPHA, "ESTACIONARIA", "NO estacionaria"))
for (i in seq_len(nrow(up))) {
add(" ur.df precio (", format(up$Especif[i], width = 5), ") : tau = ",
formatC(up$Estad_tau[i], format = "f", digits = 3, width = 8),
" VC5% = ", formatC(up$VC_5pct[i], format = "f", digits = 2),
" -> ", sn(up$Estad_tau[i] < up$VC_5pct[i],
"ESTACIONARIA", "NO estacionaria"))
}
for (i in seq_len(nrow(ur))) {
add(" ur.df rendim. (", format(ur$Especif[i], width = 5), ") : tau = ",
formatC(ur$Estad_tau[i], format = "f", digits = 3, width = 8),
" VC5% = ", formatC(ur$VC_5pct[i], format = "f", digits = 2),
" -> ", sn(ur$Estad_tau[i] < ur$VC_5pct[i],
"ESTACIONARIA", "NO estacionaria"))
}
# Veredicto del ur.df en la especificacion adecuada a cada serie:
# 'trend' para el precio (tiene tendencia), 'drift' para el rendimiento
# (media distinta de cero).
up_ok <- up[up$Especif == "trend", ]
ur_ok <- ur[ur$Especif == "drift", ]
est_p_adf <- ap$p_valor < ALPHA
est_r_adf <- ar$p_valor < ALPHA
est_p_urd <- up_ok$Estad_tau < up_ok$VC_5pct
est_r_urd <- ur_ok$Estad_tau < ur_ok$VC_5pct
add(" Conclusion: el precio es ",
sn(est_p_adf && est_p_urd, "estacionario en niveles, I(0)",
"NO estacionario, integrado de orden 1, I(1)"),
" y el rendimiento")
if (est_r_adf == est_r_urd) {
add(" compuesto es ",
sn(est_r_adf, "estacionario, I(0). Los dos tests coinciden.",
"NO estacionario. Los dos tests coinciden."))
} else {
add(" compuesto es estacionario segun ur.df (",
round(ur_ok$Estad_tau, 2), " < ", ur_ok$VC_5pct,
") pero NO segun ADF")
add(" (p = ", pv(ar$p_valor),
"). Los tests DISCREPAN: ver la nota al final de esta seccion.")
}
add("")
}
# Coincidencia entre los dos tests usando la especificacion adecuada
coinc <- sum(mapply(function(a, t, v) (a < ALPHA) == (t < v),
resumen$ADF_p_precio, resumen$urdf_tau_precio,
resumen$urdf_VC5_precio)) +
sum(mapply(function(a, t, v) (a < ALPHA) == (t < v),
resumen$ADF_p_rend, resumen$urdf_tau_rend,
resumen$urdf_VC5_rend))
# Identifica explicitamente donde discrepan los dos tests
discrep <- character(0)
for (i in seq_len(nrow(resumen))) {
if ((resumen$ADF_p_precio[i] < ALPHA) !=
(resumen$urdf_tau_precio[i] < resumen$urdf_VC5_precio[i])) {
discrep <- c(discrep, paste0(resumen$Serie[i], " (precio)"))
}
if ((resumen$ADF_p_rend[i] < ALPHA) !=
(resumen$urdf_tau_rend[i] < resumen$urdf_VC5_rend[i])) {
discrep <- c(discrep, paste0(resumen$Serie[i], " (rendimiento)"))
}
}
add("Lectura conjunta de ambos tests:")
add(" - Coincidencia: ", coinc, " de ", 2 * nrow(resumen),
" diagnosticos son iguales entre ADF y ur.df")
add(" (usando 'trend' para el precio y 'drift' para el rendimiento). Que dos")
add(" tests con implementaciones distintas lleguen al mismo sitio da robustez.")
if (length(discrep) == 0) {
add(" - No hay ninguna discrepancia entre los dos tests en esta muestra.")
} else {
add(" - DISCREPANCIA en: ", paste(discrep, collapse = ", "), ".")
add(" Vale la pena detenerse aqui, porque es el caso que muestra por que no")
add(" basta con un solo test.")
for (nm in NOMBRES) {
for (tp in c("Precio", "Rendimiento")) {
i <- which(resumen$Serie == nm)
es_disc <- if (tp == "Precio") {
(resumen$ADF_p_precio[i] < ALPHA) !=
(resumen$urdf_tau_precio[i] < resumen$urdf_VC5_precio[i])
} else {
(resumen$ADF_p_rend[i] < ALPHA) !=
(resumen$urdf_tau_rend[i] < resumen$urdf_VC5_rend[i])
}
if (!es_disc) next
a <- tabla_adf[tabla_adf$Serie == nm & tabla_adf$Tipo == tp, ]
u <- tabla_urdf[tabla_urdf$Serie == nm & tabla_urdf$Tipo == tp &
tabla_urdf$Especif ==
ifelse(tp == "Precio", "trend", "drift"), ]
add(" * ", nm, " - ", tp, ": el ADF no concluye lo mismo que el ur.df.")
add(" ADF : DF = ", round(a$Estad_DF, 3), ", p = ", pv(a$p_valor),
" -> ", sn(a$p_valor < ALPHA, "estacionaria", "NO estacionaria"),
" (n = ", a$n, ", ", a$Rezagos, " rezagos fijos)")
add(" ur.df : tau = ", round(u$Estad_tau, 3), " vs VC5% = ",
u$VC_5pct, " -> ",
sn(u$Estad_tau < u$VC_5pct, "estacionaria", "NO estacionaria"),
" (especificacion ", u$Especif, ")")
add(" Por que difieren. Son dos regresiones distintas sobre los mismos")
add(" datos. adf.test() incluye SIEMPRE tendencia y fija los rezagos en")
add(" trunc((n-1)^(1/3)) = ", a$Rezagos, "; ur.df() aqui usa ",
u$Especif, " y elige los")
add(" rezagos por AIC. Meter una tendencia que la serie no necesita, y")
add(" arrastrar rezagos innecesarios, consume grados de libertad y")
add(" resta POTENCIA al test. Con una muestra corta (n = ", a$n,
") esa")
add(" perdida de potencia basta para no rechazar una H0 que es falsa.")
add(" En este caso se debe preferir el ur.df bien especificado: la")
add(" serie es estacionaria y el ADF esta cometiendo error de tipo II.")
add(" Es tambien la razon por la que el enunciado pide los dos tests.")
}
}
}
add(" - Que aporta cada uno. adf.test() es una funcion de conveniencia: impone")
add(" constante y tendencia, fija los rezagos en trunc((n-1)^(1/3)) y entrega")
add(" un p-valor interpolado y truncado en [0.01, 0.99]. Por eso muchos")
add(" rendimientos muestran exactamente p = 0.01: es el piso de la tabla, no")
add(" el p-valor verdadero, que es mucho menor.")
add(" - ur.df() obliga a decidir la especificacion y muestra la regresion")
add(" completa, los rezagos elegidos por AIC y los estadisticos conjuntos")
add(" phi1, phi2 y phi3. Es el que se debe reportar en un trabajo formal.")
add(" - La especificacion importa y se ve en los resultados. Sobre un precio")
add(" con tendencia, 'none' omite la constante y la tendencia y sesga el test")
add(" hacia el no rechazo. Sobre un rendimiento con media distinta de cero,")
add(" 'none' tambien esta mal especificado: por eso conviene 'drift'.")
add(" - Implicacion practica: no se debe modelar ni correr regresiones con los")
add(" precios en niveles, porque llevaria a regresiones espurias (Granger y")
add(" Newbold). Se trabaja con la primera diferencia del logaritmo, es decir,")
add(" con el rendimiento compuesto.")
## --- 12.3 Autocorrelacion --------------------------------------------------
add("")
add("")
add("3. INTERPRETACION DE LOS TESTS DE AUTOCORRELACION (Ljung-Box y Box-Pierce)")
add(linea("-"))
add("H0: rho_1 = ... = rho_m = 0, las primeras m autocorrelaciones son todas")
add(" cero, es decir, la serie es ruido blanco.")
add("Se rechaza H0 si p-valor < ", ALPHA, ": SI hay autocorrelacion.")
add("")
for (nm in NOMBRES) {
s <- S[[nm]]
lp <- tabla_lb[tabla_lb$Serie == nm & tabla_lb$Tipo == "Precio", ]
lr <- tabla_lb[tabla_lb$Serie == nm & tabla_lb$Tipo == "Rendimiento", ]
la <- tabla_arch[tabla_arch$Serie == nm, ]
add("* ", s$etiqueta)
add(" Precios Q(", paste(lp$Rezagos, collapse = "/"), ") = ",
paste(round(lp$Q_LjungBox, 1), collapse = " / "),
" p = ", paste(sapply(lp$p_LjungBox, pv), collapse = " / "))
add(" Rendimientos Q(", paste(lr$Rezagos, collapse = "/"), ") = ",
paste(round(lr$Q_LjungBox, 1), collapse = " / "),
" p = ", paste(sapply(lr$p_LjungBox, pv), collapse = " / "))
add(" Rendim.^2 Q(", paste(la$Rezagos, collapse = "/"), ") = ",
paste(round(la$Q_LjungBox, 1), collapse = " / "),
" p = ", paste(sapply(la$p_LjungBox, pv), collapse = " / "))
if (all(lp$p_LjungBox < ALPHA)) {
add(" En NIVELES se rechaza H0 de forma contundente en todos los rezagos.")
add(" El precio esta fuertemente autocorrelacionado, pero esto es una")
add(" consecuencia mecanica de la raiz unitaria y no aporta informacion")
add(" economica: cualquier camino aleatorio produce el mismo resultado.")
}
if (all(lr$p_LjungBox >= ALPHA)) {
add(" En RENDIMIENTOS no se rechaza H0 en ningun rezago: no hay")
add(" autocorrelacion lineal detectable.")
} else if (any(lr$p_LjungBox < ALPHA)) {
add(" En RENDIMIENTOS se rechaza H0 en ",
sum(lr$p_LjungBox < ALPHA), " de ", nrow(lr), " rezagos: queda")
add(" autocorrelacion lineal, con ACF(1) = ",
round(info_acf[[nm]]$rend$acf1, 3), ".")
}
if (any(la$p_LjungBox < ALPHA)) {
add(" Sobre el rendimiento AL CUADRADO se rechaza H0: hay agrupamiento de")
add(" volatilidad (efecto ARCH). La magnitud del movimiento si es")
add(" predecible aunque el signo no lo sea, lo que justifica GARCH.")
} else {
add(" Sobre el rendimiento al cuadrado NO se rechaza H0: no se detecta")
add(" agrupamiento de volatilidad en esta serie.")
}
add("")
}
add("Diferencia entre los dos estadisticos:")
add(" - Box-Pierce: Q = n * suma(rho_k^2), valido solo asintoticamente.")
add(" - Ljung-Box : Q = n(n+2) * suma(rho_k^2/(n-k)), corrige el sesgo de")
add(" muestra finita ponderando cada rho_k. Es el que se reporta.")
add(" - En las series diarias (n > 1200) las dos versiones dan practicamente el")
add(" mismo valor. En las series trimestrales (n ~ 116) Box-Pierce queda por")
add(" debajo de Ljung-Box, y ahi la correccion si cambia el numero.")
add("")
add("Como se relacionan los dos bloques de tests:")
add(" - ADF y Ljung-Box contestan preguntas distintas. El ADF pregunta si el")
add(" nivel vuelve a una media (estacionariedad). Ljung-Box pregunta si los")
add(" valores pasados ayudan a predecir el siguiente (autocorrelacion).")
add(" - Sobre los PRECIOS ambos apuntan a lo mismo por construccion: hay raiz")
add(" unitaria y por tanto hay autocorrelacion masiva.")
add(" - Sobre los RENDIMIENTOS los dos resultados juntos son informativos: la")
add(" serie es estacionaria (ADF rechaza) Y ademas impredecible en media")
add(" (Ljung-Box no rechaza). Esa es la definicion operativa de ruido blanco")
add(" y el contenido empirico de la eficiencia en forma debil.")
add(" - Es importante que el rendimiento sea ruido blanco en MEDIA pero no en")
add(" VARIANZA: el Ljung-Box sobre r^2 lo detecta. Ruido blanco no implica")
add(" independencia, solo ausencia de correlacion lineal.")
## --- 12.4 Contraste financiera vs no financiera ----------------------------
add("")
add("")
add("4. CONTRASTE: ACTIVOS FINANCIEROS vs PRECIOS NO FINANCIEROS")
add(linea("-"))
s_nal <- S[["IPVN_NAL"]]; s_cal <- S[["IPVN_CALI"]]
r_fin <- resumen[resumen$Clase == "Financiera", ]
r_nal <- resumen[resumen$Serie == "IPVN_NAL", ]
r_cal <- resumen[resumen$Serie == "IPVN_CALI", ]
d_fin <- tabla_desc[tabla_desc$Clase == "Financiera", ]
d_nal <- tabla_desc[tabla_desc$Serie == "IPVN_NAL", ]
d_cal <- tabla_desc[tabla_desc$Serie == "IPVN_CALI", ]
e_nal <- tabla_estac[tabla_estac$Serie == "IPVN_NAL", ]
e_cal <- tabla_estac[tabla_estac$Serie == "IPVN_CALI", ]
add("Serie no financiera elegida: Indice de Precios de Vivienda Nueva (IPVN)")
add("del DANE, que mide el precio de la vivienda nueva en Colombia.")
add("Se analizan dos versiones:")
add(" - IPVN_NAL : total nacional, ", length(s_nal$precio), " trimestres (",
format(min(s_nal$fecha), "%Y-%m"), " a ",
format(max(s_nal$fecha), "%Y-%m"), ")")
add(" - IPVN_CALI : area urbana de Cali (Cali, Jamundi, Palmira, Yumbo), ",
length(s_cal$precio), " trimestres")
add("El nivel se reconstruyo encadenando las variaciones trimestrales")
add("oficiales con base 1997-I = 100. La correlacion con el indice publicado")
add("por el DANE para 2016 en adelante es ", round(cor_valid, 4),
", lo que valida el encadenamiento.")
add("")
add("Nota sobre la comparacion: las series financieras son diarias (n ~ ",
round(mean(sapply(FIN, function(nm) length(S[[nm]]$precio)))),
") y las de")
add("vivienda son trimestrales (n ~ ", length(s_nal$r), "). La diferencia de")
add("frecuencia y de potencia estadistica se tiene en cuenta al interpretar.")
add("")
add("SIMILITUD: el nivel de precios es NO estacionario en todos los casos")
add(linea("."))
add(" - Precios NO estacionarios: ", sum(resumen$Precio_estac == "NO"),
" de ", nrow(resumen), " series (coinciden ADF y ur.df).")
add(" - Rendimientos estacionarios: ",
sum(resumen$urdf_tau_rend < resumen$urdf_VC5_rend), " de ", nrow(resumen),
" segun ur.df con especificacion")
add(" 'drift', y ", sum(resumen$Rend_estac == "SI"), " de ", nrow(resumen),
" segun ADF. La diferencia se explica en la seccion 2.")
add(" - La ACF del precio arranca en ",
round(min(resumen$ACF1_precio), 3), "-", round(max(resumen$ACF1_precio), 3),
" en todas ellas, el ADF y el ur.df no")
add(" rechazan la raiz unitaria y el Ljung-Box en niveles rechaza H0 con")
add(" contundencia. El comportamiento I(1) es comun a cualquier serie de")
add(" precios, financiera o no, y por si solo NO distingue un mercado")
add(" financiero de otro tipo de mercado. Esta es la leccion mas importante")
add(" del contraste: la no estacionariedad no es un rasgo 'financiero'.")
add("")
add("DIFERENCIA 1: autocorrelacion del rendimiento (la diferencia de fondo)")
add(linea("."))
add(" - Financieras: ", sum(r_fin$Rend_ruido_blanco == "SI"), " de ",
nrow(r_fin), " se comportan como ruido blanco. ACF(1) entre ",
round(min(r_fin$ACF1_rend), 3), " y ", round(max(r_fin$ACF1_rend), 3), ".")
for (i in seq_len(nrow(r_fin))) {
add(" ", format(r_fin$Serie[i], width = 9), " ACF(1) = ",
formatC(r_fin$ACF1_rend[i], format = "f", digits = 3, width = 7),
" LB p = ", pv(r_fin$LB_p_rend[i]), " ruido blanco: ",
r_fin$Rend_ruido_blanco[i])
}
add(" - IPVN_NAL : ACF(1) = ", round(r_nal$ACF1_rend, 3),
", LB p = ", pv(r_nal$LB_p_rend), " -> ",
sn(r_nal$LB_p_rend < ALPHA, "SI hay autocorrelacion",
"no se detecta autocorrelacion"))
add(" - IPVN_CALI: ACF(1) = ", round(r_cal$ACF1_rend, 3),
", LB p = ", pv(r_cal$LB_p_rend), " -> ",
sn(r_cal$LB_p_rend < ALPHA, "SI hay autocorrelacion",
"no se detecta autocorrelacion"))
add("")
if (r_nal$LB_p_rend < ALPHA && r_cal$LB_p_rend >= ALPHA) {
add(" Este es el resultado central y merece leerse con cuidado, porque las")
add(" dos series de vivienda NO dicen lo mismo.")
add("")
add(" En el TOTAL NACIONAL la variacion trimestral del precio de la vivienda")
add(" esta claramente autocorrelacionada: ACF(1) = ",
round(r_nal$ACF1_rend, 3), " frente a una banda")
add(" de +/- ", round(info_acf$IPVN_NAL$rend$banda, 3),
", con ", info_acf$IPVN_NAL$rend$signif_acf, "/",
info_acf$IPVN_NAL$rend$k, " rezagos significativos. La inflacion de")
add(" vivienda tiene memoria (momentum): un trimestre de alza se prolonga en")
add(" el siguiente y el pasado SI ayuda a pronosticar. Esto es exactamente")
add(" lo contrario de lo que se observa en los activos financieros.")
add("")
add(" En CALI, en cambio, no se detecta autocorrelacion (ACF(1) = ",
round(r_cal$ACF1_rend, 3), ").")
add(" La explicacion no es que el mercado de vivienda de Cali sea eficiente,")
add(" sino que es un problema de MEDICION. El indice de una sola ciudad se")
add(" construye con pocos proyectos por trimestre, de modo que la")
add(" composicion de la muestra cambia de un trimestre a otro y agrega")
add(" ruido. Ese ruido de muestreo es aproximadamente independiente entre")
add(" trimestres y diluye la senal de persistencia: la desviacion tipica de")
add(" la variacion trimestral en Cali es ", round(d_cal$DesvEst, 2),
"% contra ", round(d_nal$DesvEst, 2), "% en el")
add(" agregado nacional, es decir ",
round(d_cal$DesvEst / d_nal$DesvEst, 1),
" veces mas volatil sin que el mercado")
add(" subyacente lo sea. Al promediar todas las ciudades el ruido se cancela")
add(" y la persistencia reaparece.")
add("")
add(" Conclusion metodologica: con una sola serie de ciudad se habria")
add(" concluido, por error, que el precio de la vivienda es tan")
add(" impredecible como una accion. Comparar el agregado con la ciudad es")
add(" lo que permite separar la propiedad economica del artefacto de")
add(" medicion. Es la razon por la que se incluyeron las dos series.")
} else if (r_nal$LB_p_rend < ALPHA && r_cal$LB_p_rend < ALPHA) {
add(" Las dos series de vivienda muestran autocorrelacion en la variacion")
add(" trimestral: el precio de la vivienda tiene memoria y es parcialmente")
add(" predecible, a diferencia de los activos financieros.")
} else {
add(" En esta muestra ninguna de las dos series de vivienda muestra")
add(" autocorrelacion significativa en la variacion trimestral, de modo que")
add(" el contraste con los activos financieros se apoya sobre todo en la")
add(" volatilidad, las colas y el efecto ARCH.")
}
add("")
add(" Por que cabe esperar memoria en el precio de la vivienda:")
add(" - la oferta es rigida y tarda anos en responder a la demanda;")
add(" - no hay subasta continua: los precios se fijan por listas, costos y")
add(" contratos, y se ajustan despacio;")
add(" - no existe venta en corto ni arbitraje que elimine la")
add(" predictibilidad, porque el activo es indivisible, iliquido y con")
add(" costos de transaccion altos;")
add(" - el indice es un promedio construido con rezago, lo que suaviza.")
add(" En un mercado financiero liquido cualquier patron predecible seria")
add(" explotado de inmediato y desapareceria: por eso el rendimiento diario")
add(" es ruido blanco.")
add("")
add("DIFERENCIA 2: magnitud del riesgo y forma de la distribucion")
add(linea("."))
add(" - Volatilidad anualizada financieras: ",
round(min(d_fin$Volat_anual), 1), "% a ",
round(max(d_fin$Volat_anual), 1), "%.")
add(" - Volatilidad anualizada IPVN_NAL: ", round(d_nal$Volat_anual, 1),
"% IPVN_CALI: ", round(d_cal$Volat_anual, 1), "%.")
add(" - Curtosis financieras: ", round(min(d_fin$Curtosis), 1), " a ",
round(max(d_fin$Curtosis), 1),
"; IPVN_NAL ", round(d_nal$Curtosis, 1),
"; IPVN_CALI ", round(d_cal$Curtosis, 1), ".")
add(" Referencia: una distribucion normal tiene curtosis 3.")
add(" - Los activos financieros presentan colas pesadas: eventos extremos")
add(" mucho mas frecuentes de lo que predice la normal. El caso extremo es")
add(" Bitcoin, con volatilidad anualizada de ",
round(d_fin$Volat_anual[d_fin$Serie == "BTC"], 1),
"% y un peor dia de ",
round(d_fin$Minimo[d_fin$Serie == "BTC"], 1), "%.")
add(" - El indice de vivienda nacional es la serie mas tranquila del conjunto.")
add(" Parte de esa calma es real (el mercado se mueve despacio) y parte es")
add(" artificial: al ser un promedio trimestral, cualquier movimiento brusco")
add(" dentro del trimestre queda suavizado antes de entrar en la serie.")
add("")
add("DIFERENCIA 3: agrupamiento de volatilidad (efecto ARCH)")
add(linea("."))
add(" - Financieras con efecto ARCH: ", sum(r_fin$Efecto_ARCH == "SI"),
" de ", nrow(r_fin), ". p-valores de Ljung-Box sobre r^2: ",
paste(sapply(r_fin$LB_p_rend2, pv), collapse = ", "), ".")
add(" - IPVN_NAL : p = ", pv(r_nal$LB_p_rend2), " -> ",
sn(r_nal$LB_p_rend2 < ALPHA, "hay efecto ARCH", "sin efecto ARCH"))
add(" - IPVN_CALI: p = ", pv(r_cal$LB_p_rend2), " -> ",
sn(r_cal$LB_p_rend2 < ALPHA, "hay efecto ARCH", "sin efecto ARCH"))
add(" - En los activos financieros la volatilidad viene en rachas: periodos de")
add(" calma seguidos de periodos de estres. Es visible a simple vista en el")
add(" grafico 03_rendimientos.png, donde las barras se agrupan.")
add("")
add(" Aqui hay que ser cuidadoso al comparar, porque el mismo p-valor no")
add(" significa lo mismo en las dos clases de serie. Lo caracteristico de un")
add(" activo financiero es la combinacion: el rendimiento NO esta")
add(" autocorrelacionado en media y SIN EMBARGO su cuadrado SI lo esta. Es")
add(" decir, el signo del proximo movimiento es impredecible pero su magnitud")
add(" no. Esa combinacion es la que exige un modelo GARCH.")
add(" Series con esa firma: ",
paste(resumen$Serie[resumen$Rend_ruido_blanco == "SI" &
resumen$Efecto_ARCH == "SI"], collapse = ", "), ".")
add("")
if (r_nal$Efecto_ARCH == "SI" && r_nal$Rend_ruido_blanco == "NO") {
add(" El IPVN nacional rechaza H0 sobre r^2 (p = ", pv(r_nal$LB_p_rend2),
"), pero NO por la misma")
add(" razon. Como su rendimiento ya esta autocorrelacionado en media")
add(" (ACF(1) = ", round(r_nal$ACF1_rend, 3), "), el cuadrado hereda")
add(" mecanicamente esa correlacion: el")
add(" test sobre r^2 no puede separar la persistencia de la media de la")
add(" persistencia de la varianza. No es evidencia limpia de volatilidad")
add(" por rachas; lo correcto seria contrastar ARCH sobre los residuales de")
add(" un modelo AR ya ajustado a la media.")
add(" En los activos financieros esa ambiguedad no existe, porque la media")
add(" ya es ruido blanco y todo lo que aparece en r^2 es varianza.")
}
if (r_cal$Efecto_ARCH == "NO") {
add(" En el IPVN de Cali no se detecta ningun patron en r^2 (p = ",
pv(r_cal$LB_p_rend2), "),")
add(" coherente con una serie dominada por ruido de muestreo independiente.")
}
add("")
add("DIFERENCIA 4: estacionalidad")
add(linea("."))
add(" - IPVN_NAL : ANOVA por trimestre F = ", round(e_nal$F_valor, 2),
", p = ", pv(e_nal$p_valor), " -> ",
sn(e_nal$p_valor < ALPHA, "HAY estacionalidad", "sin estacionalidad"))
add(" - IPVN_CALI: ANOVA por trimestre F = ", round(e_cal$F_valor, 2),
", p = ", pv(e_cal$p_valor), " -> ",
sn(e_cal$p_valor < ALPHA, "HAY estacionalidad", "sin estacionalidad"))
add(" - Era razonable esperar un patron por trimestre en un mercado de")
add(" vivienda, pero los datos no lo respaldan: las medias trimestrales son")
add(" parecidas entre si. La persistencia del IPVN nacional es memoria")
add(" genuina (momentum), no un ciclo de calendario. Conviene descartarlo")
add(" explicitamente antes de proponer un modelo con componente estacional.")
add("")
add("CONCLUSION DEL CONTRASTE")
add(linea("."))
add("1. Lo que NO distingue a un activo financiero: la raiz unitaria. Todas las")
add(" series de precios analizadas, financieras y no financieras, son I(1).")
add(" Los precios suben y bajan acumulando choques y ninguno revierte a una")
add(" media fija. Encontrar una raiz unitaria en una serie de precios no")
add(" dice nada sobre la naturaleza del mercado.")
add("2. Lo que SI distingue: la estructura de la PRIMERA DIFERENCIA.")
add(" - Activos financieros liquidos: el rendimiento es ruido blanco en")
add(" media pero con varianza condicional autocorrelacionada y colas")
add(" pesadas. Modelo natural: camino aleatorio con drift en el nivel mas")
add(" GARCH en la varianza.")
if (r_nal$LB_p_rend < ALPHA) {
add(" - Precio de la vivienda (agregado nacional): la variacion es")
add(" persistente, con ACF(1) = ", round(r_nal$ACF1_rend, 3),
", y por tanto parcialmente")
add(" predecible. Modelo natural: ARIMA con componente AR sobre la")
add(" variacion. No hace falta termino estacional (el ANOVA por trimestre")
add(" no lo respalda) y el aparente efecto ARCH deberia reevaluarse sobre")
add(" los residuales del AR antes de anadir una ecuacion de varianza.")
}
add("3. La leccion economica: lo que separa a un activo financiero de otro")
add(" precio de la economia no es la no estacionariedad, es la EFICIENCIA")
add(" INFORMATIVA. En un mercado liquido, con arbitraje y venta en corto,")
add(" cualquier patron predecible se explota y desaparece, y el rendimiento")
add(" queda impredecible en media. En el mercado de vivienda, con oferta")
add(" rigida, activos indivisibles y sin arbitraje posible, la")
add(" predictibilidad sobrevive.")
add("4. La leccion estadistica: antes de atribuir un resultado al")
add(" comportamiento del mercado hay que descartar que venga de la medicion.")
add(" El contraste entre el IPVN nacional y el de Cali lo muestra: la misma")
add(" variable, medida con mas o menos ruido, lleva a conclusiones opuestas")
add(" sobre la predictibilidad.")
## --- 12.5 Inventario de salidas -------------------------------------------
add("")
add(linea())
add("ARCHIVOS GENERADOS")
add(linea())
add("graficos/01_precios_niveles.png Precios en niveles, 7 paneles")
add("graficos/02_precios_indexados100.png Financieros base 100")
add("graficos/03_rendimientos.png Rendimientos compuestos")
add("graficos/04_correlograma_<SERIE>.png ACF y PACF, precio y rendimiento")
add("graficos/05_contraste_acf_rendimientos.png ACF: SPY vs IPVN nacional vs Cali")
add("resultados/01_estadisticos_rendimientos.csv")
add("resultados/02_resumen_correlogramas.csv")
add("resultados/03_test_adf.csv")
add("resultados/04_test_urdf.csv")
add("resultados/04b_urdf_salidas_completas.txt Regresiones ur.df completas")
add("resultados/05_test_ljungbox.csv")
add("resultados/06_ljungbox_rend2_arch.csv")
add("resultados/07_estacionalidad_trimestral.csv")
add("resultados/08_resumen_comparativo.csv")
add("resultados/09_interpretaciones.txt Este texto")
con <- file("resultados/09_interpretaciones.txt", open = "wt", encoding = "UTF-8")
writeLines(txt, con)
close(con)
cat(paste(txt, collapse = "\n"), "\n")
cat("\nLISTO. Revise las carpetas graficos/ y resultados/\n")