English Deutsch 日本語
preview
Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR)

Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR)

MetaTrader 5Торговые системы |
121 0
Omega J Msigwa
Omega J Msigwa

Содержание


Что такое векторная авторегрессия (VAR)?

Это традиционный статистический инструмент прогнозирования временных рядов, используемый для исследования динамических взаимосвязей между несколькими переменными временного ряда. В отличие от одномерных авторегрессионных моделей, таких как ARIMA (мы обсуждали ее в предыдущей статье), которые прогнозируют только одну переменную на основе ее прошлых значений, VAR-модели исследуют взаимосвязь множества переменных.

Для этого они моделируют каждую переменную как функцию не только ее собственных прошлых значений, но и прошлых значений других переменных в системе. В этой статье мы рассмотрим основы векторной авторегрессии и ее применение в трейдинге.

Происхождение
Векторная авторегрессия была впервые представлена в 1960-х годах экономистом Клайвом Грейнджером. Его открытия заложили основы для понимания и моделирования динамических взаимодействий между экономическими факторами. VAR-модели получили широкое распространение в эконометрике и макроэкономике в 1970–1980-х годах.

Эта техника является многомерным вариантом авторегрессионных (AR) моделей. Традиционные AR модели, такие как ARIMA, анализируют зависимость одной переменной от ее лагов, модели VAR рассматривают несколько переменных одновременно. В VAR каждая переменная регрессируется по собственным лагам, а также по лагам других переменных системы.

В предыдущей статье мы обсуждали ARIMA и выяснили, что она не может учитывать несколько переменных в процессе обучения и прогнозирования. В этой статье мы рассмотрим VAR — модель, которую некоторые могут считать предшественницей ARIMA, поскольку она пытается решить проблему одномерного прогнозирования временных рядов.

Чтобы понять технику в основе модели, рассмотрим ее математическую структуру.


Математика в основе векторной авторегрессии

Главное отличие других авторегрессионных моделей (AR, ARMA, ARIMA) от VAR заключается в том, что первые модели являются однонаправленными (переменные-предикторы влияют на целевую переменную, но не наоборот), а VAR — двунаправленная.

Математически модель VAR(p) с 'p' лагами может быть представлена как:

где:

  • c — константный член (интерсепт) модели
  •  — коэффициент лагов Y до порядка p
  •  — значение временного ряда в момент времени t
  •  — ошибка в момент времени t

K-мерная модель VAR порядка P, обозначаемая как VAR(p), для k=2 примет вид:

Для VAR-модели существует несколько переменных временного ряда, которые влияют друг на друга. Модель представлена системой уравнений, по одному уравнению на каждую переменную. В матричной форме формула выглядит следующим образом:

Окончательное уравнение VAR будет таким:

Чтобы результаты VAR были корректными и надежными, необходимо соблюдение ряда предпосылок и требований.


Предположения, лежащие в основе модели VAR

  • Линейность
    Как видно из формулы, VAR является линейной моделью, поэтому все переменные, используемые в модели, должны быть линейными (т.е. выражаться как взвешенные суммы лагов).
  • Стационарность
    Все переменные должны быть стационарными, т.е. среднее, дисперсия и ковариация каждой характеристики временного ряда должны быть постоянными во времени. Все нестационарные признаки необходимо преобразовать в стационарные.
  • Отсутствие точной мультиколлинеарности между признаками
    Чтобы модель VAR работала правильно, объясняющие переменные не должны быть точной линейной комбинацией других. Это важно, поскольку позволяет избежать вырожденных матриц при оценивании методом OLS (т.е. матрица    должна быть обратимой). Лишние признаки необходимо удалить или использовать метод регуляризации.
  • Отсутствие автокорреляции остатков
    Предполагается, что остатки не коррелированы во времени и представляют собой белый шум. Автокорреляция искажает стандартные ошибки и делает статистические тесты недействительными.
  • Достаточное количество наблюдений
    VAR предполагает наличие достаточного объема данных для оценки параметров. На вход модели нужно подавать максимальное количество информации, чтобы она была эффективной.

Далее посмотрим, как реализовать модель на Python.


Реализация модели VAR на OHLC значениях в Python

Начнем с установки всех зависимостей Python. Файл requirements.txt находится в разделе вложений.

pip install -r requirements.txt

Импорт.

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

Импортируем значения Open, High, Low и Close из 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

Результаты.

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


Мы получили 10 000 баров с дневного таймфрейма. Это большой объем, этих данных должно быть достаточно для модели.

Поскольку модель будет использоваться на значениях OHLC, удалим все остальные столбцы.

ohlc_df = df.drop(columns=[
    "time",
    "tick_volume",
    "spread",
    "real_volume"
])

ohlc_df

Я решил использовать только OHLC, так как верю, что существует сильная взаимосвязь между этими переменными, которую модель может выявить. Не говоря уже о том, что эти четыре переменные являются фундаментальными характеристиками финансовых инструментов.

Поскольку модель предполагает стационарность признаков, а OHLC значения не являются стационарными, необходимо сделать их стационарными, дифференцируя каждое значение относительно предыдущего один раз.

stationary_df = pd.DataFrame()

for col in df.columns:
    stationary_df["Diff_"+col] = df[col].diff()

stationary_df.dropna(inplace=True)
stationary_df

Результаты.

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


При получении новых переменных, также можно проверить их стационарность.

from statsmodels.tsa.stattools import adfuller

for col in stationary_df.columns:
    
    result = adfuller(stationary_df[col])
    print(f'{col} p-value: {result[1]}')

Результаты.

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

Чтобы данные считались стационарными, значение P должно быть меньше 0,05 (<0,05). У нас p меньше 0,05, значит, данные подходят для работы.

Снова, согласно предположениям VAR, между признаками не должно быть идеальной мультиколлинеарности. Проверим это.

stationary_df.corr()

Результаты.

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


Матрица корреляций между признаками выглядит корректно. Можно также проверить средний модуль коэффициента корреляции всей матрицы, должно быть |p| < 0,8.

print("Mean absolute |p|:", np.abs(np.corrcoef(stationary_df, rowvar=False).mean()))

Результаты.

Mean absolute |p|: 0.5924538886295351

Выбор оптимального числа лагов

Судя по формуле, VAR использует прошлую информацию (лаги) для прогнозирования будущего, поэтому необходимо определить оптимальное количество лагов. Удобно, что функция VAR из библиотеки statsmodels позволяет выбрать число лагов по нескольким критериям:

Рассчитаем количество лагов для 30 дней (напомню, наши данные получены с дневного таймфрейма) и посмотрим на информационные критерии.

# Select optimal lag using AIC
lag_order = model.select_order(maxlags=30)

print(lag_order.summary())

Результаты.

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

В каждой строке показаны значения для разных порядков лагов. Значение, отмеченное звездочкой, является минимальным по данному критерию и указывает на "лучший" порядок согласно этому критерию.

Итак, вот какие значения мы получили:

  • AIC — лучший показатель при значения лага 29 (значение -46.56)
  • BIC — наилучшая модель получилась при кол-во лагов 17 (значение -46,29).
  • FPE — кол-во лагов 29 (-6.031e-21)
  • HQIC — лучший результат с 29 лагами (-46.44)

Чаще всего для выбора модели используются AIC и BIC. AIC, как правило, выбирает более сложные модели (с большим количеством лагов), а BIC наказывает за сложность, часто выбирая более простые модели. 

HQIC занимает промежуточное положение между AIC и BIC, а метод FPE анализирует ошибку прогнозирования.

Обучим модель со значением лагов, полученным по критерию AIC.

# Fit the model with selected lag
results = model.fit(lag_order.aic)

print(results.summary())

Результаты.

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

Как и другие традиционные статистические модели временных рядов, VAR предоставляет подробную сводку результатов работы модели и характеристик переменных. Такой анализ помогает лучше понять модель. Давайте кратко проанализируем результаты

Результаты регрессионного анализа
--------------------------------------------------------------------
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

  • Кол-во уравнений: 4 означает, что модель содержит 4 эндогенные переменные: diff_open, diff_high, diff_low, diff_close.
  • Nobs (количество использованных наблюдений) - поскольку мы выбрали критерий AIC, который использует 29 лагов, 29+1 признаков не были включены в процесс обучения (оценки), так как эти значения использовались в качестве начальных лагов.
  • AIC, BIC, HQIC и FPE все значения отрицательны (что нормально) и указывают на хорошее соответствие модели данным.
  • Log Likelihood (логарифмическая функция подобия) - высокое положительное значение указывает на хорошее качество обучения модели.

Результаты по каждому уравнению

Для каждой переменной (diff_open, diff_high, diff_low и diff_close) представлены:

  • Коэффициенты
    Они показывают влияние каждого лагированного признака (L1, L2 и т.д.) на текущее значение. Коэффициенты отражают направление и величину влияния соответствующих лагированных переменных на текущее значение. Положительный знак указывает на прямую связь, отрицательный — на обратную.
    Например:
    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
    Коэффициент -0,959329 означает, что увеличение diff_open на 1 единицу на вчерашнем шаге (lag-1) связано с уменьшением сегодняшнего diff_open на 0,959329 единицы при условии постоянства всех остальных переменных.
  • Стандартная ошибка

    Отражает точность оценок коэффициентов.
  • t-stat

    Показывает статистическую значимость. Чем больше абсолютное значение |t-stat|, тем значимее переменная. Большое абсолютное значение (например, |t|>2) указывает на статистическую значимость.

    Например, |(-87,867)| = 87,867 — очень большое значение, что говорит о высокой значимости влияния переменной diff_open на лаге 1 (не случайное).

  • prob

    Значение P, связанное с t-статистикой коэффициента. Оно показывает, имеет ли конкретная лагированная переменная значимое влияние на текущее значение зависимой переменной.

    Если prob ≤ 0,05, переменная считается статистически значимой.

Матрица остаточной корреляции

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

Показывает корреляцию между ошибками прогнозирования в разных уравнениях.

Высокая корреляция между diff_high/diff_close (0,77) и diff_low/diff_close (примерно 0,766) указывает на наличие общих необъясненных факторов, влияющих на эти пары переменных.


Прогнозирование вне выборки с использованием модели VAR

Как и в случае с моделью ARIMA, о которой мы говорили в предыдущей статье, прогнозирование вне выборки с помощью VAR является достаточно сложной задачей. В отличие от моделей машинного обучения, эти модели необходимо регулярно обновлять с учетом новой информации.

Для этого создадим функцию.

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

Чтобы получить прогноз, нужно использовать обученную модель и обновлять ее после каждого прогноза - для этого будем переназначать переменную самой себе.

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)

Результаты.

next forecasted:
    diff_open  diff_high  diff_low  diff_close
0    0.00435   0.003135  0.001032   -0.000655

Чтобы упростить процесс обучения и прогнозирования, обернем все в класс.

Файл 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

Теперь реализуем все это в виде торгового робота на Python.


Торговый робот на основе VAR

Используя класс, показанный выше, который позволяет обучить модель и прогнозировать следующие значения, внедрим эти предсказанные результаты в торговую стратегию.

В предыдущем примере мы использовали стационарные значения, полученные путем дифференцирования текущих значений относительно предыдущих. И хотя в целом этот подход работает, он не очень практичен для построения торговой стратегии.

Вместо этого определим разницу между открытием и максимумом свечи, чтобы измерить движение цены вверх от открытия, и разницу между открытием и минимумом, чтобы измерить движение цены вниз от открытия.

Эти два значения позволяют отслеживать движение свечи вверх и вниз, а предсказанные значения можно использовать для установки стоп-лосса и тейк-профита.

Изменим признаки, используемые в модели.

# 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"]
})

Полученные признаки после дифференцирования, скорее всего, являются стационарными (проверка не требуется на данном этапе).

В основном файле робота добавим процесс обучения и вывод прогнозных значений.

Файл 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()

Результаты.

[[0.00464001 0.00439884]]

У нас получилось два отдельных прогноза для high_open и open_low. Создадим теперь простую торговую стратегию на основе скользящей средней.

Файл 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()

Здесь традиционно используем торговые классы. Проверяем, есть ли на счете уже открытая позиция того же типа. Если нет, открываем позицию. Прогнозные значения high_open и open_low используем для установки тейк-профита и стоп-лосса для покупки и наоборот для продажи.

В качестве сигнала подтверждения используем индикатор простой скользящей средней с периодом 20. Если текущая цена закрытия выше скользящей средней — открываем покупку. В противном случае — продажу.

Результаты.


Заключительные мысли

Векторная авторегрессия — это классическая модель временных рядов с возможностью прогнозирования нескольких регрессионных признаков, чего большинство моделей машинного обучения не умеют.

Преимущества таких моделей:

  • Гибкая структура лагов, позволяющая использовать разные длины лагов для различных переменных.
  • Захватывает взаимозависимости (динамические связи между переменными).
  • Не требует строгой экзогенности, как в традиционных регрессионных моделях.

Однако есть и недостатки:

  • Чувствительность к стационарности признаков — модель работает лучше всего на стационарных данных.
  • Предполагает линейную зависимость между переменной и ее лагами, что не всегда реализуемо на финансовых рынках.
  • Возможна подгонка при большом количестве переменных и лагов.

Цель статьи — рассказать о модели VAR, ее особенностях и применении к торговым данным, поскольку информации по этому вопросу в сети немного. Призываю всех экспериментировать и дорабатывать эти идеи под свои потребности.

Всем удачи! 


Оставайтесь с нами и вносите свой вклад в разработку алгоритмов машинного обучения для языка MQL5 в этом GitHub-репозитории.


Таблица вложений

Имя файла Описание и назначение
Trade/* Торговые классы, аналогичные MQL5, на языке Python.
error_description.py  Содержит описания кодов ошибок MetaTrader 5.
forex-ts-forecasting-using-var.ipynb Блокнот Jupyter, содержащий примеры для обучения.
VAR.py Содержит класс, использующий модель VAR для обучения и построения прогнозов.
VAR-TradingRobot.py  Торговый робот, который открывает сделки на покупку и продажу на основе прогнозов, сделанных моделью VAR.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18371

Прикрепленные файлы |
Attachments.zip (54.26 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем
В продолжение нашей предыдущей статьи о паре индикаторов MACD и OBV, мы рассмотрим, как эту пару можно улучшить с помощью машинного обучения. MACD и OBV — это взаимодополняющая пара, отражающая тренд и объем. Наш подход к машинному обучению использует сверточную нейронную сеть (convolution neural network, CNN), которая задействует экспоненциальное ядро (Exponential kernel) для определения размеров своих ядер и каналов при настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA) Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)
Статья посвящена реализации алгоритма искусственного поискового роя (ASSA) на MQL5 в составе унифицированного тестового стенда. Разобраны три поведенческих правила движения, механизм сигнала и глобального табло, нормализация пространства, а также параметры stepRatio и Pc. Читатель получит готовую основу для интеграции ASSA, а также ответ на вопрос — насколько тактическая метафора оказалась удачным фундаментом для конкурентоспособности оптимизационного алгоритма.