Activo elegido. Acción ordinaria de Bank of America Corporation (ticker BAC), que cotiza en la Bolsa de Nueva York (NYSE) en dólares, de 9:30 a 16:00, hora de Nueva York. Es uno de los bancos de importancia sistémica mundial y una de las acciones más negociadas de EE. UU. Esa liquidez y su historia larga y continua la hacen adecuada para modelar series de tiempo. Además, su precio depende de dos factores medibles, el ciclo del mercado accionario y las tasas de interés, lo que la hace buena candidata para un modelo con variables exógenas.
Datos. Precio ajustado semanal de Yahoo Finance entre el 2 de diciembre de 2016 y el 23 de octubre de 2026, 517 semanas después de rezagar las exógenas. Las primeras 414 semanas (80 %) se usan para estimar, hasta T0 = 1 de noviembre de 2024; las 103 restantes (20 %) para validar.
Pregunta de investigación. ¿Un modelo ARIMA, ARIMAX o SARIMAX estimado con precios semanales permite anticipar si el retorno de BAC a 12 semanas supera el 2 %, y justificar así una posición larga?
Horizonte, capital y regla de decisión. Horizonte de 12 semanas, que equivale a un trimestre y coincide con el ciclo de publicación de resultados del banco. Capital de USD 100.000. La regla se fijó antes de mirar el test: si el retorno esperado a 12 semanas supera THETA = 2 %, se invierte todo el capital en BAC; si no, se mantiene en liquidez.
Principal conclusión. El modelo seleccionado con criterios de train fue un ARIMA(0,1,0) con deriva, es decir, una caminata aleatoria con tendencia: tuvo el menor AIC, AICc y BIC de los ocho candidatos. Los retornos no mostraron una dinámica aprovechable, no hubo estacionalidad anual y las variables de mercado y tasas rezagadas no fueron significativas. En T0 el modelo proyectó un retorno esperado de 3,70 % a 12 semanas, superior al umbral de 2 %, por lo que se compraron 2.503,23 acciones a USD 39,95. El 24 de enero de 2025 el portafolio valía USD 112.018 (+12,02 %).
El resultado fue favorable, pero no estaba asegurado. Las 5.000 trayectorias simuladas daban 41,78 % de probabilidad de pérdida, y la deriva sobre la que descansa el pronóstico no es estadísticamente significativa (p = 0,34). En el test, el modelo tuvo un error medio de USD 4,92 (10,0 % del precio), algo menor que el de una caminata aleatoria sin deriva (USD 5,16), pero la prueba de Diebold-Mariano no encuentra diferencia significativa (p = 0,56). Esto coincide con la eficiencia débil del mercado: el precio pasado aporta poca información sobre el retorno futuro, y el modelo es más útil para medir el riesgo de la decisión que para anticipar el precio.
Bank of America Corporation es una sociedad holding bancaria constituida en Delaware y con sede en Charlotte, Carolina del Norte (Bank of America Corporation, 2026a). Opera en cuatro segmentos: Banca de Consumo (depósitos, tarjetas, hipotecas y crédito a personas y pequeñas empresas), Gestión Global de Patrimonio e Inversiones, Banca Global (crédito corporativo, tesorería y banca de inversión) y Mercados Globales (creación de mercado y negociación de renta fija, acciones, divisas y derivados). En 2025 tuvo ingresos por USD 113.100 millones, utilidad neta de USD 30.500 millones y más de 213.000 empleados (Bank of America Corporation, 2026b).
Su acción ordinaria cotiza en la Bolsa de Nueva York (NYSE) con el ticker BAC, en dólares, de 9:30 a 16:00, hora de Nueva York. Es un banco de importancia sistémica mundial y una de las acciones más líquidas del mercado estadounidense. Como la mayor parte de su utilidad proviene del margen entre lo que cobra por préstamos y lo que paga por depósitos, su precio es sensible a las tasas de interés y al ciclo económico.
Los datos se descargan de Yahoo Finance con el paquete
quantmod. Se usa el precio ajustado, que corrige dividendos
y divisiones de acciones. BAC paga dividendo trimestral: con el precio
de cierre sin ajustar, cada fecha ex-dividendo aparecería como una caída
que no es una pérdida real para el inversionista.
Figura 1. Precio ajustado semanal de BAC, diciembre de 2016 a octubre de 2026. La línea punteada horizontal es la media y la vertical marca T0.
Entre diciembre de 2016 y octubre de 2026 el precio ajustado fue de un mínimo de USD 16,82, el 20 de marzo de 2020, a un máximo de USD 64,16, el 14 de agosto de 2026. La media fue de USD 32,09, la mediana de USD 29,11 y la desviación estándar de USD 10,81 (coeficiente de variación de 33,7 %). La serie tiene tendencia creciente con caídas bruscas, como la de 2020 y la de 2022-2023, y no vuelve a un nivel fijo: es un primer indicio de que no es estacionaria en media. El máximo no es un dato atípico, sino el nivel alcanzado en la parte final de la muestra; para el 23 de octubre de 2026 el precio había bajado a USD 50,56.
Estadísticos descriptivos del precio y del retorno logarítmico semanal de BAC (diciembre de 2016 a octubre de 2026).
| Serie | n | Media | Mediana | Desv. est. | Mínimo | Máximo | Asimetría | Curtosis |
|---|---|---|---|---|---|---|---|---|
| Precio (USD) | 517 | 32,09 | 29,11 | 10,81 | 16,82 | 64,16 | 0,86 | 2,96 |
| Retorno semanal (%) | 516 | 0,21 | 0,59 | 4,30 | −20,56 | 21,60 | −0,37 | 6,30 |
El retorno logarítmico semanal tuvo media de 0,21 %, mediana de 0,59 % y desviación estándar de 4,30 %, con extremos de −20,56 % y +21,60 % en una sola semana. La asimetría es negativa (−0,37): predominan las semanas de ganancias pequeñas, pero las pérdidas extremas son más fuertes. La curtosis es 6,30, un exceso de 3,30 sobre la normal: hay muchas más semanas extremas de las que predice una distribución normal. Las colas pesadas son un hecho estilizado de los retornos financieros (Cont, 2001). La asimetría y la curtosis del precio en nivel no se interpretan, porque la serie no es estacionaria.
Figura 2. Volatilidad móvil de 12 semanas del retorno logarítmico de BAC.
La volatilidad móvil de 12 semanas no es constante. Fue moderada en 2017 (promedio de 0,032), tuvo un repunte a 0,06 a finales de 2018 y en 2019 llegó a un mínimo de 0,016. Alcanzó su máximo, 0,117, el 24 de abril de 2020, equivalente a una volatilidad anual cercana al 85 %. En 2022 y 2023 se mantuvo alta durante dos años (promedios de 0,047 y 0,044, con máximos de 0,057). En 2024 cayó a su mínimo de toda la muestra, 0,015 el 31 de mayo de 2024, y en 2025 volvió a subir hasta 0,074. Los periodos agitados y tranquilos se agrupan en el tiempo (Mandelbrot, 1963).
Hecho 1: crisis de liquidez por el COVID-19 (febrero a abril de 2020). El cierre de la economía provocó una venta masiva de activos para conseguir efectivo, que tensionó incluso el mercado de bonos del Tesoro (Ihrig et al., 2020). El 15 de marzo de 2020 la Reserva Federal redujo la tasa de fondos federales a 0-0,25 % (Board of Governors of the Federal Reserve System, 2020). Para un banco, esto significa dos golpes a la vez: margen de intereses comprimido y temor a pérdidas de crédito. En los datos de BAC es el mínimo de precio de toda la muestra (USD 16,82 el 20 de marzo de 2020) y el máximo de volatilidad (0,117 en abril). Para el modelo, es la principal fuente de las colas pesadas y anticipa que los residuos no serán normales.
Hecho 2: alzas de tasas de la Fed y crisis de bancos regionales (2022-2023). Desde marzo de 2022 la Fed subió la tasa de forma sostenida hasta llevarla a 5,25-5,50 % en julio de 2023 (Board of Governors of the Federal Reserve System, 2023). El 10 de marzo de 2023 cerró Silicon Valley Bank (Federal Deposit Insurance Corporation, 2023), cuyo origen fueron pérdidas en bonos comprados con tasas bajas. Aunque BAC es mucho más diversificado, el episodio castigó a todo el sector y expuso las pérdidas no realizadas de las carteras de bonos bancarias. En el gráfico de precio, BAC cayó de unos USD 43 a comienzos de 2022 a unos USD 24 a finales de 2023, y la volatilidad no bajó de 0,029 en esos dos años, con un promedio cercano a 0,045. Para el modelo, esa persistencia es la señal de heterocedasticidad condicional que motiva la prueba ARCH-LM y la extensión GARCH (Engle, 1982; Bollerslev, 1986), y justifica incluir la tasa a 10 años como exógena.
Hecho 3: anuncio de aranceles de EE. UU. (abril de 2025). El 2 de abril de 2025 EE. UU. anunció aranceles a casi todos sus socios comerciales. Las acciones cayeron con fuerza, en especial las bancarias, hasta que el 9 de abril se anunció una pausa (International Monetary Fund, 2025). Para un banco, un choque comercial eleva el riesgo de recesión y de impagos. En el gráfico es el segundo pico de volatilidad de la muestra (0,074). Para el modelo es importante que este choque cae en el periodo de test, no en train: el modelo no lo vio al estimarse y, como se verá en la sección 7, produjo algunos de los mayores errores de pronóstico.
Persistencia: las caídas de 2020 y 2023 no se revierten hacia un nivel fijo, lo que sugiere raíz unitaria y d = 1. Estacionalidad: los resultados trimestrales y el dividendo trimestral podrían generar patrones, que se revisan en la ACF de rezagos largos. Rupturas: 2020, 2023 y 2025 son cambios bruscos de nivel y volatilidad que un modelo lineal no anticipa. Sensibilidad externa: el precio responde al mercado accionario y a las tasas, lo que motiva el ARIMAX.
Índice S&P 500: resume el riesgo sistemático; BAC, como banco grande, se mueve fuertemente con el mercado. Rendimiento del bono del Tesoro a 10 años: las tasas largas afectan el margen de intereses (lo amplían) y el valor de la cartera de bonos (lo reducen), como mostró el caso SVB. Ambas entran rezagadas 12 semanas, para que en cada fecha de decisión solo se use información ya publicada. La relación que se estime será estadística, no causal.
Antes de evaluar el test se fijó la regla: si en T0 el modelo proyecta un retorno esperado a 12 semanas mayor que 2 %, se invierte todo el capital en BAC; si no, se mantiene en liquidez. El umbral de 2 % en 12 semanas equivale a cerca de 9,0 % anual. El 1 de noviembre de 2024, fecha de T0, la letra del Tesoro a 3 meses rendía 4,41 % anual, es decir, 1,02 % en 12 semanas (Board of Governors of the Federal Reserve System, s. f.). El umbral exige entonces una prima de casi un punto porcentual sobre la alternativa sin riesgo, como compensación por la volatilidad de la acción. Por simplicidad, la estrategia supone que la liquidez no rinde intereses.
Los datos se descargan de Yahoo Finance con el paquete
quantmod para tres series: la acción de Bank of America
(BAC, precio ajustado), el índice S&P 500 (^GSPC, cierre) y el
rendimiento del bono del Tesoro a 10 años (TNX, cierre, en porcentaje).
El periodo va del 2 de septiembre de 2016 al 23 de octubre de 2026,
fecha de corte que corresponde a la última semana completa disponible.
Son más de 10 años de historia. La base descargada se guarda en
datos_semanales_yahoo.csv para que el trabajo pueda
reproducirse sin conexión.
Se eligió frecuencia semanal por tres razones. Los datos diarios tienen ruido de microestructura (diferencias entre precio de compra y venta, días con poca negociación) y calendarios festivos distintos entre series. Los mensuales dejarían cerca de 120 observaciones, muy pocas para reservar un bloque de test y estimar con holgura. La semanal da 529 observaciones y es coherente con un horizonte de decisión de 12 semanas.
De cada serie diaria se toma el último dato hábil de cada semana ISO, normalmente el viernes. Las tres series se unen por semana y no por fecha exacta: cuando un viernes es festivo, el último día hábil puede diferir entre activos y una unión por fecha perdería esa semana.
La base unida tiene 530 semanas, sin fechas duplicadas. Solo hay un valor faltante, en el rendimiento del bono a 10 años; esa semana se elimina y quedan 529. La distancia entre observaciones consecutivas es de 7 días en 497 casos y de 6 u 8 días en 32 casos (16 y 16), que corresponden a semanas cuyo último día hábil se movió por un festivo. La serie es, por lo tanto, semanal y regular. Unidades: precio de BAC en dólares, S&P 500 en puntos de índice y rendimiento del bono en porcentaje. El precio de BAC en la base completa va de USD 12,4 a USD 64,2, un rango coherente con la historia de la acción.
| Variable | Definición | Uso |
|---|---|---|
| Pₜ | Precio ajustado semanal de BAC (USD) | Serie original, evaluación en USD |
| yₜ = log(Pₜ) | Logaritmo del precio | Variable modelada |
| log(SPXₜ₋₁₂) | Logaritmo del S&P 500 rezagado 12 semanas | Exógena del ARIMAX/SARIMAX |
| TNXₜ₋₁₂ | Rendimiento del bono a 10 años rezagado 12 semanas (%) | Exógena del ARIMAX/SARIMAX |
Para pronosticar la semana t + h desde la fecha t, con h ≤ 12, se usa el valor de X observado en t + h − 12, que siempre es anterior o igual a t. Así el modelo solo usa información que realmente estaba publicada en la fecha de decisión y no hace falta pronosticar las exógenas. El costo es perder las primeras 12 semanas: la base pasa de 529 a 517 semanas, del 2 de diciembre de 2016 al 23 de octubre de 2026.
La muestra se divide en orden cronológico, antes de cualquier transformación o identificación, para que todo lo que depende de la muestra se estime solo con train. Se reserva el 20 % final para test. 414 semanas de train, hasta T0 = 1 de noviembre de 2024, y 103 de test (19,9 %). Con esta proporción el test cubre casi dos años, incluye episodios de mercado distintos, como el choque arancelario de 2025, y permite 92 pronósticos a 12 semanas con origen móvil, con fechas objetivo del 24 de enero de 2025 al 23 de octubre de 2026. Un 15 % dejaría menos ventanas de evaluación y un 25 % reduciría la muestra de estimación.
Para decidir la transformación se estimó el parámetro de Box-Cox con el método de Guerrero (1993), solo con datos de train, y se obtuvo λ = −0,086. Este método busca la potencia que hace que la dispersión de la serie no dependa de su nivel. Como λ = 0 corresponde al logaritmo natural (Box y Cox, 1964) y el valor estimado está muy cerca de cero, se modela yₜ = log(Pₜ). Esto tiene sentido económico: las variaciones del precio son proporcionales a su nivel, y en logaritmos se vuelven comparables a lo largo de la muestra. Además, la primera diferencia de yₜ es el retorno logarítmico semanal, así que estimar con d = 1 equivale a modelar los retornos.
Figura 3. Serie de train transformada con Box-Cox (λ = −0,086).
Figura 4. Logaritmo del precio en train.
Todos los modelos parten de un proceso de ruido blanco: una secuencia de choques con media cero, varianza constante y sin correlación entre periodos.
\[\varepsilon_{t} \sim RB\left( 0,\sigma^{2} \right):\quad E\left( \varepsilon_{t} \right) = 0,\quad Var\left( \varepsilon_{t} \right) = \sigma^{2},\quad Cov\left( \varepsilon_{t},\varepsilon_{t - k} \right) = 0\;\;\forall k \neq 0\]
El choque εₜ se llama innovación porque es la parte de yₜ que no se podía predecir con la información disponible en t − 1. Un modelo bien especificado deja como residuo solo innovaciones; por eso el diagnóstico de la sección 6 verifica que los residuos se comporten como ruido blanco.
El operador L desplaza la serie un periodo hacia atrás y permite escribir los modelos como polinomios:
\[L\, y_{t} = y_{t - 1},\quad\quad L^{k}\, y_{t} = y_{t - k},\quad\quad(1 - L)\, y_{t} = y_{t} - y_{t - 1}\]
El valor actual depende linealmente de sus p valores pasados más una innovación:
\[y_{t} = c + \phi_{1}y_{t - 1} + \ldots + \phi_{p}y_{t - p} + \varepsilon_{t}\;\; \Leftrightarrow \;\;\phi(L)\, y_{t} = c + \varepsilon_{t},\quad\quad\phi(L) = 1 - \phi_{1}L - \ldots - \phi_{p}L^{p}\]
El parámetro p es el orden autorregresivo: cuántos periodos pasados de la propia serie entran al modelo. El caso AR(1) muestra la condición de estacionariedad. Sustituyendo hacia atrás:
\[\left( 1 - \phi_{1}L \right)\, y_{t} = c + \varepsilon_{t}\;\; \Rightarrow \;\; y_{t} = \frac{c}{1 - \phi_{1}} + \sum_{j = 0}^{\infty}\phi_{1}^{\, j}\,\varepsilon_{t - j}\quad\quad\text{si }\left| \phi_{1} \right| < 1\]
\[E\left( y_{t} \right) = \frac{c}{1 - \phi_{1}},\quad\quad Var\left( y_{t} \right) = \frac{\sigma^{2}}{1 - \phi_{1}^{2}},\quad\quad\rho_{k} = \phi_{1}^{\, k}\]
Con |φ₁| < 1 los choques pasados pierden peso y la media y la varianza son constantes: el proceso es estacionario. Con φ₁ = 1 la suma no converge, cada choque es permanente y la varianza crece con t: hay raíz unitaria. En general, un AR(p) es estacionario si todas las raíces de φ(z) = 0 están fuera del círculo unitario (|z| > 1). Su firma en los correlogramas es una ACF que decae gradualmente y una PACF que se corta después del rezago p.
El valor actual depende de la innovación presente y de las q innovaciones pasadas:
\[y_{t} = \mu + \varepsilon_{t} + \theta_{1}\varepsilon_{t - 1} + \ldots + \theta_{q}\varepsilon_{t - q}\;\; \Leftrightarrow \;\; y_{t} = \mu + \theta(L)\,\varepsilon_{t},\quad\quad\theta(L) = 1 + \theta_{1}L + \ldots + \theta_{q}L^{q}\]
Un MA(q) siempre es estacionario porque es una suma finita de ruidos blancos. Su memoria es corta: en un MA(1), ρ₁ = θ₁ / (1 + θ₁²) y ρₖ = 0 para k > 1, así que la ACF se corta después del rezago q. La condición que se exige es la invertibilidad: que el MA pueda escribirse como un AR(∞) y así recuperar las innovaciones a partir de los datos observados. Para el MA(1):
\[\varepsilon_{t} = \frac{y_{t} - \mu}{1 + \theta_{1}L} = \sum_{j = 0}^{\infty}\left( - \theta_{1} \right)^{j}\,\left( y_{t - j} - \mu \right)\quad\quad\text{si }\left| \theta_{1} \right| < 1\]
En general, el MA(q) es invertible si las raíces de θ(z) = 0 están fuera del círculo unitario. Esto además garantiza que el modelo estimado sea único.
Combina ambas partes para describir con pocos parámetros una dependencia que como AR o MA puro necesitaría muchos:
\[\phi(L)\, y_{t} = c + \theta(L)\,\varepsilon_{t}\]
Es estacionario si lo es su parte AR e invertible si lo es su parte MA. Por el teorema de Wold, todo proceso estacionario puede aproximarse así.
Los precios de acciones no cumplen la condición de estacionariedad: sus choques son permanentes. Si φ(L) tiene una raíz unitaria, puede factorizarse como φ(L) = φ*(L)(1 − L), donde φ*(L) es estacionario. Al aplicar el operador de diferencia d veces se elimina la raíz unitaria y queda un ARMA estacionario sobre la serie diferenciada:
\[\phi(L)\,(1 - L)^{d}\, y_{t} = c + \theta(L)\,\varepsilon_{t},\quad\quad w_{t} = (1 - L)^{d}\, y_{t}\]
El parámetro d es el orden de integración: cuántas diferencias hacen falta para que la serie sea estacionaria. Con d = 1 y la serie en logaritmos, la diferencia es el retorno logarítmico semanal:
\[w_{t} = (1 - L)\log P_{t} = \log P_{t} - \log P_{t - 1} = \log \frac{P_{t}}{P_{t - 1}} = r_{t}\]
Por eso modelar log(Pₜ) con d = 1 equivale a modelar el retorno. Las pruebas ADF y Phillips-Perron contrastan la hipótesis de raíz unitaria (Dickey y Fuller, 1979; Phillips y Perron, 1988) y KPSS la de estacionariedad (Kwiatkowski et al., 1992); usarlas juntas da una conclusión más robusta sobre d.
La función Arima del paquete forecast
incluye la deriva como una tendencia lineal en el nivel, que al
diferenciar se convierte en una constante:
\[y_{t} = b\, t + \eta_{t},\quad\phi(L)(1 - L)\,\eta_{t} = \theta(L)\,\varepsilon_{t}\;\; \Rightarrow \;\;\phi(L)\,(1 - L)\, y_{t} = \phi(1)\, b + \theta(L)\,\varepsilon_{t}\]
El modelo seleccionado, ARIMA(0,1,0) con deriva, es el caso con p = q = 0:
\[(1 - L)\, y_{t} = b + \varepsilon_{t}\;\; \Rightarrow \;\; y_{t} = y_{t - 1} + b + \varepsilon_{t}\]
Es una caminata aleatoria con deriva: el retorno semanal esperado es b y todo lo demás es innovación impredecible. Su pronóstico y su error a h pasos son:
\[{\widehat{y}}_{T + h|T} = y_{T} + h\, b,\quad\quad Var\left( y_{T + h} - {\widehat{y}}_{T + h|T} \right) = h\,\sigma^{2}\]
La incertidumbre crece con la raíz de h, por eso los intervalos se abren en forma de abanico. Como el modelo está en logaritmos, el precio pronosticado sigue una distribución log-normal y hay que distinguir mediana y media:
\[\text{Mediana}\left( P_{T + h} \right) = e^{{\widehat{y}}_{T + h|T}},\quad\quad E\left( P_{T + h} \right) = e^{{\widehat{y}}_{T + h|T} + h\sigma^{2}/2}\]
La decisión de inversión usa la media, porque el retorno esperado del portafolio depende del precio esperado.
Un ARIMA solo usa el pasado de la propia serie. Una variable Xₜ
aporta si contiene información sobre yₜ que no está en ese pasado: en el
caso de BAC, el riesgo sistemático del mercado (S&P 500) y el nivel
de tasas (bono a 10 años), que afectan el valor de un banco por canales
que el precio propio no recoge por completo. La función
Arima estima una regresión con errores ARIMA:
\[y_{t} = b\, t + \beta'x_{t} + \eta_{t},\quad\quad\phi(L)\,(1 - L)\,\eta_{t} = \theta(L)\,\varepsilon_{t}\]
Al diferenciar, la ecuación que realmente se estima es:
\[(1 - L)\, y_{t} = b + \beta'(1 - L)\, x_{t} + \frac{\theta(L)}{\phi(L)}\,\varepsilon_{t}\]
Por eso cada β se interpreta como el cambio en el retorno semanal de BAC asociado a un cambio de una unidad en X, manteniendo lo demás constante. Es una asociación estadística, no un efecto causal: puede haber variables omitidas que muevan ambas series a la vez.
Para pronosticar con un ARIMAX hace falta X(T+1), …, X(T+h), que en la fecha T todavía no se conocen. Pronosticarlas agrega un error adicional y usar sus valores reales sería fuga de información. En este trabajo se usa xₜ = (log SPXₜ₋₁₂, TNXₜ₋₁₂)’. Con h ≤ 12, el valor de X necesario para t + h es el de t + h − 12 ≤ t, ya observado en la fecha de decisión.
Algunas series repiten patrones cada s periodos. Para capturarlos se agregan polinomios en L^s, que relacionan cada observación con la del mismo periodo del ciclo anterior. El modelo multiplicativo es:
\[\phi(L)\,\Phi\left( L^{s} \right)\,(1 - L)^{d}\,\left( 1 - L^{s} \right)^{D}\, y_{t} = c + \theta(L)\,\Theta\left( L^{s} \right)\,\varepsilon_{t}\]
\[\Phi\left( L^{s} \right) = 1 - \Phi_{1}L^{s} - \ldots - \Phi_{P}L^{Ps},\quad\quad\Theta\left( L^{s} \right) = 1 + \Theta_{1}L^{s} + \ldots + \Theta_{Q}L^{Qs}\]
Se escribe ARIMA(p,d,q)(P,D,Q)ₛ. Los órdenes en minúscula describen la dinámica regular, de un periodo al siguiente; los de mayúscula, la dinámica estacional, de un ciclo al siguiente. P y Q son los órdenes AR y MA estacionales, D el número de diferencias estacionales, donde (1 − L^s) yₜ = yₜ − yₜ₋ₛ, y s el número de periodos de un ciclo. Con datos semanales y ciclo anual, s = 52, que aproxima las 52,18 semanas del año. El producto de polinomios genera términos cruzados; por ejemplo:
\[\left( 1 - \phi_{1}L \right)\left( 1 - \Phi_{1}L^{52} \right) = 1 - \phi_{1}L - \Phi_{1}L^{52} + \phi_{1}\Phi_{1}L^{53}\]
El SARIMAX agrega las exógenas igual que el ARIMAX. El candidato evaluado, ARIMAX(0,1,0)(1,0,0)\[52\] con deriva, queda así:
\[y_{t} = b\, t + \beta'x_{t} + \eta_{t},\quad\quad\left( 1 - \Phi_{1}L^{52} \right)\,(1 - L)\,\eta_{t} = \varepsilon_{t}\]
La estacionalidad no se impone: se incluye solo si hay evidencia. Las señales serían picos significativos de la ACF o la PACF en los rezagos 52 y 104, un patrón repetido en el gráfico o un motivo de mercado (resultados trimestrales, pago de dividendos). Si no aparecen, el modelo estacional queda solo como contraste y su Φ₁ no debería ser significativo.
Los parámetros se estiman por máxima verosimilitud suponiendo innovaciones normales. Los modelos candidatos se comparan con criterios que premian el ajuste y penalizan el número de parámetros k:
\[AIC = - 2\ln\widehat{L} + 2k,\quad\quad AICc = AIC + \frac{2k(k + 1)}{n - k - 1},\quad\quad BIC = - 2\ln\widehat{L} + k\ln n\]
Se prefiere el valor más bajo. El BIC penaliza más fuerte que el AIC cuando n es grande, por lo que tiende a elegir modelos más parsimoniosos.
Referencias de esta sección: Box et al. (2015), Hamilton (1994) y Hyndman y Athanasopoulos (2018), además de las pruebas ya citadas.
Ver la Figura 4.
El log-precio muestra tendencia y no vuelve a un nivel medio fijo. Su ACF parte de 0,974 en el rezago 1 y decae muy lentamente (0,696 en el rezago 15), la firma típica de una raíz unitaria. Las pruebas formales (Dickey y Fuller, 1979; Phillips y Perron, 1988; Kwiatkowski et al., 1992) coinciden:
| Serie | Prueba | H0 | Estadístico | p-valor | Decisión al 5 % |
|---|---|---|---|---|---|
| log(precio) | ADF | Raíz unitaria | −2,469 | 0,379 | No se rechaza |
| log(precio) | Phillips-Perron | Raíz unitaria | −15,674 | 0,234 | No se rechaza |
| log(precio) | KPSS nivel | Estacionaria | 3,935 | ≤ 0,01 | Se rechaza |
| log(precio) | KPSS tendencia | Estacionaria en tendencia | 0,297 | ≤ 0,01 | Se rechaza |
| Δ log(precio) | ADF | Raíz unitaria | −6,953 | ≤ 0,01 | Se rechaza |
| Δ log(precio) | Phillips-Perron | Raíz unitaria | −416,782 | ≤ 0,01 | Se rechaza |
| Δ log(precio) | KPSS nivel | Estacionaria | 0,047 | ≥ 0,10 | No se rechaza |
| Δ log(precio) | KPSS tendencia | Estacionaria en tendencia | 0,044 | ≥ 0,10 | No se rechaza |
En nivel, ADF y PP no rechazan la raíz unitaria y KPSS rechaza la
estacionariedad, incluso alrededor de una tendencia lineal. Las tres
pruebas, con hipótesis nulas opuestas, apuntan a lo mismo: el log-precio
es integrado de orden uno y su tendencia es estocástica, no
determinística. En la primera diferencia la conclusión se invierte: ADF
y PP rechazan la raíz unitaria y KPSS no rechaza la estacionariedad. Por
lo tanto se usa d = 1 y no hace falta una segunda
diferencia. Los p-valores de tseries están acotados entre
0,01 y 0,10, por eso se reportan como ≤ 0,01 y ≥ 0,10.
Figura 5. ACF del retorno logarítmico semanal en train, 20 rezagos (eje en años: 0,1 ≈ 5 semanas).
Figura 6. PACF del retorno logarítmico semanal en train, 20 rezagos.
La serie diferenciada de train tiene 413 observaciones, así que las bandas de confianza al 95 % son ±1,96/√413 = ±0,096. En los primeros rezagos todos los coeficientes están dentro: el rezago 1 vale −0,039 en la ACF y en la PACF. Solo los rezagos 8 (0,098), 15 (0,100), 18 (−0,11) y 19 (−0,14) superan las bandas: los dos primeros por muy poco y los dos últimos con algo más de margen, sin formar un patrón. En la PACF solo sale el rezago 19 (−0,14). En los gráficos el eje de rezagos está en años (1 = 52 semanas) porque la serie tiene frecuencia 52. La prueba de Ljung-Box (Ljung y Box, 1978) no rechaza la ausencia de autocorrelación en ningún rezago entre 1 y 15 (p-valores entre 0,18 y 0,44). Con 20 rezagos sí rechaza al 5 % (Q = 33,33; p = 0,031), por la contribución de los rezagos 18 y 19, aunque no al 1 %.
Esa autocorrelación en rezagos lejanos y aislados no justifica un modelo AR o MA de orden bajo: ningún corte en la ACF o la PACF sugiere p o q mayores que cero. Capturarla exigiría un AR(19), con muchos parámetros y sin razón económica para una relación con el retorno de hace 19 semanas. Además, la prueba de Ljung-Box supone varianza constante, y con heterocedasticidad condicional, que estos retornos tienen (sección 6), tiende a rechazar más de lo debido (Diebold, 1986). Por eso los órdenes sugeridos son p = 0 y q = 0. El resultado es coherente con la hipótesis de eficiencia débil: los retornos pasados no ayudan a predecir de forma lineal el retorno siguiente (Fama, 1970).
Figura 7. ACF hasta 110 rezagos. En el eje, 1,0 corresponde al rezago 52 y 2,0 al rezago 104.
Figura 8. PACF hasta 110 rezagos.
Si hubiera un patrón anual aparecerían picos en los rezagos 52 y 104. Ninguno de los dos sale de las bandas: la ACF vale 0,066 en el rezago 52 y 0,075 en el 104, y la PACF 0,049 y 0,053. Los cruces de la ACF están en los rezagos 8, 15, 18, 19, 34, 61, 76 y 82, y los de la PACF en 19, 38, 53, 54, 61, 76, 82, 91 y 110. Con 110 rezagos al 95 % se esperan unos 5,5 cruces por azar en cada función (110 × 0,05). Se observan algunos más, pero dispersos y sin relación con los múltiplos de 52. Los de la PACF en 53 y 54 están junto al rezago anual pero no en él, y el coeficiente estacional del SARIMAX lo confirma: Φ₁ = 0,078 con p = 0,12. Se concluye que no hay estacionalidad anual; el modelo estacional se estima solo como contraste.
Se estiman manualmente, solo con train, sin
auto.arima():
| Modelo | Por qué se incluye |
|---|---|
| ARIMA(0,1,0) con deriva | Especificación sugerida por ACF y PACF: caminata aleatoria con deriva |
| ARIMA(1,1,0) con deriva | Verifica si el rezago 1 de la PACF aporta, aunque no sea significativo |
| ARIMA(0,1,1) con deriva | Verifica si el rezago 1 de la ACF aporta como componente MA |
| ARIMA(1,1,1) con deriva | Combinación mínima AR y MA; contrasta si hay dinámica mixta |
| ARIMA(5,1,0) con deriva | Control de sobreparametrización: muestra si agregar rezagos mejora los criterios |
| ARIMAX(0,1,0) con deriva | Modelo base más S&P 500 y bono a 10 años rezagados 12 semanas |
| ARIMAX(1,1,1) con deriva | Exógenas con dinámica ARMA |
| SARIMAX(0,1,0)(1,0,0)\[52\] con deriva | Contraste estacional con exógenas |
| Modelo | k | AIC | AICc | BIC | Coef. no signif. 5 % | Ljung-Box p |
|---|---|---|---|---|---|---|
| ARIMA(0,1,0) + deriva | 1 | −1405,25 | −1405,22 | −1397,20 | 1 | 0,030 |
| ARIMA(1,1,1) + deriva | 3 | −1403,99 | −1403,89 | −1387,90 | 1 | 0,021 |
| ARIMA(1,1,0) + deriva | 2 | −1403,87 | −1403,82 | −1391,80 | 2 | 0,021 |
| ARIMA(0,1,1) + deriva | 2 | −1403,82 | −1403,76 | −1391,75 | 2 | 0,021 |
| SARIMAX(0,1,0)(1,0,0)\[52\] + deriva | 4 | −1402,91 | −1402,76 | −1382,79 | 4 | 0,028 |
| ARIMAX(0,1,0) + deriva | 3 | −1402,51 | −1402,41 | −1386,41 | 3 | 0,039 |
| ARIMAX(1,1,1) + deriva | 5 | −1401,52 | −1401,31 | −1377,38 | 3 | 0,032 |
| ARIMA(5,1,0) + deriva | 6 | −1401,31 | −1401,04 | −1373,15 | 6 | 0,042 |
Coeficientes estimados de los ocho modelos candidatos (train).
| Modelo | Coeficiente | Estimación | Error est. | z | p-valor |
|---|---|---|---|---|---|
| ARIMA(0,1,0) + deriva | deriva | 0,0021 | 0,0022 | 0,95 | 0,340 |
| ARIMA(1,1,0) + deriva | ar1 | −0,0389 | 0,0493 | −0,79 | 0,430 |
| ARIMA(1,1,0) + deriva | deriva | 0,0021 | 0,0021 | 0,99 | 0,323 |
| ARIMA(0,1,1) + deriva | ma1 | −0,0357 | 0,0474 | −0,75 | 0,451 |
| ARIMA(0,1,1) + deriva | deriva | 0,0021 | 0,0021 | 0,99 | 0,323 |
| ARIMA(1,1,1) + deriva | ar1 | −0,8464 | 0,1621 | −5,22 | < 0,001 |
| ARIMA(1,1,1) + deriva | ma1 | 0,8029 | 0,1800 | 4,46 | < 0,001 |
| ARIMA(1,1,1) + deriva | deriva | 0,0021 | 0,0021 | 0,98 | 0,328 |
| ARIMA(5,1,0) + deriva | ar1 | −0,0395 | 0,0492 | −0,80 | 0,422 |
| ARIMA(5,1,0) + deriva | ar2 | 0,0431 | 0,0492 | 0,88 | 0,381 |
| ARIMA(5,1,0) + deriva | ar3 | −0,0663 | 0,0491 | −1,35 | 0,177 |
| ARIMA(5,1,0) + deriva | ar4 | −0,0439 | 0,0491 | −0,89 | 0,371 |
| ARIMA(5,1,0) + deriva | ar5 | −0,0670 | 0,0491 | −1,36 | 0,173 |
| ARIMA(5,1,0) + deriva | deriva | 0,0020 | 0,0018 | 1,11 | 0,267 |
| ARIMAX(0,1,0) + deriva | deriva | 0,0021 | 0,0022 | 0,99 | 0,323 |
| ARIMAX(0,1,0) + deriva | log S&P 500 (rezago 12) | 0,0154 | 0,0880 | 0,18 | 0,861 |
| ARIMAX(0,1,0) + deriva | Bono 10 años (rezago 12) | −0,0202 | 0,0181 | −1,11 | 0,266 |
| ARIMAX(1,1,1) + deriva | ar1 | −0,8630 | 0,1469 | −5,87 | < 0,001 |
| ARIMAX(1,1,1) + deriva | ma1 | 0,8194 | 0,1658 | 4,94 | < 0,001 |
| ARIMAX(1,1,1) + deriva | deriva | 0,0021 | 0,0021 | 1,02 | 0,309 |
| ARIMAX(1,1,1) + deriva | log S&P 500 (rezago 12) | 0,0165 | 0,0886 | 0,19 | 0,853 |
| ARIMAX(1,1,1) + deriva | Bono 10 años (rezago 12) | −0,0223 | 0,0182 | −1,22 | 0,221 |
| SARIMAX(0,1,0)(1,0,0)\[52\] + deriva | sar1 | 0,0777 | 0,0500 | 1,55 | 0,120 |
| SARIMAX(0,1,0)(1,0,0)\[52\] + deriva | deriva | 0,0023 | 0,0023 | 0,97 | 0,330 |
| SARIMAX(0,1,0)(1,0,0)\[52\] + deriva | log S&P 500 (rezago 12) | 0,0185 | 0,0877 | 0,21 | 0,833 |
| SARIMAX(0,1,0)(1,0,0)\[52\] + deriva | Bono 10 años (rezago 12) | −0,0246 | 0,0182 | −1,35 | 0,176 |
El modelo seleccionado es el ARIMA(0,1,0) con deriva. Es el mejor en los tres criterios a la vez (AIC = −1405,25; AICc = −1405,22; BIC = −1397,20) y el más parsimonioso, con un solo coeficiente. Como AIC, AICc y BIC coinciden, no hay que elegir entre ellos. En ningún momento se usó el desempeño en test. Razones para descartar los demás:
Hay dos señales de alerta que se discuten de frente. Primero, todos los modelos, no solo el ganador, rechazan Ljung-Box al 5 % (p entre 0,021 y 0,042) y ninguno al 1 %. Como ningún término AR, MA, exógeno o estacional lo corrige, la causa no es una dinámica omitida de orden bajo, sino la autocorrelación aislada en rezagos lejanos y el efecto de la heterocedasticidad sobre la prueba (5.2). Segundo, la deriva estimada es 0,00206 por semana (0,21 %, unos 11 % al año), pero con error estándar de 0,00216 no es significativa (p = 0,34): con la volatilidad de BAC, diez años de datos no bastan para distinguir con precisión un retorno medio positivo de cero. Se mantiene por razón económica, porque una acción debe tener un retorno esperado positivo que compense su riesgo. La deriva aparece casi idéntica (0,0020 a 0,0023) en los ocho modelos, así que la estimación es estable. Esta incertidumbre se traslada a la decisión de inversión y se discute en las secciones 8 y 9.
Figura 9. Residuos del ARIMA(0,1,0) con deriva: serie en el tiempo, ACF e histograma con la curva normal.
En un ARIMA(0,1,0) con deriva el residuo es el retorno semanal menos su media, así que el diagnóstico evalúa si los retornos de train se comportan como ruido blanco. Los 414 residuos oscilan alrededor de cero sin tendencia, con una desviación estándar de 0,0439, cerca de 4,4 % semanal o 32 % anual. Su dispersión cambia mucho en el tiempo: es máxima en marzo y abril de 2020, alta en 2022-2023 y baja en 2017, 2019 y 2024. La media es estable y la varianza no lo es.
| Prueba | H0 | Estadístico | p-valor | Conclusión al 5 % |
|---|---|---|---|---|
| Media de residuos (t) | Media = 0 | 0,0000068 | 0,997 | No se rechaza |
| Ljung-Box, 20 rezagos | Sin autocorrelación | 33,42 | 0,030 | Se rechaza (no al 1 %) |
| Jarque-Bera | Normalidad | 176,22 | < 0,001 | Se rechaza |
| Shapiro-Wilk | Normalidad | 0,965 | < 0,001 | Se rechaza |
| Ljung-Box sobre e², 20 rezagos | Sin autocorrelación en e² | 168,89 | < 0,001 | Se rechaza |
| ARCH-LM, q = 12 | Sin efectos ARCH | 100,99 | < 0,001 | Se rechaza |
La media de los residuos es prácticamente cero (0,0000068; p = 0,997), así que se cumple E(εₜ) ≈ 0. Para la segunda condición, sin correlación entre periodos, el resultado es razonable pero no perfecto. La ACF de los residuos es la misma de los retornos (sección 5.2): ningún coeficiente relevante en los primeros 15 rezagos y unos pocos cruces aislados en los rezagos 8, 15, 18 y 19, el mayor de −0,14. Ljung-Box a 20 rezagos, con 20 grados de libertad porque el modelo no estima términos AR ni MA, da p = 0,030: rechaza al 5 %, pero no al 1 %.
Se considera que los residuos son aproximadamente ruido blanco en media por tres razones. Las correlaciones son pequeñas, ninguna mayor a 0,14 en valor absoluto, están en rezagos lejanos y no tienen relevancia práctica para pronosticar. Ninguno de los ocho modelos candidatos elimina el rechazo, así que no hay una dinámica de orden bajo que se esté dejando sin modelar. Y la prueba exagera el rechazo cuando hay heterocedasticidad condicional, como se ve en 6.5 (Diebold, 1986).
Figura 10. Histograma de los residuos.
Figura 11. Gráfico Q-Q normal de los residuos.
El histograma es más alto en el centro y tiene colas más largas que la normal. En el gráfico Q-Q los puntos de los extremos se separan de la línea en ambas colas; los valores más extremos, cercanos a −0,2 y +0,2, corresponden a la caída y al rebote de marzo y abril de 2020. Jarque-Bera rechaza la normalidad con claridad (JB = 176,22; p < 0,001), con curtosis de 6,17 (exceso de 3,17) y asimetría de −0,21 (Jarque y Bera, 1987). Shapiro-Wilk confirma el rechazo (W = 0,965; p < 0,001), aunque con 414 observaciones esta prueba detecta incluso desviaciones pequeñas, por eso se toma como complemento. Las colas pesadas son un rasgo conocido de los retornos financieros (Cont, 2001). En la práctica, los intervalos basados en la normal subestiman la probabilidad de movimientos extremos; por eso la simulación de la sección 8 usa bootstrap de residuos en lugar de innovaciones normales.
Figura 12. ACF de los residuos al cuadrado, 30 rezagos.
Aunque los residuos casi no están correlacionados, sus cuadrados sí lo están: la ACF de e² sale de las bandas en los rezagos 1, 2, 3, 6, 8, 11 y 14, con el valor más alto en el rezago 3 (0,44), muy por encima de la banda de ±0,096. Las pruebas lo confirman con mucha fuerza: Ljung-Box sobre e² (Q = 168,89; p < 0,001) y ARCH-LM de Engle (1982) con 12 rezagos (LM = 100,99; p < 0,001) rechazan la varianza constante. Hay agrupamiento de volatilidad: a semanas agitadas les siguen semanas agitadas, como se vio en la sección 2.
Esto no invalida el pronóstico puntual, que depende de la media, pero sí la medida de incertidumbre. El modelo usa una sola varianza, el promedio de todo train (desviación de 4,4 % semanal), que incluye el COVID. En periodos de estrés los intervalos quedan demasiado estrechos y subestiman el riesgo; en periodos de calma quedan demasiado anchos. En T0 la volatilidad móvil venía de su mínimo de la muestra (0,015 en mayo de 2024), así que es probable que el intervalo calculado en T0 sea más ancho de lo que indicaba la volatilidad del momento. La simulación tiene la misma limitación: el bootstrap remuestrea residuos de forma independiente, reproduce las colas pesadas pero no el agrupamiento de volatilidad.
La extensión natural es un modelo GARCH(1,1) para la varianza condicional (Bollerslev, 1986), manteniendo la ecuación de la media seleccionada:
\[\varepsilon_{t} = \sigma_{t}z_{t},\quad z_{t} \sim RB(0,1),\quad\quad\sigma_{t}^{2} = \omega + \alpha\,\varepsilon_{t - 1}^{2} + \beta\,\sigma_{t - 1}^{2}\]
Al reaplicar los criterios de modelación con esta evidencia, la especificación de la media no cambia: los candidatos con más términos no mejoraron la autocorrelación ni los criterios de información, y el ARIMA(0,1,0) con deriva sigue siendo el más parsimonioso. Lo que cambiaría con un GARCH son los intervalos y la simulación. Su estimación queda fuera del alcance de este trabajo.
Aunque el ARIMAX no fue seleccionado, sus coeficientes muestran qué aportan las exógenas. Como d = 1, la ecuación estimada relaciona cambios con cambios:
\[\Delta\log P_{t} = b + \beta_{1}\,\Delta\log SPX_{t - 12} + \beta_{2}\,\Delta TNX_{t - 12} + \varepsilon_{t}\]
| Variable | β estimado | Error estándar | z | p-valor |
|---|---|---|---|---|
| log S&P 500 rezagado 12 semanas | 0,0154 | 0,0880 | 0,18 | 0,861 |
| Bono a 10 años rezagado 12 semanas | −0,0202 | 0,0181 | −1,11 | 0,266 |
β₁ = 0,0154: si el S&P 500 subió 1 % en la semana t − 12, el retorno de BAC en la semana t es 0,015 puntos porcentuales mayor en promedio, un efecto prácticamente nulo. β₂ = −0,0202: si el rendimiento del bono a 10 años subió un punto porcentual en la semana t − 12, el retorno de BAC en la semana t es 2,0 puntos porcentuales menor. El signo negativo iría en la dirección del canal de pérdidas en la cartera de bonos, el del caso SVB, pero ninguno de los dos coeficientes es significativo, así que no se puede afirmar ninguna relación.
Estos no son los betas habituales del CAPM. El beta de mercado de BAC mide cómo se mueve la acción en la misma semana que el mercado y es alto; aquí se mide si el mercado de hace tres meses predice el retorno de hoy. Que los coeficientes sean cercanos a cero y no significativos es lo esperable si el mercado es eficiente: la información de hace 12 semanas ya está incorporada en el precio. Además, se trata de una asociación estadística, no de un efecto causal: puede haber factores omitidos, como el ciclo económico o la política de la Fed, que muevan a la vez el mercado, las tasas y la acción.
Un ARIMAX necesita los valores futuros de las exógenas. En la práctica habría dos opciones malas: pronosticarlas, lo que suma su propio error al pronóstico de BAC, o usar los valores observados después, lo que es fuga de información y haría que el modelo pareciera mejor de lo que es. Este trabajo lo resuelve rezagando las exógenas 12 semanas, igual que el horizonte: para pronosticar cualquier semana hasta T0 + 12 se usa X observado hasta T0. El costo es renunciar a la información contemporánea, que es la que más explica el retorno, y eso ayuda a entender por qué las exógenas aportan poco.
El modelo se evalúa como se usaría en la práctica: pronosticando 12 semanas hacia adelante. Los parámetros se estiman una sola vez con train y quedan fijos. Luego, para cada semana del test que sirve como origen, el modelo incorpora los datos observados hasta esa semana, sin reestimar, y pronostica el precio 12 semanas después. El primer origen es T0 (1 de noviembre de 2024) y el último es 12 semanas antes del fin de la muestra, lo que da 92 pronósticos para fechas objetivo entre el 24 de enero de 2025 y el 23 de octubre de 2026. En los modelos con exógenas se usa en cada origen solo X ya publicado, gracias al rezago de 12 semanas. El pronóstico puntual es exp(ŷ), la mediana del precio.
Se comparan tres modelos sobre el mismo horizonte y las mismas fechas: el seleccionado, ARIMA(0,1,0) con deriva; el ARIMA(0,1,1) con deriva, como alternativa con dinámica; y el ARIMAX(0,1,0) con deriva, para medir el aporte de las exógenas. El punto de referencia es el random walk sin deriva, que pronostica que el precio dentro de 12 semanas será igual al de hoy.
Con e = P real − P pronosticado, sobre los N pronósticos:
\[ME = \frac{1}{N}\sum e_{i}\quad\quad MAE = \frac{1}{N}\sum\left| e_{i} \right|\quad\quad RMSE = \sqrt{\frac{1}{N}\sum e_{i}^{2}}\quad\quad MAPE = \frac{100}{N}\sum\left| \frac{e_{i}}{P_{i}} \right|\]
\[MASE = \frac{MAE}{\frac{1}{T_{0} - 12}\sum_{t = 13}^{T_{0}}\left| P_{t} - P_{t - 12} \right|}\]
ME mide el sesgo: positivo significa que el modelo se quedó corto. MAE y RMSE están en dólares; RMSE castiga más los errores grandes. MAPE expresa el error como porcentaje del precio. MASE divide el MAE por el error que tuvo en train el pronóstico ingénuo a 12 semanas, que fue de USD 2,90; un valor menor que 1 significa que el modelo pronostica mejor que ese ingénuo (Hyndman y Koehler, 2006).
| Modelo | ME (USD) | MAE (USD) | RMSE (USD) | MAPE (%) | MASE |
|---|---|---|---|---|---|
| ARIMA(0,1,0) + deriva (seleccionado) | 0,71 | 4,92 | 5,98 | 10,04 | 1,70 |
| ARIMA(0,1,1) + deriva | 0,72 | 4,92 | 5,97 | 10,04 | 1,70 |
| ARIMAX(0,1,0) + deriva | 0,72 | 4,84 | 5,91 | 9,84 | 1,67 |
| Random walk sin deriva | 1,92 | 5,16 | 6,18 | 10,37 | 1,78 |
Figura 13. Pronósticos a 12 semanas con origen móvil frente al precio observado en el periodo de test.
El modelo seleccionado tuvo un MAE de USD 4,92 y un MAPE de 10,0 %: en promedio, el pronóstico a tres meses se equivocó en casi cinco dólares por acción, el 10 % del precio. Para una posición de USD 100.000, eso equivale a un error típico de unos USD 10.000 en el valor estimado del portafolio. El RMSE (USD 5,98) es mayor que el MAE porque hay semanas con errores grandes. El ME de USD 0,71 indica que el modelo tendió a subestimar el precio, porque BAC subió en el periodo de test más rápido que su deriva histórica; aun así, la deriva redujo el sesgo del random walk (USD 1,92) en casi dos tercios.
Los cinco mayores errores, todos de unos USD 12, coinciden con giros bruscos del precio que ningún modelo lineal anticipa:
El modelo seleccionado tuvo menos error que el random walk: MAE de USD 4,92 frente a 5,16 (4,5 % menos) y RMSE de USD 5,98 frente a 6,18. La ventaja proviene solo de la deriva: en un periodo en que la acción subió, suponer un retorno semanal positivo redujo el sesgo. Sin embargo, la prueba de Diebold y Mariano (1995), que compara los errores cuadráticos de ambos modelos teniendo en cuenta que los pronósticos a 12 semanas se solapan, no encuentra diferencia significativa (DM = −0,59; p = 0,56). Estadísticamente, el modelo y la caminata aleatoria pronostican igual de bien. Era esperable: los dos solo difieren en la deriva, que a 12 semanas suma cerca de 2,5 % al pronóstico. Que ni los modelos con términos AR o MA ni el que usa exógenas superen con claridad a una caminata aleatoria es coherente con la eficiencia débil: el pasado del precio y la información pública rezagada aportan poco para predecir el precio futuro (Fama, 1970).
Todos los modelos, incluido el random walk, tienen MASE mayor que 1 (entre 1,67 y 1,78). Eso no significa que sean peores que un pronóstico ingénuo en el test, porque el random walk es justamente ese pronóstico. Significa que el test fue más difícil de pronosticar que train: el precio en test se movió entre unos USD 34 y 64, en promedio muy por encima de los niveles de train, así que los mismos movimientos porcentuales producen errores más grandes en dólares, y en 2025 hubo un choque de volatilidad.
| Modelo | AICc en train | RMSE en train (log, 1 semana) | RMSE en test (USD, 12 semanas) | MAE en test (USD) |
|---|---|---|---|---|
| ARIMA(0,1,0) + deriva | −1405,22 | 0,0439 | 5,976 | 4,924 |
| ARIMA(0,1,1) + deriva | −1403,76 | 0,0439 | 5,971 | 4,923 |
| ARIMAX(0,1,0) + deriva | −1402,41 | 0,0438 | 5,910 | 4,840 |
No del todo. El modelo con mejor AICc en train fue el ARIMA(0,1,0) con deriva, pero en test el menor error lo tuvo el ARIMAX(0,1,0), con un MAE 1,7 % menor (USD 4,84 frente a 4,92). Las columnas de error no se comparan en nivel, porque el error de train está en logaritmo y a una semana, y el de test en dólares y a 12 semanas; lo que se compara es el orden de los modelos. La diferencia es muy pequeña, menor que la que separa a cualquiera de ellos del random walk, y los coeficientes de las exógenas no fueron significativos en train, así que puede deberse al azar del periodo. El test se usó solo para evaluar el modelo ya elegido, nunca para elegirlo: cambiar al ARIMAX por esta diferencia sería ajustar el modelo al test, justo lo que la separación train/test busca evitar.
Figura 14. Pronóstico desde T0 (1 de noviembre de 2024) con intervalos de 80 % y 95 %, frente al precio observado.
Ubicados en T0, el 1 de noviembre de 2024, sin conocer los precios posteriores, el modelo proyecta el log-precio de las 12 semanas siguientes. El pronóstico crece en línea recta por la deriva y los intervalos se abren con la raíz del horizonte:
| Semana | Fecha | Mediana (USD) | Media (USD) | IC 80 % (USD) | IC 95 % (USD) | Observado (USD) |
|---|---|---|---|---|---|---|
| 1 | 8 nov 2024 | 40,03 | 40,07 | 37,84 - 42,35 | 36,72 - 43,64 | 43,17 |
| 4 | 29 nov 2024 | 40,28 | 40,44 | 35,98 - 45,09 | 33,90 - 47,86 | 45,45 |
| 8 | 27 dic 2024 | 40,61 | 40,93 | 34,63 - 47,63 | 31,82 - 51,83 | 42,65 |
| 12 | 24 ene 2025 | 40,95 | 41,43 | 33,69 - 49,78 | 30,38 - 55,20 | 44,75 |
Al volver de logaritmos a dólares hay que distinguir dos valores. exp(ŷ) es la mediana del precio: a 12 semanas, USD 40,95, con 50 % de probabilidad de quedar por encima y 50 % por debajo. La media es exp(ŷ + σ²ₕ/2) = USD 41,43, más alta porque la distribución log-normal tiene cola larga hacia arriba. La decisión usa la media, porque el resultado esperado del portafolio depende del precio esperado.
El precio observado salió por encima del intervalo de 80 % en las primeras cuatro semanas, aunque sin salir del de 95 %. La causa fue un hecho que el modelo no podía anticipar: tras la elección presidencial de EE. UU. del 5 de noviembre de 2024, las acciones de los grandes bancos subieron con fuerza ante la expectativa de una regulación más laxa (Business Insider, 2024). En la semana 12 el precio, USD 44,75, ya estaba dentro del intervalo de 80 %.
Se aplica la regla fijada en la sección 2:
| Concepto | Valor |
|---|---|
| Precio de entrada P0 (1 nov 2024) | USD 39,95 |
| Precio esperado a 12 semanas (media) | USD 41,43 |
| Retorno esperado | 3,70 % |
| Umbral THETA | 2 % |
| Posición | Larga |
| Acciones compradas | 2.503,23 |
| Capital invertido | USD 100.000 |
El retorno esperado supera el umbral, así que se invierte todo el capital en BAC. Con la mediana el retorno esperado sería de 2,51 %, que también supera el 2 %: la decisión no depende de usar media o mediana, aunque con la mediana el margen sobre el umbral es de apenas medio punto. Esa decisión descansa sobre la deriva, que en la sección 5 resultó no significativa; es decir, la regla se activa por un retorno medio histórico positivo que no está estimado con precisión. Se suponen acciones fraccionarias y sin costos de transacción.
Doce semanas después, el 24 de enero de 2025, BAC cotizaba a USD 44,75, un retorno real de 12,02 %. El portafolio terminó en USD 112.018, una ganancia de USD 12.018. Como la regla indicó posición larga, el resultado coincide con el de comprar y mantener. Frente a la liquidez sin intereses la estrategia ganó 12,0 puntos porcentuales, y frente a una letra del Tesoro a 3 meses (1,02 % en 12 semanas), unos 11 puntos. El modelo acertó en la dirección, pero no en la magnitud: el retorno real fue más del triple del esperado, en buena parte por el repunte posterior a la elección.
Para medir el riesgo de la decisión se simularon 5.000 trayectorias de 12 semanas desde T0 con el modelo seleccionado. Como Jarque-Bera rechazó la normalidad de los residuos (sección 6), las innovaciones no se generaron con una normal: se tomaron al azar, con reposición, de los residuos de train. Es decir, bootstrap de residuos (Efron, 1979). Se fijó la semilla 123 para que el resultado sea reproducible.
Figura 15. Trayectorias simuladas del precio desde T0 (200 de 5.000). La línea roja marca el precio de entrada.
Figura 16. Distribución del valor final simulado del portafolio con posición larga. La línea roja marca el capital inicial.
| Medida | Precio a 12 semanas (USD) | Valor del portafolio (USD) |
|---|---|---|
| Media | 41,51 | 103.915 |
| Percentil 5 % | 31,77 | 79.529 |
| Percentil 50 % | 41,23 | 103.213 |
| Percentil 95 % | 52,28 | 130.866 |
| Probabilidad de pérdida | 41,78 % | 41,78 % |
La media simulada del precio (USD 41,51) es casi igual a la del pronóstico analítico (USD 41,43), lo que confirma que la simulación es coherente con el modelo. La probabilidad de terminar por debajo del precio de entrada es 41,78 %: aunque el retorno esperado es positivo, cerca de dos de cada cinco escenarios terminan en pérdida, porque la deriva semanal (0,21 %) es pequeña frente a la volatilidad (4,4 %). En el 5 % de los peores escenarios el portafolio vale USD 79.529 o menos: con 95 % de confianza, la pérdida a 12 semanas no supera USD 20.471, que es el valor en riesgo (VaR) al 95 %. Si se cae en ese peor 5 %, el portafolio vale en promedio USD 74.122, una pérdida media de USD 25.878. En el escenario favorable del 95 % llega a USD 130.866. El resultado real, USD 112.018, quedó en el percentil 72 de la distribución simulada: fue un buen resultado, pero dentro de lo posible y no un caso extremo.
La ventaja es que no supone normalidad: usa los choques que BAC realmente tuvo, con sus colas pesadas y su asimetría. Tiene tres limitaciones. Primero, remuestrea cada semana de forma independiente, así que mezcla semanas de 2020 con semanas tranquilas y no reproduce el agrupamiento de volatilidad. Segundo, solo puede generar choques del tamaño de los observados en train, por lo que el futuro se parece al pasado por construcción. Tercero, mantiene fijos los parámetros, sin incluir la incertidumbre de la deriva, que en este caso es grande. Además, a 12 semanas la suma de 12 choques se parece más a una normal que cada choque semanal, así que la diferencia con una simulación normal se nota sobre todo en los percentiles extremos.
Estos escenarios son una herramienta para medir riesgo, no precios garantizados. Describen lo que podría pasar si el futuro se comporta como train; un choque nuevo, como el de abril de 2025, puede quedar fuera de ese rango.
La respuesta es parcialmente afirmativa. El proceso de identificación, estimación y validación permitió tomar una decisión fundamentada en T0: el ARIMA(0,1,0) con deriva proyectó un retorno esperado de 3,70 % a 12 semanas, mayor que el umbral de 2 %, y la posición larga ganó 12,02 %. Sin embargo, el modelo no anticipa el retorno en el sentido de encontrar patrones aprovechables. Su pronóstico es la deriva histórica, que no es estadísticamente distinta de cero (p = 0,34) y es pequeña frente a la volatilidad: la simulación daba 41,78 % de probabilidad de pérdida. En el test el modelo tuvo un error algo menor que una caminata aleatoria sin deriva (MAE de USD 4,92 frente a 5,16), pero la diferencia no es significativa (Diebold-Mariano, p = 0,56). El buen resultado se explica por una apuesta con retorno esperado positivo que coincidió con un periodo alcista, impulsado en sus primeras semanas por el repunte bancario tras la elección de EE. UU., y no por una ventaja predictiva del modelo.
Cuatro. Primero, la varianza crece con el nivel del precio (λ de Guerrero = −0,086), por eso se modela en logaritmos. Segundo, el log-precio tiene raíz unitaria con tendencia estocástica (ADF p = 0,38; KPSS p ≤ 0,01 en nivel y con tendencia) y su diferencia es estacionaria, por eso d = 1. Tercero, los retornos no tienen autocorrelación de orden bajo: ningún coeficiente relevante en los primeros rezagos y Ljung-Box sin rechazo hasta el rezago 15, por eso p = q = 0. Cuarto, la ACF no muestra picos en los rezagos 52 y 104 (0,066 y 0,075, dentro de la banda de ±0,096), por eso no hay componente estacional. A esto se suma que las exógenas rezagadas 12 semanas no fueron significativas (p = 0,86 y 0,27).
El ARIMA(0,1,0) con deriva, es decir, una caminata aleatoria con tendencia. Fue el mejor de los ocho candidatos en AIC (−1405,25), AICc (−1405,22) y BIC (−1397,20) y es el más parsimonioso. Los modelos con términos AR o MA no tuvieron coeficientes significativos, salvo el ARIMA(1,1,1), cuyos coeficientes (−0,846 y 0,803) casi se cancelan. El ARIMA(5,1,0) confirmó que agregar rezagos sobreajusta, y el ARIMAX y el SARIMAX no mejoraron el ajuste ni tuvieron coeficientes exógenos o estacionales significativos. La deriva no es significativa al 5 % (p = 0,34), pero se mantiene por razón económica, porque una acción tiene retorno esperado positivo, y su valor es estable en todos los modelos (0,0020 a 0,0023). La elección se hizo solo con train.
En media, sí: su media es cero (p = 0,997) y sus autocorrelaciones son pequeñas, ninguna mayor a 0,14 en valor absoluto. La prueba de Ljung-Box a 20 rezagos rechaza al 5 % (p = 0,030) pero no al 1 %, por correlaciones aisladas en rezagos lejanos que ningún candidato corrige y que la heterocedasticidad exagera. En distribución, no: Jarque-Bera rechaza la normalidad por colas pesadas (curtosis de 6,17; p < 0,001), lo que llevó a simular con bootstrap. En varianza, tampoco: ARCH-LM (LM = 100,99; p < 0,001) y la ACF de los residuos al cuadrado muestran agrupamiento de volatilidad. El pronóstico puntual es válido, pero los intervalos y la simulación subestiman el riesgo en periodos de estrés y lo sobrestiman en periodos de calma.
El modelo es útil como referencia de lo que se puede esperar en promedio y, sobre todo, como herramienta para medir riesgo: la distribución simulada dice cuánto se puede perder y con qué probabilidad (VaR al 95 % de USD 20.471 sobre USD 100.000). No es útil para anticipar movimientos puntuales del precio ni choques como la elección de 2024, los aranceles de abril de 2025 o la caída de agosto a octubre de 2026, que explican los mayores errores del test.
El análisis completo se ejecuta de principio a fin con el script
Pronostico_BAC_ARIMA.R y con el archivo .Rmd
que genera esta publicación, sin pasos manuales. Los datos se descargan
de Yahoo Finance y se guardan en datos_semanales_yahoo.csv;
si no hay conexión, el script lee ese archivo. La base procesada, con
las exógenas rezagadas, está en
BAC_datos_procesados.csv.
Los archivos CSV entregados son la versión oficial de los datos. Yahoo Finance recalcula el precio ajustado hacia atrás cada vez que la empresa paga un dividendo, así que una descarga posterior daría cifras ligeramente distintas a las de este trabajo.
La simulación usa la semilla set.seed(123), fijada al
inicio y otra vez antes de generar las 5.000 trayectorias, de modo que
los percentiles y la probabilidad de pérdida se reproducen exactamente.
El análisis se ejecutó en R versión 4.6.1 (R Core Team, 2026); la
información completa de la sesión está en
13_sessionInfo.txt.
| Paquete | Versión | Función en el trabajo | Referencia |
|---|---|---|---|
| quantmod | 0.4.29 | Descarga de precios desde Yahoo Finance y de la tasa DTB3 desde FRED | Ryan y Ulrich (2026) |
| zoo | 1.9.1 | Volatilidad móvil de 12 semanas | Zeileis y Grothendieck (2005) |
| moments | 0.14.1 | Asimetría y curtosis | Komsta y Novomestky (2022) |
| forecast | 9.0.2 | Box-Cox, estimación ARIMA/ARIMAX/SARIMAX, pronóstico, simulación, diagnóstico y prueba de Diebold-Mariano | Hyndman et al. (2026); Hyndman y Khandakar (2008) |
| tseries | 0.10.63 | Pruebas ADF, Phillips-Perron, KPSS y Jarque-Bera | Trapletti y Hornik (2026) |
| lmtest | 0.9.40 | Significancia de los coeficientes | Zeileis y Hothorn (2002) |
| actfts | 0.3.0 | Tablas de ACF/PACF y pruebas de estacionariedad | Rodríguez y Sierra (2025) |
| ggplot2 | 4.0.3 | Gráficos de pronóstico | Wickham (2016) |
Publicación en RPubs: \[enlace\].
Archivos fuente (.Rmd, .R y datos): entregados
en el campus virtual del ITM.
Bank of America Corporation. (2026a). Form 10-K for the fiscal year ended December 31, 2025. U.S. Securities and Exchange Commission. https://investor.bankofamerica.com/regulatory-and-other-filings/annual-reports/content/0000070858-26-000157/0000070858-26-000157.pdf
Bank of America Corporation. (2026b). Annual report 2025. https://d1io3yog0oux5.cloudfront.net/\_5c76696a3cb9e80ade9fbabe3ee74746/bankofamerica/db/867/10497/annual_report/BAC+2025+Annual+Report.pdf
Board of Governors of the Federal Reserve System. (s. f.). 3-month Treasury bill secondary market rate, discount basis \[DTB3\]. FRED, Federal Reserve Bank of St. Louis. https://fred.stlouisfed.org/series/DTB3
Board of Governors of the Federal Reserve System. (2020, 15 de marzo). Federal Reserve issues FOMC statement \[Comunicado de prensa\]. https://www.federalreserve.gov/newsevents/pressreleases/monetary20200315a.htm
Board of Governors of the Federal Reserve System. (2023, 26 de julio). Federal Reserve issues FOMC statement \[Comunicado de prensa\]. https://www.federalreserve.gov/newsevents/pressreleases/monetary20230726a.htm
Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics, 31(3), 307-327. https://doi.org/10.1016/0304-4076(86)90063-1
Box, G. E. P., & Cox, D. R. (1964). An analysis of transformations. Journal of the Royal Statistical Society: Series B, 26(2), 211-252.
Box, G. E. P., Jenkins, G. M., Reinsel, G. C., & Ljung, G. M. (2015). Time series analysis: Forecasting and control (5.ª ed.). Wiley.
Business Insider. (2024, 6 de noviembre). Bank stocks soar after Donald Trump’s election victory. https://businessinsider.in/stock-market/news/bank-stocks-soar-after-donald-trumps-election-victory/amp_articleshow/115024906.cms
Cont, R. (2001). Empirical properties of asset returns: Stylized facts and statistical issues. Quantitative Finance, 1(2), 223-236. https://doi.org/10.1080/713665670
Dickey, D. A., & Fuller, W. A. (1979). Distribution of the estimators for autoregressive time series with a unit root. Journal of the American Statistical Association, 74(366), 427-431. https://doi.org/10.1080/01621459.1979.10482531
Diebold, F. X. (1986). Testing for serial correlation in the presence of ARCH. En Proceedings of the American Statistical Association, Business and Economic Statistics Section (pp. 323-328). American Statistical Association.
Diebold, F. X., & Mariano, R. S. (1995). Comparing predictive accuracy. Journal of Business & Economic Statistics, 13(3), 253-263. https://doi.org/10.1080/07350015.1995.10524599
Efron, B. (1979). Bootstrap methods: Another look at the jackknife. The Annals of Statistics, 7(1), 1-26. https://doi.org/10.1214/aos/1176344552
Engle, R. F. (1982). Autoregressive conditional heteroscedasticity with estimates of the variance of United Kingdom inflation. Econometrica, 50(4), 987-1007. https://doi.org/10.2307/1912773
Fama, E. F. (1970). Efficient capital markets: A review of theory and empirical work. The Journal of Finance, 25(2), 383-417. https://doi.org/10.2307/2325486
Federal Deposit Insurance Corporation. (2023, 10 de marzo). FDIC creates a Deposit Insurance National Bank of Santa Clara to protect insured depositors of Silicon Valley Bank, Santa Clara, California \[Comunicado de prensa\]. https://www.fdic.gov/news/press-releases/2023/pr23016.html
Guerrero, V. M. (1993). Time-series analysis supported by power transformations. Journal of Forecasting, 12(1), 37-48. https://doi.org/10.1002/for.3980120104
Hamilton, J. D. (1994). Time series analysis. Princeton University Press.
Hyndman, R. J., & Athanasopoulos, G. (2018). Forecasting: Principles and practice (2.ª ed.). OTexts. https://otexts.com/fpp2/
Hyndman, R., Athanasopoulos, G., Bergmeir, C., Caceres, G., Chhay, L., O’Hara-Wild, M., Petropoulos, F., Razbash, S., Wang, E., & Yasmeen, F. (2026). forecast: Forecasting functions for time series and linear models (Versión 9.0.2) \[Paquete de R\]. https://doi.org/10.32614/CRAN.package.forecast
Hyndman, R. J., & Khandakar, Y. (2008). Automatic time series forecasting: The forecast package for R. Journal of Statistical Software, 27(3), 1-22. https://doi.org/10.18637/jss.v027.i03
Hyndman, R. J., & Koehler, A. B. (2006). Another look at measures of forecast accuracy. International Journal of Forecasting, 22(4), 679-688. https://doi.org/10.1016/j.ijforecast.2006.03.001
Ihrig, J. E., Weinbach, G., & Wolla, S. A. (2020). COVID-19’s effects on the economy and the Fed’s response. Page One Economics. Federal Reserve Bank of St. Louis. https://www.stlouisfed.org/covid-19/research
International Monetary Fund. (2025). Global financial stability report: Enhancing resilience amid uncertainty (abril de 2025). https://www.imf.org/-/media/files/publications/gfsr/2025/april/english/text.pdf
Jarque, C. M., & Bera, A. K. (1987). A test for normality of observations and regression residuals. International Statistical Review, 55(2), 163-172. https://doi.org/10.2307/1403192
Komsta, L., & Novomestky, F. (2022). moments: Moments, cumulants, skewness, kurtosis and related tests (Versión 0.14.1) \[Paquete de R\]. https://doi.org/10.32614/CRAN.package.moments
Kwiatkowski, D., Phillips, P. C. B., Schmidt, P., & Shin, Y. (1992). Testing the null hypothesis of stationarity against the alternative of a unit root. Journal of Econometrics, 54(1-3), 159-178. https://doi.org/10.1016/0304-4076(92)90104-Y
Ljung, G. M., & Box, G. E. P. (1978). On a measure of lack of fit in time series models. Biometrika, 65(2), 297-303. https://doi.org/10.1093/biomet/65.2.297
Mandelbrot, B. (1963). The variation of certain speculative prices. The Journal of Business, 36(4), 394-419. https://doi.org/10.1086/294632
Phillips, P. C. B., & Perron, P. (1988). Testing for a unit root in time series regression. Biometrika, 75(2), 335-346. https://doi.org/10.1093/biomet/75.2.335
R Core Team. (2026). R: A language and environment for statistical computing (Versión 4.6.1) \[Software\]. R Foundation for Statistical Computing. https://www.R-project.org/
Rodríguez, D., & Sierra, S. (2025). actfts: Autocorrelation tools featured for time series (Versión 0.3.0) \[Paquete de R\]. https://doi.org/10.32614/CRAN.package.actfts
Ryan, J. A., & Ulrich, J. M. (2026). quantmod: Quantitative financial modelling framework (Versión 0.4.29) \[Paquete de R\]. https://doi.org/10.32614/CRAN.package.quantmod
Trapletti, A., & Hornik, K. (2026). tseries: Time series analysis and computational finance (Versión 0.10-63) \[Paquete de R\]. https://doi.org/10.32614/CRAN.package.tseries
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer-Verlag New York. https://ggplot2.tidyverse.org
Zeileis, A., & Grothendieck, G. (2005). zoo: S3 infrastructure for regular and irregular time series. Journal of Statistical Software, 14(6), 1-27. https://doi.org/10.18637/jss.v014.i06
Zeileis, A., & Hothorn, T. (2002). Diagnostic checking in regression relationships. R News, 2(3), 7-10. https://CRAN.R-project.org/doc/Rnews/
El siguiente código reproduce todos los cálculos, tablas y gráficos de este documento.
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE,
fig.align = 'center', fig.width = 9, fig.height = 5.3, dpi = 110)
library(forecast) # Arima(), forecast(), simulate(), checkresiduals(), BoxCox.lambda()
library(tseries) # adf.test(), kpss.test(), pp.test(), jarque.bera.test()
library(lmtest) # coeftest()
library(actfts) # acfinter(): tabla ACF/PACF y pruebas de estacionariedad (clase)
library(ggplot2)
library(moments) # skewness(), kurtosis()
library(zoo) # rollapply()
library(quantmod) # getSymbols(): descarga desde Yahoo Finance
options(scipen = 999)
# Parámetros del trabajo (definidos antes de ver el test)
TICKER <- 'BAC'
START <- as.Date('2016-09-01') # más de 5 años de historia
END <- as.Date('2026-10-23') # fecha de corte: última semana completa
PRECIO <- 'Adjusted' # precio ajustado por dividendos y divisiones
ARCHIVO_DATOS <- 'datos_semanales_yahoo.csv'
DIR_RES <- 'resultados'
H_INV <- 12 # horizonte de inversión en semanas
PCT_TEST <- 0.20 # proporción de test
THETA <- 0.02 # umbral de retorno esperado a H_INV semanas
CAPITAL <- 100000 # USD
M <- 5000 # trayectorias simuladas
SEED <- 123
set.seed(SEED)
dir.create(DIR_RES, showWarnings = FALSE)
guardar_tabla <- function(x, archivo) { # conserva nombres de fila solo si son texto
con_nombres <- is.matrix(x) || anyNA(suppressWarnings(as.integer(rownames(x))))
write.csv(x, file.path(DIR_RES, archivo), row.names = con_nombres)
invisible(x)
}
# Se usa primero el CSV entregado: Yahoo Finance recalcula el precio ajustado
# cada vez que hay un dividendo, así que una descarga nueva cambiaría las cifras.
bajar_semanal <- function(simbolo, columna) {
x <- quantmod::getSymbols(simbolo, src = 'yahoo', from = START, to = END + 1,
auto.assign = FALSE)
x <- if (columna == 'Adjusted') quantmod::Ad(x) else quantmod::Cl(x)
d <- data.frame(Fecha = index(x), v = as.numeric(x))
d$sem <- format(d$Fecha, '%G-%V') # semana ISO
d[!duplicated(d$sem, fromLast = TRUE), ] # último día hábil de cada semana
}
if (file.exists(ARCHIVO_DATOS)) {
datos_w <- read.csv(ARCHIVO_DATOS)
datos_w$Fecha <- as.Date(datos_w$Fecha)
} else {
b <- bajar_semanal(TICKER, PRECIO); names(b)[2] <- 'P'
s <- bajar_semanal('^GSPC', 'Close'); names(s)[2] <- 'SPX'
tn <- bajar_semanal('^TNX', 'Close'); names(tn)[2] <- 'TNX'
d <- merge(merge(b, s[, c('sem', 'SPX')], by = 'sem'), tn[, c('sem', 'TNX')], by = 'sem')
datos_w <- d[order(d$Fecha), c('Fecha', 'P', 'SPX', 'TNX')]
write.csv(datos_w, ARCHIVO_DATOS, row.names = FALSE)
}
# Verificación de fechas, duplicados, faltantes y unidades
verificacion <- list(
duplicados = sum(duplicated(datos_w$Fecha)),
faltantes = colSums(is.na(datos_w)),
periodicidad = table(diff(datos_w$Fecha)),
rango = range(datos_w$Fecha),
n = nrow(datos_w),
resumen_P = summary(datos_w$P))
datos_w <- na.omit(datos_w)
# Exógenas rezagadas H_INV semanas: sin fuga de información
rezagar <- function(x, k) c(rep(NA, k), head(x, -k))
dat <- data.frame(Fecha = datos_w$Fecha,
P = datos_w$P,
ly = log(datos_w$P),
lSPX_lag = rezagar(log(datos_w$SPX), H_INV),
TNX_lag = rezagar(datos_w$TNX, H_INV))
dat <- dat[complete.cases(dat), ]
write.csv(dat, 'BAC_datos_procesados.csv', row.names = FALSE)
f1 <- dat$Fecha[1]
ini <- c(as.numeric(format(f1, '%Y')), max(1, as.numeric(format(f1, '%U'))))
precio <- ts(dat$P, start = ini, frequency = 52)
n_total <- nrow(dat)
n_test <- round(PCT_TEST * n_total)
n_train <- n_total - n_test
T0 <- dat$Fecha[n_train]
ly_all <- dat$ly
X_all <- as.matrix(dat[, c('lSPX_lag', 'TNX_lag')])
train <- ts(dat$P[1:n_train], start = ini, frequency = 52)
ly_train <- log(train)
X_train <- X_all[1:n_train, , drop = FALSE]
particion <- data.frame(n_total, n_train, n_test, pct_test = n_test / n_total,
inicio = dat$Fecha[1], T0 = T0, fin = dat$Fecha[n_total])
guardar_tabla(particion, '02_particion.csv')
retorno <- diff(log(precio)) * 100 # retorno logarítmico semanal (%)
descr <- function(x) c(n = length(x), media = mean(x), mediana = median(x), sd = sd(x),
min = min(x), max = max(x), asimetria = skewness(x),
curtosis = kurtosis(x))
tabla_descriptiva <- rbind(Precio_USD = descr(precio), Retorno_pct = descr(retorno))
guardar_tabla(round(tabla_descriptiva, 4), '03_descriptivos.csv')
volatilidad <- rollapply(diff(log(precio)), width = 12, FUN = sd, fill = NA)
ts.plot(precio, main = 'BAC - Precio ajustado semanal', ylab = 'USD', xlab = 'Tiempo',
col = 'steelblue', lwd = 2)
abline(h = mean(precio), col = 'red', lty = 2)
abline(v = time(precio)[n_train], col = 'darkgreen', lty = 3) # T0
plot(volatilidad, main = 'Volatilidad móvil (12 semanas) - retornos log BAC',
col = 'darkorange', lwd = 2, ylab = 'volatilidad', xlab = 'Tiempo')
lambda_bc <- BoxCox.lambda(train, method = 'guerrero')
precio_bc <- BoxCox(train, lambda_bc)
ts.plot(precio_bc, main = paste('Box-Cox train (lambda =', round(lambda_bc, 3), ')'),
ylab = 'precio_bc', xlab = 'Tiempo')
ts.plot(ly_train, main = 'Log-precio BAC (train)', ylab = 'ly_train', xlab = 'Tiempo')
res_ly <- acfinter(ly_train, lag = 15, ci.method = 'ma')
dly_train <- diff(ly_train)
res_dly <- acfinter(dly_train, lag = 15, ci.method = 'ma')
pruebas_estac <- function(x) { # H0: ADF y PP = raíz unitaria; KPSS = estacionariedad
r <- list(ADF = adf.test(x), PP = pp.test(x),
KPSS_nivel = kpss.test(x, null = 'Level'),
KPSS_tend = kpss.test(x, null = 'Trend'))
data.frame(Prueba = names(r),
Estadistico = sapply(r, function(z) unname(z$statistic)),
p_valor = sapply(r, function(z) z$p.value), row.names = NULL)
}
tabla_estac <- rbind(cbind(Serie = 'log(precio)', suppressWarnings(pruebas_estac(ly_train))),
cbind(Serie = 'dlog(precio)', suppressWarnings(pruebas_estac(dly_train))))
guardar_tabla(tabla_estac, '05_pruebas_estacionariedad.csv')
acf(dly_train, lag.max = 20, main = 'ACF - dlog(precio) train')
pacf(dly_train, lag.max = 20, main = 'PACF - dlog(precio) train')
acf(dly_train, lag.max = 110, main = 'ACF hasta 110 rezagos (picos en 52 y 104?)')
pacf(dly_train, lag.max = 110, main = 'PACF hasta 110 rezagos')
ajustar <- function(orden, estacional = NULL, usar_x = FALSE, drift = TRUE) {
tryCatch(
Arima(ly_train, order = orden,
seasonal = if (is.null(estacional)) c(0, 0, 0)
else list(order = estacional, period = 52),
xreg = if (usar_x) X_train else NULL,
include.drift = drift),
error = function(e) { message('Falló el ajuste: ', conditionMessage(e)); NULL })
}
candidatos <- list(
'ARIMA(0,1,0)+drift' = list(orden = c(0, 1, 0)),
'ARIMA(1,1,0)+drift' = list(orden = c(1, 1, 0)),
'ARIMA(0,1,1)+drift' = list(orden = c(0, 1, 1)),
'ARIMA(1,1,1)+drift' = list(orden = c(1, 1, 1)),
'ARIMA(5,1,0)+drift' = list(orden = c(5, 1, 0)),
'ARIMAX(0,1,0)+drift' = list(orden = c(0, 1, 0), usar_x = TRUE),
'ARIMAX(1,1,1)+drift' = list(orden = c(1, 1, 1), usar_x = TRUE),
'SARIMAX(0,1,0)(1,0,0)[52]+drift' =
list(orden = c(0, 1, 0), estacional = c(1, 0, 0), usar_x = TRUE))
modelos <- lapply(candidatos, function(s) do.call(ajustar, s))
modelos <- Filter(Negate(is.null), modelos)
tabla_coef <- do.call(rbind, lapply(names(modelos), function(nm) {
ct <- coeftest(modelos[[nm]])
data.frame(Modelo = nm, Coeficiente = rownames(ct), Estimacion = ct[, 1],
Error_est = ct[, 2], z = ct[, 3], p_valor = ct[, 4], row.names = NULL)
}))
guardar_tabla(tabla_coef, '07_coeficientes_candidatos.csv')
comparacion <- do.call(rbind, lapply(names(modelos), function(nm) {
f <- modelos[[nm]]
gl <- sum(f$arma[1:4])
lb <- Box.test(residuals(f), lag = 20, type = 'Ljung-Box', fitdf = gl)
pv <- tabla_coef$p_valor[tabla_coef$Modelo == nm]
data.frame(Modelo = nm, k = length(coef(f)), AIC = AIC(f), AICc = f$aicc, BIC = BIC(f),
Coef_no_signif_5pct = sum(pv > 0.05), LjungBox_p = round(lb$p.value, 4))
}))
comparacion <- comparacion[order(comparacion$AICc), ]
guardar_tabla(comparacion, '08_comparacion_modelos.csv')
GANADOR <- 'ARIMA(0,1,0)+drift' # elegido con criterios de train (sección 5.5)
modelo_ganador <- modelos[[GANADOR]]
usa_x <- isTRUE(candidatos[[GANADOR]]$usar_x)
e <- as.numeric(residuals(modelo_ganador))
res_diag <- acfinter(ts(e, frequency = 52), lag = 15, ci.method = 'ma')
arch_lm <- function(res, q = 12) { # prueba ARCH de Engle: LM = n * R^2 ~ chi2(q)
e2 <- as.numeric(res)^2
m <- embed(e2, q + 1)
r2 <- summary(lm(m[, 1] ~ m[, -1]))$r.squared
LM <- nrow(m) * r2
data.frame(LM = LM, gl = q, p_valor = 1 - pchisq(LM, q))
}
gl_g <- sum(modelo_ganador$arma[1:4])
jb <- jarque.bera.test(e)
sw <- shapiro.test(e)
lb <- Box.test(e, lag = 20, type = 'Ljung-Box', fitdf = gl_g)
lb2 <- Box.test(e^2, lag = 20, type = 'Ljung-Box')
arch <- arch_lm(e, q = 12)
diag_residuos <- data.frame(
Prueba = c('Media de residuos', 'Ljung-Box (lag 20)', 'Jarque-Bera', 'Shapiro-Wilk',
'Ljung-Box residuos^2 (lag 20)', 'ARCH-LM (q = 12)'),
Estadistico = c(mean(e), lb$statistic, jb$statistic, sw$statistic, lb2$statistic, arch$LM),
p_valor = c(t.test(e)$p.value, lb$p.value, jb$p.value, sw$p.value, lb2$p.value, arch$p_valor))
guardar_tabla(diag_residuos, '09_diagnostico_residuos.csv')
beta_arimax <- tabla_coef[tabla_coef$Modelo == 'ARIMAX(0,1,0)+drift' &
tabla_coef$Coeficiente %in% colnames(X_all), ]
guardar_tabla(beta_arimax, '09_beta_arimax.csv')
checkresiduals(modelo_ganador, lag = 20, test = FALSE)
hist(e, breaks = 30, col = 'steelblue', main = 'Histograma de residuos', xlab = 'Residuos')
qqnorm(e)
qqline(e, col = 'red')
acf(e^2, lag.max = 30, main = 'ACF de residuos al cuadrado')
# Parámetros fijos de train; en cada origen solo se actualiza la información.
rolling_h <- function(fit, usar_x, h = H_INV) {
origenes <- n_train:(n_total - h)
pr <- sapply(origenes, function(o) {
y_o <- ts(ly_all[1:o], start = ini, frequency = 52)
if (usar_x) {
m <- Arima(y_o, model = fit, xreg = X_all[1:o, , drop = FALSE])
f <- forecast(m, h = h, xreg = X_all[(o + 1):(o + h), , drop = FALSE])
} else {
m <- Arima(y_o, model = fit)
f <- forecast(m, h = h)
}
as.numeric(f$mean[h])
})
data.frame(origen = origenes, objetivo = origenes + h, pron = exp(pr))
}
COMPARAR <- unique(c(GANADOR, 'ARIMA(0,1,1)+drift', 'ARIMAX(0,1,0)+drift'))
COMPARAR <- COMPARAR[COMPARAR %in% names(modelos)]
preds <- lapply(COMPARAR, function(nm) rolling_h(modelos[[nm]], isTRUE(candidatos[[nm]]$usar_x)))
names(preds) <- COMPARAR
base_p <- preds[[1]]
real <- dat$P[base_p$objetivo]
fechas_o <- dat$Fecha[base_p$objetivo]
naive_p <- dat$P[base_p$origen] # random walk: último precio observado
escala_mase <- mean(abs(diff(as.numeric(train), lag = H_INV)))
metricas <- function(real, pron) {
err <- real - pron
c(ME = mean(err), MAE = mean(abs(err)), RMSE = sqrt(mean(err^2)),
MAPE = mean(abs(err / real)) * 100, MASE = mean(abs(err)) / escala_mase)
}
tabla_metricas <- rbind(do.call(rbind, lapply(preds, function(p) metricas(real, p$pron))),
'Random walk (naive)' = metricas(real, naive_p))
guardar_tabla(round(tabla_metricas, 4), '10_metricas_test.csv')
train_vs_test <- data.frame(
Modelo = COMPARAR,
AICc_train = sapply(COMPARAR, function(nm) modelos[[nm]]$aicc),
RMSE_train_log = sapply(COMPARAR, function(nm) sqrt(mean(residuals(modelos[[nm]])^2))),
RMSE_test_USD = tabla_metricas[COMPARAR, 'RMSE'],
MAE_test_USD = tabla_metricas[COMPARAR, 'MAE'],
row.names = NULL)
guardar_tabla(train_vs_test, '10_train_vs_test.csv')
dm <- forecast::dm.test(real - preds[[1]]$pron, real - naive_p, h = H_INV, power = 2)
largo <- do.call(rbind, c(
list(data.frame(Fecha = fechas_o, Serie = 'Observado', Precio = real)),
lapply(names(preds), function(nm) data.frame(Fecha = fechas_o, Serie = nm,
Precio = preds[[nm]]$pron)),
list(data.frame(Fecha = fechas_o, Serie = 'Random walk', Precio = naive_p))))
g_test <- ggplot(largo, aes(Fecha, Precio, colour = Serie)) + geom_line() +
labs(title = paste0('Pronóstico a ', H_INV, ' semanas vs observado (test)'), y = 'USD', x = NULL)
print(g_test)
# El modelo está en log: exp(media log) es la mediana del precio; la media es exp(mu + sigma^2/2).
f0 <- if (usa_x) {
forecast(modelo_ganador, h = H_INV, level = c(80, 95),
xreg = X_all[(n_train + 1):(n_train + H_INV), , drop = FALSE])
} else forecast(modelo_ganador, h = H_INV, level = c(80, 95))
mu_log <- as.numeric(f0$mean)
se_log <- (as.numeric(f0$upper[, 2]) - mu_log) / qnorm(0.975)
fan <- data.frame(Fecha = dat$Fecha[(n_train + 1):(n_train + H_INV)],
Obs = dat$P[(n_train + 1):(n_train + H_INV)],
Pron_mediana = exp(mu_log),
Pron_media = exp(mu_log + se_log^2 / 2),
L80 = exp(as.numeric(f0$lower[, 1])), U80 = exp(as.numeric(f0$upper[, 1])),
L95 = exp(as.numeric(f0$lower[, 2])), U95 = exp(as.numeric(f0$upper[, 2])))
guardar_tabla(fan, '10_pronostico_desde_T0.csv')
g_fan <- ggplot(fan, aes(Fecha)) +
geom_ribbon(aes(ymin = L95, ymax = U95), alpha = 0.15) +
geom_ribbon(aes(ymin = L80, ymax = U80), alpha = 0.25) +
geom_line(aes(y = Pron_media, colour = 'Pronóstico (media)')) +
geom_line(aes(y = Obs, colour = 'Observado')) +
labs(title = 'Pronóstico desde T0 con IC 80 % y 95 %', y = 'USD', x = NULL, colour = NULL)
print(g_fan)
# Regla fijada antes de ver el test: largo si el retorno esperado supera THETA.
P0 <- dat$P[n_train]
Pf0 <- fan$Pron_media[H_INV]
ret_esp <- Pf0 / P0 - 1
posicion_T0 <- ifelse(ret_esp > THETA, 'LARGA', 'LIQUIDEZ')
acciones_T0 <- if (posicion_T0 == 'LARGA') CAPITAL / P0 else 0
P_real_h <- dat$P[n_train + H_INV]
valor_final_T0 <- if (posicion_T0 == 'LARGA') acciones_T0 * P_real_h else CAPITAL
decision_T0 <- data.frame(T0 = T0, P0, Pronostico_media = Pf0,
Pronostico_mediana = fan$Pron_mediana[H_INV],
Retorno_esperado = ret_esp, THETA, Posicion = posicion_T0,
Acciones = acciones_T0, Capital_invertido = acciones_T0 * P0,
P_real_a_H = P_real_h, Retorno_real = P_real_h / P0 - 1,
Valor_final_estrategia = valor_final_T0,
Valor_buy_hold = CAPITAL * P_real_h / P0)
guardar_tabla(decision_T0, '11_decision_T0.csv')
BOOT <- jb$p.value < 0.05 # normalidad rechazada: bootstrap de residuos
set.seed(SEED)
xf <- if (usa_x) X_all[(n_train + 1):(n_train + H_INV), , drop = FALSE] else NULL
sim_mat <- replicate(M, as.numeric(simulate(modelo_ganador, nsim = H_INV, future = TRUE,
bootstrap = BOOT, xreg = xf)))
precios_sim <- exp(sim_mat)
precios_finales <- precios_sim[H_INV, ]
valor_largo <- CAPITAL * precios_finales / P0
valor_portafolio <- if (posicion_T0 == 'LARGA') valor_largo else rep(CAPITAL, M)
pct <- quantile(precios_finales, probs = c(0.05, 0.50, 0.95))
riesgo_precio <- data.frame(precio_entrada = P0, media_sim = mean(precios_finales),
mediana_sim = median(precios_finales),
p5 = pct[1], p50 = pct[2], p95 = pct[3],
prob_perdida = mean(precios_finales < P0),
metodo = if (BOOT) 'bootstrap' else 'normal', row.names = NULL)
guardar_tabla(riesgo_precio, '12_simulacion_precio.csv')
resumen_valor <- function(v) c(media = mean(v), quantile(v, c(0.05, 0.50, 0.95)),
prob_perdida = mean(v < CAPITAL))
riesgo_portafolio <- rbind(Estrategia_adoptada = resumen_valor(valor_portafolio),
Posicion_larga = resumen_valor(valor_largo))
guardar_tabla(riesgo_portafolio, '12_simulacion_portafolio.csv')
matplot(precios_sim[, 1:min(200, M)], type = 'l', lty = 1, col = adjustcolor('steelblue', 0.15),
main = 'Trayectorias simuladas del precio desde T0', xlab = 'Semanas', ylab = 'USD')
abline(h = P0, col = 'red', lwd = 2, lty = 2)
hist(valor_largo, breaks = 40, col = 'steelblue', xlab = 'USD',
main = paste0('Valor final simulado con posición larga (estrategia: ', posicion_T0, ')'))
abline(v = CAPITAL, col = 'red', lwd = 2, lty = 2)
writeLines(capture.output(sessionInfo()), file.path(DIR_RES, '13_sessionInfo.txt'))