1 Objetivo y datos

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.

2 Serie de rendimientos compuestos

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.

3 Gráficos de precios y de rendimientos

Series de precios en niveles. Todas muestran trayectorias sin reversión a una media fija.

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.

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.

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.

4 Correlogramas ACF y PACF

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 KO

Correlogramas de BONO10

Correlogramas de BONO10

Correlogramas de USDCOP

Correlogramas de USDCOP

Correlogramas de BTC

Correlogramas de BTC

Correlogramas de SPY

Correlogramas de SPY

Correlogramas de IPVN_NAL

Correlogramas de IPVN_NAL

Correlogramas de IPVN_CALI

Correlogramas de IPVN_CALI

4.1 Interpretación de los correlogramas

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.

5 Tests de estacionariedad

5.1 Test ADF (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.

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

5.3 Interpretación de ambos tests de estacionariedad

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.

6 Tests de autocorrelación

\(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 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

6.1 Ljung-Box sobre los rendimientos al cuadrado

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

6.2 Estacionalidad de las series trimestrales

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

6.3 Interpretación de ambos tests de autocorrelación

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.

7 Contraste con series de precios no financieras

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.

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.

==============================================================================

8 Anexo: código completo

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