Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)
Contenido
- ¿Qué es la autorregresión vectorial (VAR)?
- Matemáticas detrás del modelo de autorregresión vectorial (VAR)
- Supuestos subyacentes al modelo VAR
- Implementación del modelo VAR en valores OHLC en Python
- Pronóstico fuera de muestra utilizando VAR
- Creación del robot de trading basado en VAR
- Conclusiones finales
¿Qué es la autorregresión vectorial (VAR)?
Se trata de una herramienta tradicional de carácter estadístico para el pronóstico de series temporales, que se utiliza para investigar las relaciones dinámicas entre múltiples variables de series temporales. A diferencia de los modelos autorregresivos univariantes, como el ARIMA (analizado en el artículo anterior), que solo pronostican una única variable basándose en sus valores anteriores, los modelos VAR analizan la interrelación entre múltiples variables.
Lo consiguen modelando cada variable como una función no solo de sus valores anteriores, sino también de los valores pasados de otras variables del sistema. En este artículo, exploraremos los fundamentos de la autorregresión vectorial y su aplicación al trading.
Su origen
El modelo de autorregresión vectorial fue presentado por primera vez en la década de 1960 por el economista Clive Granger. Los importantes descubrimientos de Granger sentaron las bases para comprender y modelar las interacciones dinámicas que existen entre los factores económicos. Los modelos VAR adquirieron un impulso significativo en econometría y macroeconomía durante las décadas de 1970 y 1980.
Esta técnica es una extensión multivariante de los modelos de autorregresión (AR). Mientras que los modelos AR tradicionales, como ARIMA, analizan la relación entre una sola variable y sus valores rezagados, los modelos VAR consideran múltiples variables simultáneamente. En un modelo VAR, cada variable se modela en función de sus propios valores rezagados, así como sobre los valores rezagados de otras variables del sistema.

En el artículo anterior de esta serie, hablamos sobre ARIMA y descubrimos que no puede incorporar múltiples variables en su proceso de entrenamiento y pronóstico. En este artículo, hablaremos sobre VAR, que algunos podrían considerar predecesor de ARIMA, ya que busca abordar la limitación univariante del pronóstico de series temporales.
Para comprender esta sencilla técnica (modelo), analicemos sus bases matemáticas.
Matemáticas detrás del modelo de autorregresión vectorial (VAR)
La principal diferencia entre otros modelos autorregresivos (AR, ARMA y ARIMA) y el modelo VAR es que los primeros son unidireccionales, mientras que VAR es bidireccional.
Matemáticamente, un modelo VAR(p) con 'p' retardos se puede representar como:
![]()
Donde:
- c = El término constante (ordenada al origen) del modelo
-
= Coeficiente de retardos Y hasta el orden p. -
= El valor de la serie temporal en el instante t -
= El término de error en el instante t.
Un modelo VAR de K dimensiones de orden P, denotado como VAR(p), considerando k=2, la ecuación será la siguiente.

En el modelo VAR, tenemos múltiples variables de series temporales que se influyen mutuamente; se modela como un sistema de ecuaciones con una ecuación por cada variable de serie temporal. A continuación se muestra la fórmula en forma matricial.

La ecuación VAR final queda así:

Para garantizar la validez y la fiabilidad de los resultados obtenidos con el modelo VAR, deben cumplirse diversos supuestos y requisitos.
Supuestos subyacentes al modelo VAR
- Linealidad
Como hemos visto en su fórmula, el modelo VAR es esencialmente un modelo lineal, por lo que todas las variables utilizadas en este modelo deben ser lineales (es decir, expresadas como sumas ponderadas de valores rezagados). - Estacionariedad
Todas las variables utilizadas en este modelo deben ser estacionarias, es decir, la media, la varianza y la covarianza de cada característica de la serie temporal deben ser constantes a lo largo del tiempo. Tenemos que convertir todas las características no estacionarias en estacionarias si las hay en el conjunto de datos. - No debe existir multicolinealidad perfecta entre las variables
Para que el modelo VAR funcione eficazmente, ninguna variable explicativa puede ser una combinación lineal exacta de otras. Esto es importante porque ayuda a evitar matrices singulares en la estimación por mínimos cuadrados ordinarios (es decir,
debe ser invertible). Tenemos que eliminar las características redundantes o utilizar una técnica de regularización para resolver este problema. - No hay autocorrelación en los residuos
Se asume que los residuos no presentan correlación serial y se comportan como ruido blanco. La autocorrelación sesga los errores estándar e invalida las pruebas estadísticas. - Observaciones suficientes
El modelo VAR asume que ha recibido datos suficientes para la estimación de parámetros. Por lo tanto, necesitamos alimentar este modelo con la mayor cantidad de información posible para lograr la máxima eficiencia.
Ahora veamos cómo puedes implementar este modelo en el lenguaje de programación Python.
Implementación del modelo VAR en valores OHLC en Python
Empieza por instalar todas las dependencias de Python; el archivo requirements.txt se encuentra en la sección de archivos adjuntos.
pip install -r requirements.txt
Importaciones.
import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt import warnings # Suppress all warnings warnings.filterwarnings("ignore") sns.set_style("darkgrid")
Empezamos importando los valores OHLC (apertura, máximo, mínimo y cierre) desde MetaTrader 5.
symbol = "EURUSD" timeframe = mt5.TIMEFRAME_D1 if not mt5.symbol_select(symbol, True): print("Failed to select and add a symbol to the MarketWatch, Error = ",mt5.last_error) quit() rates = mt5.copy_rates_from_pos(symbol, timeframe, 1, 10000) df = pd.DataFrame(rates) # convert rates into a pandas dataframe df
Resultados.
| time | open | high | low | close | tick_volume | spread | real_volume | |
|---|---|---|---|---|---|---|---|---|
| 0 | 611280000 | 1.00780 | 1.01050 | 1.00630 | 1.00760 | 821 | 50 | 0 |
| 1 | 611366400 | 0.99620 | 1.00580 | 0.99100 | 0.99600 | 2941 | 50 | 0 |
| 2 | 611452800 | 0.99180 | 0.99440 | 0.98760 | 0.99190 | 1351 | 50 | 0 |
| 3 | 611539200 | 0.99330 | 0.99370 | 0.99310 | 0.99310 | 101 | 50 | 0 |
| 4 | 611798400 | 0.97360 | 0.97360 | 0.97320 | 0.97360 | 81 | 50 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9995 | 1748390400 | 1.13239 | 1.13453 | 1.12838 | 1.12910 | 153191 | 0 | 0 |
| 9996 | 1748476800 | 1.12918 | 1.13849 | 1.12105 | 1.13659 | 191948 | 0 | 0 |
| 9997 | 1748563200 | 1.13630 | 1.13901 | 1.13127 | 1.13470 | 186924 | 0 | 0 |
| 9998 | 1748822400 | 1.13435 | 1.14500 | 1.13412 | 1.14436 | 168697 | 0 | 0 |
| 9999 | 1748908800 | 1.14385 | 1.14549 | 1.13642 | 1.13708 | 147424 | 0 | 0 |
Obtuvimos 10000 barras del marco temporal diario, lo cual podemos considerar mucho porque, según el supuesto de los modelos, los datos deben ser suficientes.
Dado que queremos utilizar este modelo con valores OHLC, eliminemos las demás columnas.
ohlc_df = df.drop(columns=[ "time", "tick_volume", "spread", "real_volume" ]) ohlc_df
Opté por utilizar únicamente los valores OHLC porque creo que existe una fuerte relación entre estos valores que el modelo podría ayudarnos a detectar, sin mencionar que estas cuatro variables son las características fundamentales que podemos extraer de los instrumentos financieros.
Dado que este modelo asume estacionariedad en sus características, podemos decir que los valores OHLC no son estacionarios, así que hagámoslos estacionarios diferenciando cada valor con respecto a su(s) valor(es) anterior(es) una vez.
stationary_df = pd.DataFrame() for col in df.columns: stationary_df["Diff_"+col] = df[col].diff() stationary_df.dropna(inplace=True) stationary_df
Resultados.
| Diff_Open | Diff_High | Diff_Low | Diff_Close | |
|---|---|---|---|---|
| 1 | 0.00080 | 0.00180 | -0.01670 | -0.00950 |
| 2 | -0.00960 | -0.00840 | -0.01370 | -0.01880 |
| 3 | -0.01870 | -0.01930 | -0.00350 | -0.00190 |
| 4 | -0.00180 | -0.00210 | -0.00590 | -0.00870 |
| 5 | -0.00890 | -0.00310 | -0.01300 | -0.01200 |
| ... | ... | ... | ... | ... |
Opcionalmente, podemos comprobar la estacionariedad si no estamos seguros de las variables recién obtenidas.
from statsmodels.tsa.stattools import adfuller for col in stationary_df.columns: result = adfuller(stationary_df[col]) print(f'{col} p-value: {result[1]}')
Resultados.
Diff_Open p-value: 0.0 Diff_High p-value: 1.0471939301334604e-28 Diff_Low p-value: 1.1015540451195308e-23 Diff_Close p-value: 0.0
El valor p debe ser menor que 0,05 (<0,05) para que los datos se consideren estacionarios. Como podemos ver, el valor p es menor que 0,05, por lo que nuestros datos son buenos por ahora.
Nuevamente, según los supuestos de VAR, no debe existir multicolinealidad perfecta entre las características, así que asegurémonos de ello.
stationary_df.corr()
Resultados.
| Diff_Open | Diff_High | Diff_Low | Diff_Close | |
|---|---|---|---|---|
| Diff_Open | 1.000000 | 0.565829 | 0.563516 | 0.036347 |
| Diff_High | 0.565829 | 1.000000 | 0.452775 | 0.564026 |
| Diff_Low | 0.563516 | 0.452775 | 1.000000 | 0.557139 |
| Diff_Close | 0.036347 | 0.564026 | 0.557139 | 1.000000 |
La matriz de correlación entre las características parece correcta, incluso podemos comprobar el coeficiente de correlación absoluto medio de toda la matriz, asegurándonos de que |p| < 0,8.
print("Mean absolute |p|:", np.abs(np.corrcoef(stationary_df, rowvar=False).mean()))
Resultados.
Media absoluta |p|:0,5924538886295351 Seleccionar el número óptimo de retardos
Como hemos visto en la fórmula, el modelo VAR utiliza la información pasada (retrasos) para pronosticar el futuro; debemos saber qué número de retrasos utilizar produce el mejor resultado. Por suerte, el VAR que ofrece stats models cuenta con una función que nos ayuda a determinar este valor según una serie de criterios:
- AIC (Criterio de información de Akaike)
- BIC (Criterio de información bayesiano/de Schwarz)
- FPE (Error de predicción final)
- HQIC (Criterio de información de Hannan-Quinn)
# Select optimal lag using AIC lag_order = model.select_order(maxlags=30) print(lag_order.summary())
Resultados.
VAR Order Selection (* highlights the minimums) ================================================== AIC BIC FPE HQIC -------------------------------------------------- 0 -41.87 -41.87 6.537e-19 -41.87 1 -45.15 -45.14 2.457e-20 -45.15 2 -45.63 -45.60 1.530e-20 -45.62 3 -45.85 -45.81 1.225e-20 -45.84 4 -45.99 -45.94 1.065e-20 -45.97 5 -46.18 -46.12 8.805e-21 -46.16 6 -46.24 -46.17 8.256e-21 -46.22 7 -46.28 -46.20 7.951e-21 -46.25 8 -46.31 -46.22 7.708e-21 -46.28 9 -46.34 -46.24 7.471e-21 -46.31 10 -46.36 -46.24 7.368e-21 -46.32 11 -46.41 -46.28 6.979e-21 -46.37 12 -46.42 -46.28 6.890e-21 -46.38 13 -46.44 -46.28 6.806e-21 -46.38 14 -46.45 -46.28 6.730e-21 -46.39 15 -46.45 -46.28 6.697e-21 -46.39 16 -46.46 -46.28 6.628e-21 -46.40 17 -46.49 -46.29* 6.460e-21 -46.42 18 -46.50 -46.28 6.419e-21 -46.42 19 -46.50 -46.28 6.383e-21 -46.43 20 -46.50 -46.27 6.358e-21 -46.43 21 -46.51 -46.27 6.306e-21 -46.43 22 -46.52 -46.26 6.292e-21 -46.43 23 -46.53 -46.26 6.216e-21 -46.44 24 -46.53 -46.25 6.185e-21 -46.44 25 -46.54 -46.24 6.162e-21 -46.44 26 -46.54 -46.24 6.113e-21 -46.44 27 -46.55 -46.23 6.092e-21 -46.44 28 -46.55 -46.22 6.086e-21 -46.44 29 -46.56* -46.22 6.031e-21* -46.44* 30 -46.56 -46.21 6.033e-21 -46.44 --------------------------------------------------
Cada fila muestra valores para diferentes órdenes de retardo; un valor marcado con un asterisco es el valor mínimo para ese criterio, lo que indica el "mejor" orden de retardo según ese criterio.
Entonces, según este resumen del orden de retraso.
- Para AIC, el mejor modelo se encuentra en el retardo 29 (valor -46,56).
- Para BIC, el mejor modelo se encuentra en el retardo 17 (valor -46,29).
- Para FPE, el mejor modelo está en el retardo 29 (valor -6,031e-21).
- Para HQIC, el mejor modelo se encuentra en el retardo 29 (valor -46,44).
Habitualmente se utilizan los criterios AIC y BIC para seleccionar el modelo. En pocas palabras, el criterio AIC tiende a seleccionar modelos más complejos (con mayores retardos), mientras que el criterio BIC penaliza la complejidad con mayor severidad, seleccionando a menudo modelos más simples.
HQIC se sitúa en un punto intermedio entre AIC y BIC, mientras que FPE se centra en el error de predicción.
Por ahora, ajustemos el modelo con el valor de retardo según el criterio AIC.
# Fit the model with selected lag results = model.fit(lag_order.aic) print(results.summary())
Resultados.
Summary of Regression Results ================================== Model: VAR Method: OLS Date: Wed, 04, Jun, 2025 Time: 10:40:37 -------------------------------------------------------------------- No. of Equations: 4.00000 BIC: -46.2188 Nobs: 9970.00 HQIC: -46.4425 Log likelihood: 175968. FPE: 6.03280e-21 AIC: -46.5571 Det(Omega_mle): 5.75774e-21 -------------------------------------------------------------------- Results for equation diff_open ================================================================================= coefficient std. error t-stat prob --------------------------------------------------------------------------------- const -0.000002 0.000013 -0.115 0.908 L1.diff_open -0.959329 0.010918 -87.867 0.000 L1.diff_high 0.009878 0.004957 1.993 0.046 L1.diff_low 0.006869 0.005010 1.371 0.170 L1.diff_close 0.995718 0.004583 217.244 0.000 L2.diff_open -0.935345 0.015071 -62.062 0.000 L2.diff_high 0.007118 0.006749 1.055 0.292 L2.diff_low 0.022288 0.006819 3.268 0.001 L2.diff_close 0.939861 0.011863 79.226 0.000 L3.diff_open -0.906595 0.018115 -50.045 0.000 L3.diff_high 0.003072 0.007954 0.386 0.699 L3.diff_low 0.018535 0.008097 2.289 0.022 L3.diff_close 0.910898 0.015703 58.006 0.000 L4.diff_open -0.898803 0.020501 -43.841 0.000 L4.diff_high 0.003670 0.008912 0.412 0.681 L4.diff_low 0.015668 0.009103 1.721 0.085 L4.diff_close 0.886824 0.018628 47.606 0.000 L5.diff_open -0.867308 0.022560 -38.445 0.000 L5.diff_high 0.001318 0.009676 0.136 0.892 L5.diff_low -0.000027 0.009942 -0.003 0.998 L5.diff_close 0.884632 0.020996 42.133 0.000 ... ... ... L29.diff_open -0.005922 0.004617 -1.283 0.200 L29.diff_high 0.007026 0.004956 1.418 0.156 L29.diff_low 0.004387 0.005005 0.876 0.381 L29.diff_close 0.035169 0.010568 3.328 0.001 ================================================================================= Results for equation diff_high ================================================================================= coefficient std. error t-stat prob --------------------------------------------------------------------------------- const 0.000008 0.000048 0.165 0.869 L1.diff_open -0.010294 0.038697 -0.266 0.790 L1.diff_high -0.887555 0.017570 -50.515 0.000 L1.diff_low -0.020634 0.017757 -1.162 0.245 L1.diff_close 0.969305 0.016245 59.667 0.000 L2.diff_open 0.006028 0.053418 0.113 0.910 L2.diff_high -0.838250 0.023920 -35.043 0.000 L2.diff_low -0.057396 0.024169 -2.375 0.018 L2.diff_close 0.914246 0.042047 21.744 0.000 L3.diff_open -0.160354 0.064208 -2.497 0.013 L3.diff_high -0.807663 0.028191 -28.650 0.000 L3.diff_low -0.042960 0.028698 -1.497 0.134 L3.diff_close 0.869460 0.055659 15.621 0.000 L4.diff_open -0.168775 0.072664 -2.323 0.020 L4.diff_high -0.785399 0.031589 -24.863 0.000 L4.diff_low -0.054113 0.032265 -1.677 0.094 L4.diff_close 1.013851 0.066026 15.355 0.000 L5.diff_open -0.146275 0.079959 -1.829 0.067 L5.diff_high -0.746785 0.034295 -21.775 0.000 L5.diff_low -0.098885 0.035238 -2.806 0.005 L5.diff_close 1.012989 0.074419 13.612 0.000 ... ... ... L27.diff_open 0.020345 0.053645 0.379 0.705 L27.diff_high -0.153391 0.028136 -5.452 0.000 L27.diff_low -0.065690 0.028874 -2.275 0.023 L27.diff_close 0.251005 0.062004 4.048 0.000 L28.diff_open -0.005863 0.040235 -0.146 0.884 L28.diff_high -0.087603 0.023901 -3.665 0.000 L28.diff_low 0.008246 0.024229 0.340 0.734 L28.diff_close 0.134924 0.051754 2.607 0.009 L29.diff_open -0.000480 0.016364 -0.029 0.977 L29.diff_high -0.051136 0.017564 -2.911 0.004 L29.diff_low 0.035083 0.017741 1.977 0.048 L29.diff_close 0.054123 0.037457 1.445 0.148 ================================================================================= Results for equation diff_low ================================================================================= coefficient std. error t-stat prob --------------------------------------------------------------------------------- const 0.000005 0.000047 0.101 0.920 L1.diff_open 0.024212 0.038141 0.635 0.526 L1.diff_high -0.058570 0.017317 -3.382 0.001 L1.diff_low -0.904567 0.017501 -51.686 0.000 L1.diff_close 0.976598 0.016012 60.993 0.000 L2.diff_open 0.067049 0.052650 1.274 0.203 L2.diff_high -0.084679 0.023576 -3.592 0.000 L2.diff_low -0.866233 0.023822 -36.363 0.000 L2.diff_close 0.937652 0.041442 22.626 0.000 L3.diff_open 0.065284 0.063284 1.032 0.302 L3.diff_high -0.108128 0.027785 -3.892 0.000 L3.diff_low -0.791679 0.028285 -27.989 0.000 L3.diff_close 0.844047 0.054858 15.386 0.000 L4.diff_open 0.018366 0.071619 0.256 0.798 L4.diff_high -0.116216 0.031134 -3.733 0.000 L4.diff_low -0.747223 0.031801 -23.497 0.000 L4.diff_close 0.816060 0.065076 12.540 0.000 L5.diff_open -0.040872 0.078809 -0.519 0.604 L5.diff_high -0.110998 0.033802 -3.284 0.001 L5.diff_low -0.731241 0.034731 -21.054 0.000 L5.diff_close 0.832344 0.073348 11.348 0.000 ... ... ... L29.diff_open 0.024357 0.016128 1.510 0.131 L29.diff_high 0.026179 0.017312 1.512 0.130 L29.diff_low -0.072592 0.017486 -4.151 0.000 L29.diff_close 0.051738 0.036919 1.401 0.161 ================================================================================= Results for equation diff_close ================================================================================= coefficient std. error t-stat prob --------------------------------------------------------------------------------- const 0.000013 0.000071 0.185 0.853 L1.diff_open 0.037592 0.057827 0.650 0.516 L1.diff_high 0.007085 0.026256 0.270 0.787 L1.diff_low 0.011658 0.026535 0.439 0.660 L1.diff_close -0.020373 0.024276 -0.839 0.401 L2.diff_open 0.150341 0.079825 1.883 0.060 L2.diff_high -0.035345 0.035745 -0.989 0.323 L2.diff_low -0.041114 0.036117 -1.138 0.255 L2.diff_close -0.012920 0.062832 -0.206 0.837 L3.diff_open -0.000054 0.095949 -0.001 1.000 L3.diff_high -0.047439 0.042126 -1.126 0.260 L3.diff_low 0.028500 0.042884 0.665 0.506 L3.diff_close -0.113979 0.083173 -1.370 0.171 L4.diff_open -0.083562 0.108585 -0.770 0.442 L4.diff_high -0.083193 0.047204 -1.762 0.078 L4.diff_low 0.055907 0.048215 1.160 0.246 L4.diff_close 0.026375 0.098665 0.267 0.789 L5.diff_open -0.148622 0.119487 -1.244 0.214 L5.diff_high -0.065192 0.051248 -1.272 0.203 L5.diff_low 0.011819 0.052658 0.224 0.822 L5.diff_close 0.125327 0.111207 1.127 0.260 ... ... ... L29.diff_open 0.002852 0.024453 0.117 0.907 L29.diff_high -0.011652 0.026247 -0.444 0.657 L29.diff_low -0.004191 0.026511 -0.158 0.874 L29.diff_close 0.070689 0.055974 1.263 0.207 ================================================================================= Correlation matrix of residuals diff_open diff_high diff_low diff_close diff_open 1.000000 0.223818 0.241416 0.126479 diff_high 0.223818 1.000000 0.452061 0.770309 diff_low 0.241416 0.452061 1.000000 0.765777 diff_close 0.126479 0.770309 0.765777 1.000000
Al igual que otros modelos estadísticos/tradicionales de series temporales, el modelo VAR proporciona un resumen detallado del rendimiento del modelo y sus características. Este resumen nos ayuda a comprender el modelo en detalle; analicemos brevemente el resumen del modelo anterior.
-------------------------------------------------------------------- No. of Equations: 4.00000 BIC: -46.2188 Nobs: 9970.00 HQIC: -46.4425 Log likelihood: 175968. FPE: 6.03280e-21 AIC: -46.5571 Det(Omega_mle): 5.75774e-21
- Número de ecuaciones: 4, significa que el sistema (modelo) contiene 4 variables endógenas: diff_open, diff_high, diff_low y diff_close.
- Nobs (Número de observaciones utilizadas): Dado que elegimos el criterio AIC, que utiliza 29 retardos, las características 29+1 no se incluyeron en el proceso de entrenamiento (estimación), ya que esos valores se utilizaron anteriormente como retardos iniciales.
- AIC, BIC, HQIC y FPE: Todos estos valores son negativos (lo cual es normal), lo que constituye un buen indicio de un mejor ajuste.
- Verosimilitud logarítmica: Un valor positivo elevado indica que el modelo se ajusta bien a los datos.
Resultados para cada ecuación
Para cada variable (diff_open, diff_high, diff_low y diff_close), como puedes ver.
- Coeficientes
Estos valores representan el impacto de cada variable retrasada (L1, L2, etc.) sobre el valor actual. Cuanto más se acerque este valor a 1, mayor será el impacto positivo que tiene una variable sobre la variable de la ecuación actual, y viceversa.
Por ejemplo.
Results for equation diff_open ================================================================================= coefficient std. error t-stat prob --------------------------------------------------------------------------------- const -0.000002 0.000013 -0.115 0.908 L1.diff_open -0.959329 0.010918 -87.867 0.000
El coeficiente de -0,959329 significa aquí que un aumento de 1 unidad en el valor de «diff_open» de ayer (lag-1) se asocia con una disminución de 0,959329 unidades en el valor de «diff_open» de hoy, manteniendo constantes todas las demás variables. -
Std. Error
Estos valores reflejan la precisión de las estimaciones de los coeficientes. -
t-stat
Esto significa «significación estadística». Cuanto mayor sea el valor absoluto |t-stat| de esta métrica, más significativa será la variable. Un valor absoluto elevado (por ejemplo, |t| > 2) indica significación estadística.
El valor de |(-87,867)| = +87,867 es elevado, lo que indica que el efecto de la variable «diff_open» en el retraso 1 es muy significativo (no se debe al azar). -
prob
Esto representa el valor p asociado a la estadística t de cada coeficiente. Indica si una variable retrasada concreta tiene un efecto significativo sobre el valor actual de la variable dependiente.
Cuando el valor p es menor o igual que 0,05, una variable es estadísticamente significativa.
Matriz de correlación de los residuos
Correlation matrix of residuals diff_open diff_high diff_low diff_close diff_open 1.000000 0.223818 0.241416 0.126479 diff_high 0.223818 1.000000 0.452061 0.770309 diff_low 0.241416 0.452061 1.000000 0.765777 diff_close 0.126479 0.770309 0.765777 1.000000
Esto muestra las correlaciones entre los errores de predicción de las distintas ecuaciones.
La elevada correlación entre diff_high/diff_close (0,77) y diff_low/diff_close (aproximadamente: 0,766) sugiere que hay factores comunes no explicados que afectan a estos pares.
Previsión fuera de la muestra mediante VAR
Al igual que con el modelo ARIMA que se analizó en el artículo anterior, realizar previsiones de datos fuera de la muestra utilizando el modelo VAR resulta bastante complicado. A diferencia de los modelos de aprendizaje automático, estos modelos tradicionales deben actualizarse periódicamente con nueva información.
Creemos una función para esta tarea.
def forecast_next(model_res, symbol, timeframe): forecast = None # Get required lags for prediction rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, model_res.k_ar+1) # Get rates starting at the current bar to bars=lags used during training if rates is None or len(rates) < model_res.k_ar+1: print("Failed to get copy rates Error =", mt5.last_error()) return forecast, None # Prepare input data and make forecast input_data = pd.DataFrame(rates)[["open", "high", "low", "close"]].values stationary_input = np.diff(input_data, axis=0)[-model_res.k_ar:] # get the recent values equal to the number of lags used by the model try: forecast = model_res.forecast(stationary_input, steps=1) # predict the next price except Exception as e: print("Failed to forecast: ", str(e)) return forecast, None try: updated_data = np.vstack([model_res.endog, stationary_input[-1]]) # concatenate new/last datapoint to the data used during previous training updated_model = VAR(updated_data).fit(maxlags=model_res.k_ar) # Retrain the model with new data except Exception as e: print("Failed to update the model: ", str(e)) return forecast, None return forecast, updated_model
Para obtener una predicción, debemos partir del modelo entrenado inicialmente y actualizarlo tras cada predicción, reasignando una variable del modelo a sí misma.
res_model = results # Initial model forecast, res_model = forecast_next(model_res=res_model, symbol=symbol, timeframe=timeframe) forecast_df = pd.DataFrame(forecast, columns=stationary_df.columns) print("next forecasted:\n", forecast_df)
Resultados.
next forecasted: diff_open diff_high diff_low diff_close 0 0.00435 0.003135 0.001032 -0.000655
Podemos simplificar el proceso de entrenamiento y predicción agrupando todo esto en una clase.
Archivo: VAR.py
import pandas as pd import numpy as np import MetaTrader5 as mt5 from statsmodels.tsa.api import VAR class VARForecaster: def __init__(self, symbol: str, timeframe: int): self.symbol = symbol self.timeframe = timeframe self.model = None def train(self, start_bar: int=1, total_bars: int=10000, max_lags: int=30): """Trains the VAR model using the collected OHLC from given bars from MetaTrader5 start_bar: int: The recent bar according to copyrates_from_pos total_bars: int: Total number of bars to use for training max_lags: int: The maximum number of lags to use """ self.max_lags = max_lags if not mt5.symbol_select(self.symbol, True): print("Failed to select and add a symbol to the MarketWatch, Error = ",mt5.last_error()) quit() rates = mt5.copy_rates_from_pos(self.symbol, self.timeframe, start_bar, total_bars) if rates is None: print("Failed to get copy rates Error =", mt5.last_error()) return if total_bars < max_lags: print(f"Failed to train, max_lags: {max_lags} must be > total_bars: {total_bars}") return train_df = pd.DataFrame(rates) # convert rates into a pandas dataframe train_df = train_df[["open", "high", "low", "close"]] stationary_df = np.diff(train_df, axis=0) # Convert OHLC values into stationary ones by differenciating them self.model = VAR(stationary_df) # Select optimal lag using AIC lag_order = self.model.select_order(maxlags=self.max_lags) print(lag_order.summary()) # Fit the model with selected lag self.model_results = self.model.fit(lag_order.aic) print(self.model_results.summary()) def forecast_next(self): """Gets recent OHLC from MetaTrader5 and predicts the next differentiated prices Returns: np.array: predicted values """ forecast = None # Get required lags for prediction rates = mt5.copy_rates_from_pos(self.symbol, self.timeframe, 0, self.model_results.k_ar+1) # Get rates starting at the current bar to bars=lags used during training if rates is None or len(rates) < self.model_results.k_ar+1: print("Failed to get copy rates Error =", mt5.last_error()) return forecast # Prepare input data and make forecast input_data = pd.DataFrame(rates)[["open", "high", "low", "close"]] stationary_input = np.diff(input_data, axis=0)[-self.model_results.k_ar:] # get the recent values equal to the number of lags used by the model try: forecast = self.model_results.forecast(stationary_input, steps=1) # predict the next price except Exception as e: print("Failed to forecast: ", str(e)) return forecast try: updated_data = np.vstack([self.model_results.endog, stationary_input[-1]]) # concatenate new/last datapoint to the data used during previous training updated_model = VAR(updated_data).fit(maxlags=self.model_results.k_ar) # Retrain the model with new data except Exception as e: print("Failed to update the model: ", str(e)) return forecast self.model = updated_model return forecast
Vamos a plasmar todo esto en un robot de trading basado en Python.
Creación del robot de trading basado en el VAR
Teniendo en cuenta la clase anterior, que puede ayudarnos a entrenar el modelo y a realizar predicciones sobre el próximo valor, incorporemos estas predicciones a una estrategia de trading.
En primer lugar, en el ejemplo anterior utilizamos valores estacionarios obtenidos al restar los valores anteriores de los actuales. Aunque este enfoque funciona, no resulta muy práctico a la hora de diseñar una estrategia de trading.
En su lugar, calculamos la diferencia entre el precio de apertura y el máximo, para saber cuánto sube el precio con respecto al precio de apertura, y la diferencia entre el precio de apertura y el mínimo, para saber cuánto baja el precio con respecto al precio de apertura.
Al obtener estos dos valores —uno para seguir el movimiento alcista de la vela y otro para seguir su movimiento bajista—, podemos utilizar los resultados previstos para establecer los valores de stop loss y take profit.
Modifiquemos las características que utilizamos en nuestro modelo.
# Prepare input data and make forecast input_data = pd.DataFrame(rates)[["open", "high", "low", "close"]] stationary_input = pd.DataFrame({ "high_open": input_data["high"] - input_data["open"], "open_low": input_data["open"] - input_data["low"] })
Es muy probable que las características resultantes obtenidas mediante diferenciación sean estacionarias (no es necesario comprobarlo por ahora).
Dentro del archivo principal del robot, programemos el proceso de entrenamiento e imprimamos los valores previstos.
Nombre del archivo: VAR-TradingRobot.py
import MetaTrader5 as mt5 import schedule import time from VAR import VARForecaster symbol = "EURUSD" timeframe = mt5.TIMEFRAME_D1 mt5_path = r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader 5\terminal64.exe" # replace this with a desired MT5 path if not mt5.initialize(mt5_path): # initialize MetaTrader5 print("Failed to initialize MetaTrader5, error =", mt5.last_error()) quit() var_model = VARForecaster(symbol=symbol, timeframe=timeframe) var_model.train(start_bar=1, total_bars=10000, max_lags=30) # Train the VAR Model def get_next_forecast(): print(var_model.forecast_next()) schedule.every(1).minutes.do(get_next_forecast) while True: schedule.run_pending() time.sleep(60) else: mt5.shutdown()
Resultados.
[[0.00464001 0.00439884]]
Ahora que ya tenemos estas dos predicciones independientes para «high_open» y «open_low», vamos a crear una estrategia de trading sencilla basada en una media móvil simple.
Nombre del archivo: VAR-TradingRobot.py
import MetaTrader5 as mt5 import schedule import time import ta from VAR import VARForecaster from Trade.Trade import CTrade from Trade.SymbolInfo import CSymbolInfo from Trade.PositionInfo import CPositionInfo import numpy as np import pandas as pd symbol = "EURUSD" timeframe = mt5.TIMEFRAME_D1 mt5_path = r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader 5\terminal64.exe" # replace this with a desired MT5 path if not mt5.initialize(mt5_path): # initialize MetaTrader5 print("Failed to initialize MetaTrader5, error =", mt5.last_error()) quit() var_model = VARForecaster(symbol=symbol, timeframe=timeframe) var_model.train(start_bar=1, total_bars=10000, max_lags=30) # Train the VAR Model # Initlalize the trade classes MAGICNUMBER = 5062025 SLIPPAGE = 100 m_trade = CTrade(magic_number=MAGICNUMBER, filling_type_symbol=symbol, deviation_points=SLIPPAGE) m_symbol = CSymbolInfo(symbol=symbol) m_position = CPositionInfo() ##################################################### def pos_exists(pos_type: int, magic: int, symbol: str) -> bool: """Checks whether a position exists given a magic number, symbol, and the position type Returns: bool: True if a position is found otherwise False """ if mt5.positions_total() < 1: # no positions whatsoever return False positions = mt5.positions_get() for position in positions: if m_position.select_position(position): if m_position.magic() == magic and m_position.symbol() == symbol and m_position.position_type()==pos_type: return True return False def trading_strategy(): forecasts_arr = var_model.forecast_next().flatten() high_open = forecasts_arr[0] open_low = forecasts_arr[1] print(f"high_open: ",high_open, " open_low: ",open_low) # Get the information about the market rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 50) # Get the last 50 bars information rates_df = pd.DataFrame(rates) if rates is None: print("Failed to get copy rates Error =", mt5.last_error()) return sma_buffer = ta.trend.sma_indicator(close=rates_df["close"], window=20) m_symbol.refresh_rates() if rates_df["close"].iloc[-1] > sma_buffer.iloc[-1]: # current closing price is above sma20 if pos_exists(pos_type=mt5.POSITION_TYPE_BUY, symbol=symbol, magic=MAGICNUMBER) is False: # If a buy position doesn't exist m_trade.buy(volume=m_symbol.lots_min(), symbol=symbol, price=m_symbol.ask(), sl=m_symbol.ask()-open_low, tp=m_symbol.ask()+high_open) else: # if the closing price is below the moving average if pos_exists(pos_type=mt5.POSITION_TYPE_SELL, symbol=symbol, magic=MAGICNUMBER) is False: # If a buy position doesn't exist m_trade.sell(volume=m_symbol.lots_min(), symbol=symbol, price=m_symbol.bid(), sl=m_symbol.bid()+high_open, tp=m_symbol.bid()-open_low) schedule.every(1).minutes.do(trading_strategy) while True: schedule.run_pending() time.sleep(60) else: mt5.shutdown()
Utilizando las clases de trading que se describen en este artículo, comprobamos si existe una posición del mismo tipo; si no es así, abrimos una posición de ese mismo tipo. Los valores previstos «high_open» y «open_low» se utilizan para fijar el «take profit» y el «stop loss» de una operación, respectivamente, en el caso de una operación de compra, y a la inversa en el caso de una operación de venta.
Se utiliza un indicador de media móvil simple con un período (ventana) = 20 como señal de confirmación. Si el precio de cierre actual está por encima del indicador de media móvil, abrimos una operación de compra; en caso contrario, hacemos lo contrario y abrimos una operación de venta.
Resultados.

Conclusiones finales
La autorregresión vectorial es un modelo clásico razonablemente sólido, capaz de realizar previsiones de múltiples variables de regresión, una capacidad de la que carecen la mayoría de los modelos de aprendizaje automático.
Estos modelos ofrecen una serie de ventajas, como por ejemplo:
- Una estructura flexible de retardos, que permite emplear distintos órdenes de retardo para diferentes variables,
- Captan las interdependencias (relaciones dinámicas entre variables),
- No se basan en ningún supuesto estricto de exogeneidad, algo que suele darse en los modelos de regresión tradicionales.
Entre sus inconvenientes se encuentran:
- Su sensibilidad al requisito de estacionariedad, ya que solo funcionan de forma óptima con datos estacionarios.
- Parten de la hipótesis de que existe una relación lineal entre una variable y sus valores retrasados, algo que no siempre es viable en los mercados financieros.
- También pueden sufrir un sobreajuste cuando se les proporcionan muchas variables y retrasos.
El objetivo de este artículo era dar a conocer este modelo, su composición y cómo puede aplicarse a los datos de operaciones, ya que me pareció un tema menos documentado en Internet. No dudes en adaptar la idea a tus necesidades.
Saludos cordiales.
Tabla de archivos adjuntos
| Nombre del archivo | Descripción y uso |
|---|---|
| Trade/* | Clases de comercio similares a las de MQL5 en lenguaje Python. |
| error_description.py | Contiene descripciones de los códigos de error de MetaTrader 5. |
| forex-ts-forecasting-using-var.ipynb | Un cuaderno de Jupyter que contiene ejemplos con fines didácticos. |
| VAR.py | Contiene la clase que utiliza el modelo VAR para el entrenamiento y la elaboración de previsiones. |
| VAR-TradingRobot.py | Un robot de trading que abre operaciones de compra y venta basándose en las predicciones del modelo VAR. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18371
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Del básico al intermedio: Clases (II)
De novato a experto: Períodos del mercado Forex
Particularidades del trabajo con números del tipo double en MQL4
Simulación de mercado: Position View (XV)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso