English Русский Deutsch 日本語 Português
preview
Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)

Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)

MetaTrader 5Sistemas comerciales |
58 0
Omega J Msigwa
Omega J Msigwa

Contenido


¿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:

Calculemos los retrasos para 30 días (dado que los datos se obtuvieron a partir de un intervalo de tiempo diario) y observemos los criterios de información.

# 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.

Resultados de la regresión
--------------------------------------------------------------------
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. 


Sigue nuestras actualizaciones y contribuye al desarrollo de algoritmos de aprendizaje automático para el lenguaje MQL5 en este repositorio de GitHub.


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

Archivos adjuntos |
Attachments.zip (54.26 KB)
Del básico al intermedio: Clases (II) Del básico al intermedio: Clases (II)
Este artículo busca ser lo más didáctico posible, porque el tema que abordaremos ya suele generar bastante confusión. Por eso, mi querido lector, intenta poner en práctica lo que se explica aquí. Ante cualquier duda, no dejes de comentar, pues comprender los destructores no es precisamente una de las tareas más sencillas.
De novato a experto: Períodos del mercado Forex De novato a experto: Períodos del mercado Forex
Cada período de mercado tiene un principio y un final, y cada uno cierra con un precio que define su sentimiento, al igual que cualquier sesión de velas japonesas. Comprender estos puntos de referencia nos permite evaluar el estado de ánimo predominante del mercado, revelando si las fuerzas alcistas o bajistas tienen el control. En este análisis, damos un paso importante al desarrollar una nueva función dentro del Sincronizador de Períodos de Mercado, que visualiza las sesiones del mercado Forex para facilitar la toma de decisiones de trading más informadas. Esta herramienta puede ser especialmente útil para identificar, en tiempo real, qué bando —alcistas o bajistas— domina la sesión. Exploremos este concepto y descubramos las conclusiones que ofrece.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Simulación de mercado: Position View (XV) Simulación de mercado: Position View (XV)
En este artículo intentaré explicar, de la forma más sencilla posible, cómo puedes utilizar el intercambio de mensajes entre aplicaciones. El objetivo es que puedas desarrollar algo funcional de la manera más simple y eficaz, siempre que sea posible. No sé si lograré transmitir la idea que sustenta este concepto, pues no resulta fácil de asimilar para quien lo encuentra por primera vez. También aprovecharé para mostrarte cómo modificar el sistema de repetición/simulador, de modo que puedas depurar un Asesor Experto o cualquier otro código que estés desarrollando. Todo ello de una forma igualmente sencilla y directa.