English Español Deutsch 日本語
preview
Пример CNA (сетевого анализа причинно-следственных связей), SMOC (оптимального управления стохастической моделью) и теории игр Нэша с Глубоким обучением

Пример CNA (сетевого анализа причинно-следственных связей), SMOC (оптимального управления стохастической моделью) и теории игр Нэша с Глубоким обучением

MetaTrader 5Примеры | 11 апреля 2025, 15:16
332 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Введение

Мы пройдемся по процессу добавления Глубокого обучения к трем передовым торговым советникам. Эти три советника являются передовыми советниками, опубликованными в виде статей. 

Эти три статьи опубликованы здесь, на www.mql5.com

  1. Применение теории игр Нэша с фильтрацией НММ в трейдинге.
  2. Пример сетевого анализа причинно-следственных связей (CNA) и модели векторной авторегрессии для прогнозирования рыночных событий
  3. Пример стохастической оптимизации и оптимального управления

Если вы еще не читали об этих статьях, я очень кратко объясню о чем они, однако настоятельно рекомендую ознакомиться со статьями.



Теория игр Нэша

Равновесие Нэша считается одним из основных компонентов теории игр. 

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

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



Сетевой анализ причинно-следственных связей

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

Бот использует быстрый вывод причинно-следственных связей.

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



Стохастическая оптимизация

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

Стохастическое моделирование используется для описания систем с элементами случайности, таких как движение цен на фондовом рынке или очередь в ресторане. Оно основано на случайных переменных, распределениях вероятностей и стохастических процессах. Такие методы, как Монте-Карло (Monte Carlo) и цепи Маркова (Markov chains), позволяют моделировать эти процессы и прогнозировать их поведение.

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



Глубокое обучение

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

  1. Анализ данных: Модели глубокого обучения обрабатывают огромные объемы финансовых данных, включая историю цен, объемы торгов, экономические индикаторы и даже настроение в новостях.
  2. Распознавание паттернов: Эти модели позволяют выявлять сложные паттерны и взаимосвязи в рыночных данных, которые могут быть неочевидны для обычных трейдеров или традиционных методов анализа.
  3. Прогноз: Основываясь на выявляемых паттернах, модели глубокого обучения пытаются прогнозировать будущие движения рынка, ценовые тренды или оптимальные торговые стратегии.
  4. Автоматизированный трейдинг: Некоторые системы используют глубокое обучение для принятия автономных торговых решений, совершая сделки на основе прогнозов модели.
  5. Управление рисками: Глубокое обучение может быть использовано для более эффективной оценки торговых рисков и управления ими путем одновременного анализа множества факторов риска.
  6. Адаптивность: Эти модели могут постоянно обучаться и адаптироваться к изменяющимся рыночным условиям, что со временем потенциально повышает их производительность.
  7. Высокочастотный трейдинг: Глубокое обучение может быть особенно полезно в сценариях высокочастотной торговли, в которых решающее значение имеют решения, принимаемые за доли секунды.

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


Зачем нам нужен советник с Глубоким обучением?

Мы создадим модель Глубокого обучения на python, создав модель ONNX, и добавим эту модель к каждому из советников, а также сравним результаты с Глубоким обучением и без него.

Будут использоваться модели ONNX, поскольку их можно легко создать на python, и они являются связующим звеном между различными экосистемами, повышая гибкость и эффективность разработки и развертывания моделей. Python - это быстрый способ быстро превратить ONNX-модели или бэк-тестирование в стратегию. Для настоящей статьи будем использовать Python 3.11.9.

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



Python-скрипт

Будем использовать данный скрипт .py. Данный скрипт создает модель DL в ONNX, а также имеет показатели, позволяющие определить, корректно ли выполнена модель. У DL возникают проблемы при неправильном выполнении, потому что она может быть недостаточно обучена или переобучена, что приводит к неверным прогнозам.

#python 3.11.9, tensorflow==2.12.0, keras==2.12.0,  tf2onnx==1.16.0
# python libraries
import MetaTrader5 as mt5
import tensorflow as tf
import numpy as np
import pandas as pd
import tf2onnx
#import tensorflow as tf
#import tf2onnx
import keras

print(f"TensorFlow version: {tf.__version__}")
print(f"Keras version: {keras.__version__}")
print(f"tf2onnx version: {tf2onnx.__version__}")


# input parameters

inp_history_size = 120

sample_size = inp_history_size*3*20
symbol = "EURUSD"
optional = "D1_2024"
inp_model_name = str(symbol)+"_"+str(optional)+".onnx" 

if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

# we will save generated onnx-file near the our script to use as resource
from sys import argv
data_path=argv[0]
last_index=data_path.rfind("\\")+1
data_path=data_path[0:last_index]
print("data path to save onnx model",data_path)

# and save to MQL5\Files folder to use as file
terminal_info=mt5.terminal_info()
file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print("file path to save onnx model",file_path)

# set start and end dates for history data
from datetime import timedelta, datetime
#end_date = datetime.now()
end_date = datetime(2024, 1, 1, 0)
start_date = end_date - timedelta(days=inp_history_size*20)

# print start and end dates
print("data start date =",start_date)
print("data end date =",end_date)

# get rates
eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)

# create dataframe
df = pd.DataFrame(eurusd_rates)

# get close prices only
data = df.filter(['close']).values

# scale data
from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data = scaler.fit_transform(data)

# training size is 80% of the data
training_size = int(len(scaled_data)*0.80) 
print("Training_size:",training_size)
train_data_initial = scaled_data[0:training_size,:]
test_data_initial = scaled_data[training_size:,:1]

# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
    X, y = list(), list()
    for i in range(len(sequence)):
       # find the end of this pattern
       end_ix = i + n_steps
       # check if we are beyond the sequence
       if end_ix > len(sequence)-1:
          break
       # gather input and output parts of the pattern
       seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
       X.append(seq_x)
       y.append(seq_y)
    return np.array(X), np.array(y)

# split into samples
time_step = inp_history_size
x_train, y_train = split_sequence(train_data_initial, time_step)
x_test, y_test = split_sequence(test_data_initial, time_step)

# reshape input to be [samples, time steps, features] which is required for LSTM
x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1)
x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1)



# define model
from keras.models import Sequential
from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM
from keras.metrics import RootMeanSquaredError as rmse
from tensorflow.keras import callbacks
model = Sequential()
model.add(Conv1D(filters=256, kernel_size=2, strides=1, padding='same', activation='relu', input_shape=(inp_history_size,1)))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100, return_sequences = True))
model.add(Dropout(0.3))
model.add(LSTM(100, return_sequences = False))
model.add(Dropout(0.3))
model.add(Dense(units=1, activation = 'sigmoid'))
model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])

# Set up early stopping
early_stopping = callbacks.EarlyStopping(
    monitor='val_loss',
    patience=20,
    restore_best_weights=True,
)

# model training for 300 epochs
history = model.fit(x_train, y_train, epochs = 300 , validation_data = (x_test,y_test), batch_size=32, callbacks=[early_stopping], verbose=2)

# evaluate training data
train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = 32)
print(f"train_loss={train_loss:.3f}")
print(f"train_rmse={train_rmse:.3f}")

# evaluate testing data
test_loss, test_rmse = model.evaluate(x_test,y_test, batch_size = 32)
print(f"test_loss={test_loss:.3f}")
print(f"test_rmse={test_rmse:.3f}")

# Define a function that represents your model
@tf.function(input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)])
def model_function(x):
    return model(x)

output_path = data_path+inp_model_name
# Convert the model to ONNX
onnx_model, _ = tf2onnx.convert.from_function(
    model_function, 
    input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)],
    opset=13,
    output_path=output_path
)

print(f"Saved ONNX model to {output_path}")


# save model to ONNX
output_path = data_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

output_path = file_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

#prediction using testing data
test_predict = model.predict(x_test)
print(test_predict)
print("longitud total de la prediccion: ", len(test_predict))
print("longitud total del sample: ", sample_size)

plot_y_test = np.array(y_test).reshape(-1, 1)  # Selecciona solo el último elemento de cada muestra de prueba
plot_y_train = y_train.reshape(-1,1)
train_predict = model.predict(x_train)
#print(plot_y_test)

#calculate metrics
from sklearn import metrics
from sklearn.metrics import r2_score
#transform data to real values
value1=scaler.inverse_transform(plot_y_test)
#print(value1)
# Escala las predicciones inversas al transformarlas a la escala original
value2 = scaler.inverse_transform(test_predict.reshape(-1, 1))
#print(value2)
#calc score
score = np.sqrt(metrics.mean_squared_error(value1,value2))

print("RMSE         : {}".format(score))
print("MSE          :", metrics.mean_squared_error(value1,value2))
print("R2 score     :",metrics.r2_score(value1,value2))


#sumarize model
model.summary()

#Print error
value11=pd.DataFrame(value1)
value22=pd.DataFrame(value2)
#print(value11)
#print(value22)



value111=value11.iloc[:,:]
value222=value22.iloc[:,:]

print("longitud salida (tandas de 1 hora): ",len(value111) )
print("en horas son " + str((len(value111))*60*24)+ " minutos")
print("en horas son " + str(((len(value111)))*60*24/60)+ " horas")
print("en horas son " + str(((len(value111)))*60*24/60/24)+ " dias")


# Calculate error
error = value111 - value222

import matplotlib.pyplot as plt
# Plot error
plt.figure(figsize=(7, 6))
plt.scatter(range(len(error)), error, color='blue', label='Error')
plt.axhline(y=0, color='red', linestyle='--', linewidth=1)  # Línea horizontal en y=0
plt.title('Error de Predicción ' + str(symbol))
plt.xlabel('Índice de la muestra')
plt.ylabel('Error')
plt.legend()
plt.grid(True)
plt.savefig(str(symbol)+str(optional)+'.png') 

rmse_ = format(score)
mse_ = metrics.mean_squared_error(value1,value2)
r2_ = metrics.r2_score(value1,value2)

resultados= [rmse_,mse_,r2_]

# Abre un archivo en modo escritura
with open(str(symbol)+str(optional)+"results.txt", "w") as archivo:
    # Escribe cada resultado en una línea separada
    for resultado in resultados:
        archivo.write(str(resultado) + "\n")

# finish
mt5.shutdown()

#show iteration-rmse graph for training and validation
plt.figure(figsize = (7,10))
plt.plot(history.history['root_mean_squared_error'],label='Training RMSE',color='b')
plt.plot(history.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g')
plt.xlabel("Iteration")
plt.ylabel("RMSE")
plt.title("RMSE" + str(symbol))
plt.legend()
plt.savefig(str(symbol)+str(optional)+'1.png') 

#show iteration-loss graph for training and validation
plt.figure(figsize = (7,10))
plt.plot(history.history['loss'],label='Training Loss',color='b')
plt.plot(history.history['val_loss'],label='Validation-loss',color='g')
plt.xlabel("Iteration")
plt.ylabel("Loss")
plt.title("LOSS" + str(symbol))
plt.legend()
plt.savefig(str(symbol)+str(optional)+'2.png') 

#show actual vs predicted (training) graph
plt.figure(figsize=(7,10))
plt.plot(scaler.inverse_transform(plot_y_train),color = 'b', label = 'Original')
plt.plot(scaler.inverse_transform(train_predict),color='red', label = 'Predicted')
plt.title("Prediction Graph Using Training Data" + str(symbol))
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.savefig(str(symbol)+str(optional)+'3.png') 

#show actual vs predicted (testing) graph
plt.figure(figsize=(7,10))
plt.plot(scaler.inverse_transform(plot_y_test),color = 'b',  label = 'Original')
plt.plot(scaler.inverse_transform(test_predict),color='g', label = 'Predicted')
plt.title("Prediction Graph Using Testing Data" + str(symbol))
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.savefig(str(symbol)+str(optional)+'4.png') 


Результат, который выдает данный скрипт .py, выглядит примерно так:

177/177 - 51s - loss: 2.4565e-04 - root_mean_squared_error: 0.0157 - val_loss: 4.8860e-05 - val_root_mean_squared_error: 0.0070 - 51s/epoch - 291ms/step
177/177 [==============================] - 15s 82ms/step - loss: 1.0720e-04 - root_mean_squared_error: 0.0104
train_loss=0.000
train_rmse=0.010
42/42 [==============================] - 3s 74ms/step - loss: 4.4485e-05 - root_mean_squared_error: 0.0067
test_loss=0.000
test_rmse=0.007
Saved ONNX model to c:\XXXXXXXXXXXXXXX\EURUSD_D1_2024.onnx
saved model to c:\XXXXXXXXXXXXXXXX\EURUSD_D1_2024.onnx
saved model to C:\XXXXXXXXXX\MQL5\Files\EURUSD_D1_2024.onnx
42/42 [==============================] - 5s 80ms/step
[[0.40353602]
 [0.39568084]
 [0.39963558]
 ...
 [0.35914657]
 [0.3671721 ]
 [0.3618655 ]]
longitud total de la prediccion:  1320
longitud total del sample:  7200
177/177 [==============================] - 13s 72ms/step
RMSE         : 0.005155711558172936
MSE          : 2.6581361671078003e-05
R2 score     : 0.9915315181564703
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #
=================================================================
 conv1d (Conv1D)             (None, 120, 256)          768

 max_pooling1d (MaxPooling1D  (None, 60, 256)          0
 )

 lstm (LSTM)                 (None, 60, 100)           142800

 dropout (Dropout)           (None, 60, 100)           0

 lstm_1 (LSTM)               (None, 100)               80400

 dropout_1 (Dropout)         (None, 100)               0

 dense (Dense)               (None, 1)                 101

=================================================================
Total params: 224,069
Trainable params: 224,069
Non-trainable params: 0
________________________________________________________________

Данная модель может быть чрезмерно обучена из-за этих результатов, это может быть пределом корректного прогнозирования и она может быть переобучена:

RMSE         : 0.005155711558172936
MSE          : 2.6581361671078003e-05
R2 score     : 0.9915315181564703

Основными показателями являются эти три, R2, которые дают процент проверенных результатов с хорошими прогнозами, а RMSE, MSE - это ошибки. Если вы хотите узнать больше об этом, можете поискать в интернете.

Поскольку мы собираемся сделать это для того же символа (EURUSD), нам нужно создать только одну модель DL, и мы протестируем ее в отношении 2024 года.

Мы выбрали таймфрейм в 1 день, чтобы не использовать слишком много ресурсов наших компьютеров.

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

Графики должны выглядеть следующим образом:

Ошибки graph1 graph2 Процесс обучения Тестирование

Скрипт также сохраняет файл .txt, в котором предыдущие показатели сохраняются в том же порядке, в каком они отображаются в терминале.

Чтобы использовать данный скрипт сначала необходимо установить Python 3.11.9 и установить с ним эти библиотеки; установка пипсов:

tensorflow==2.12.0, keras==2.12.0,  tf2onnx==1.16.0, MetaTrader5, pandas, numpy, scikit-learn, tf2onnx, keras

Я настоятельно рекомендую использовать Visual Studio Code, который можно скачать из магазина Microsoft.

Вас могут попросить установить:

Microsoft Visual C++ 2015 - 2022 Redistributable

Я прикрепил скрипт .py, просто откройте его в VSC и запустите.

Если вы хотите что-либо изменить (другой символ, точку, откуда берутся данные, или период времени), измените только эти строки:

symbol = "EURUSD"
optional = "D1_2024"

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

eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)

В этой строке вы меняете таймфрейм, например, H1 (1-часовой таймфрейм).

Теперь у нас есть модель Глубокого обучения ONNX, мы можем добавить ее к советнику, который уже есть из других статей.

Я прикреплю модель DL ONNX (2024).


Что мы добавим к советникам?

Изменения в CNA

Это основное, я буду проходить этот процесс очень медленно.

во-первых, в начале, где вводные параметры, необходимо добавить эти строки:

#include <Trade\Trade.mqh>

#resource "/Files/EURUSD_D1.onnx" as uchar ExtModel[]

#define SAMPLE_SIZE 120

long     ExtHandle=INVALID_HANDLE;
int      ExtPredictedClass=-1;
datetime ExtNextBar=0;
datetime ExtNextDay=0;
float    ExtMin=0.0;
float    ExtMax=0.0;
CTrade   ExtTrade;
int dlsignal=-1;

//--- price movement prediction
#define PRICE_UP   0
#define PRICE_SAME 1
#define PRICE_DOWN 2

Не забудьте изменить эту строку в соответствии с созданной вами моделью ONNX (сохраните py-скрипты в MQL5/Files/ ... модель).

#resource "/Files/EURUSD_D1_2024.onnx" as uchar ExtModel[]

В OnInit() необходимо добавить следующее:

//--- create a model from static buffer
   ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
   if(ExtHandle==INVALID_HANDLE)
     {
      Print("OnnxCreateFromBuffer error ",GetLastError());
      return(INIT_FAILED);
     }

//--- since not all sizes defined in the input tensor we must set them explicitly
//--- first index - batch size, second index - series size, third index - number of series (only Close)
   const long input_shape[] = {1,SAMPLE_SIZE,1};
   if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      return(INIT_FAILED);
     }

//--- since not all sizes defined in the output tensor we must set them explicitly
//--- first index - batch size, must match the batch size of the input tensor
//--- second index - number of predicted prices (we only predict Close)
   const long output_shape[] = {1,1};
   if(!OnnxSetOutputShape(ExtHandle,0,output_shape))
     {
      Print("OnnxSetOutputShape error ",GetLastError());
      return(INIT_FAILED);
     }

Где? Везде... это не имеет значения (непосредственно перед возвратом функции).

В функцию OnDeinit также необходимо добавить следующее:

if(ExtHandle!=INVALID_HANDLE)
     {
      OnnxRelease(ExtHandle);
      ExtHandle=INVALID_HANDLE;
     }

Где? Где угодно, это освобождает модель ONNX.

В функцию OnTick необходимо добавить следующее:

void OnTick()
  {
  //--- check new day
   if(TimeCurrent()>=ExtNextDay)
     {
      GetMinMax();
      //--- set next day time
      ExtNextDay=TimeCurrent();
      ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
      ExtNextDay+=PeriodSeconds(PERIOD_D1);
     }

//--- check new bar
   if(TimeCurrent()<ExtNextBar)
      return;
//--- set next bar time
   ExtNextBar=TimeCurrent();
   ExtNextBar-=ExtNextBar%PeriodSeconds();
   ExtNextBar+=PeriodSeconds();
//--- check min and max
   float close=(float)iClose(_Symbol,_Period,0);
   if(ExtMin>close)
      ExtMin=close;
   if(ExtMax<close)
      ExtMax=close;
      
   PredictPrice();

Где? Вначале это важно, потому что результат работы функции PredictPrice() должен быть добавлен в исполнительную логику ордера.

В функции OnTick() также необходимо изменить функцию, которая выполняет ордеры, в данном случае (CNA_DL) мы изменим исходную строку:

int signal = GenerateSignal(symbol, prediction);

на следующую:

int signal = GenerateSignal(symbol, prediction, ExtPredictedClass);

ExtPredictedClass - это результат функции PredictedPrice(), которую мы добавили до этого.

Теперь нам также нужно изменить логику GenerateSignal(), чтобы она использовала ExtPredictedClass:

Нам придется изменить заголовок этой функции:

int GenerateSignal(string symbol, double prediction, int dlsgnl)

и добавим новую переменную в логику для создания ордеров, в данном случае, здесь:

с такой:

       bool buy_condition =  prediction >  0.00001 && rsi < 30 && trend_strong  && volatility_ok && fastMA > slowMA;
       bool sell_condition = prediction < -0.00001 && rsi > 70 && trend_strong && volatility_ok && fastMA < slowMA;

    на такую:

         bool buy_condition =  prediction >  0.00001 && rsi < 30 && trend_strong  && volatility_ok && fastMA > slowMA && dlsgnl==PRICE_UP;
         bool sell_condition = prediction < -0.00001 && rsi > 70 && trend_strong && volatility_ok && fastMA < slowMA && dlsgnl==PRICE_DOWN;

      Вот последняя часть, которую нам нужно изменить.

      Нам необходимо добавить эти функции в скрипт, где? Куда угодно, в конце, например.

      //+------------------------------------------------------------------+
      void PredictPrice(void)
        {
         static vectorf output_data(1);            // vector to get result
         static vectorf x_norm(SAMPLE_SIZE);       // vector for prices normalize
      
      //--- check for normalization possibility
         if(ExtMin>=ExtMax)
           {
            Print("ExtMin>=ExtMax");
            ExtPredictedClass=-1;
            return;
           }
      //--- request last bars
         if(!x_norm.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,1,SAMPLE_SIZE))
           {
            Print("CopyRates ",x_norm.Size());
            ExtPredictedClass=-1;
            return;
           }
         float last_close=x_norm[SAMPLE_SIZE-1];
      //--- normalize prices
         x_norm-=ExtMin;
         x_norm/=(ExtMax-ExtMin);
      //--- run the inference
         if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data))
           {
            Print("OnnxRun");
            ExtPredictedClass=-1;
            return;
           }
      //--- denormalize the price from the output value
         float predicted=output_data[0]*(ExtMax-ExtMin)+ExtMin;
      //--- classify predicted price movement
         float delta=last_close-predicted;
         if(fabs(delta)<=0.00001)
            ExtPredictedClass=PRICE_SAME;
         else
           {
            if(delta<0)
               ExtPredictedClass=PRICE_UP;
            else
               ExtPredictedClass=PRICE_DOWN;
           }
        }
        
        
        
        void GetMinMax(void)
        {
         vectorf close;
         close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,0,SAMPLE_SIZE);
         ExtMin=close.Min();
         ExtMax=close.Max();
        }

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


      Изменения в SMOC

      Все остается по-прежнему, и изменения такие же, как и предыдущие, за исключением следующего:

      Во входных параметрах вы не инициализируете dlsignal или не добавляете CTrade:

      #resource "/Files/EURUSD_D1.onnx" as uchar ExtModel[]
      
      #define SAMPLE_SIZE 120
      
      long     ExtHandle=INVALID_HANDLE;
      int      ExtPredictedClass=-1;
      datetime ExtNextBar=0;
      datetime ExtNextDay=0;
      float    ExtMin=0.0;
      float    ExtMax=0.0;
      
      //--- price movement prediction
      #define PRICE_UP   0
      #define PRICE_SAME 1
      #define PRICE_DOWN 2

      В функции OnTick() у нас будет следующее:

      void OnTick()
        {
      //--- check new day
         if(TimeCurrent()>=ExtNextDay)
           {
            GetMinMax();
            //--- set next day time
            ExtNextDay=TimeCurrent();
            ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
            ExtNextDay+=PeriodSeconds(PERIOD_D1);
           }
      
      //--- check new bar
         if(TimeCurrent()<ExtNextBar)
            return;
      //--- set next bar time
         ExtNextBar=TimeCurrent();
         ExtNextBar-=ExtNextBar%PeriodSeconds();
         ExtNextBar+=PeriodSeconds();
      //--- check min and max
         float close=(float)iClose(_Symbol,_Period,0);
         if(ExtMin>close)
            ExtMin=close;
         if(ExtMax<close)
            ExtMax=close;
      
         PredictPrice();
      
         static datetime lastBarTime = 0;
         datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, 0);
      
      // Only process on new bar
         if(currentBarTime == lastBarTime)
            return;
      
         lastBarTime = currentBarTime;
      
         double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_LAST);
         int decision = OptimalControl(currentPrice);
      
         string logMessage = StringFormat("New bar: Time=%s, Price=%f, Decision=%d",
                                          TimeToString(currentBarTime), currentPrice, decision);
         LogMessage(logMessage);
      
      // Manage open order if exists
         if(orderTicket != 0)
           {
            ManageOpenOrder(decision, ExtPredictedClass);
           }
      
         if(orderTicket == 0 && decision != 0 && IsTrendFavorable(decision) && IsLongTermTrendFavorable(decision) && (ExtPredictedClass==PRICE_DOWN || ExtPredictedClass==PRICE_UP))
           {
            ExecuteTrade(decision, ExtPredictedClass);
           }
        }

      В функцию ManageOpenOrder() внесем следующее изменение:

      void ManageOpenOrder(int decision, int dlsignal)
        {
         int barsOpen = iBars(Symbol(), PERIOD_CURRENT) - iBarShift(Symbol(), PERIOD_CURRENT, orderOpenTime);
         LogMessage(StringFormat("Bars open: %d for order ticket: %d", barsOpen, orderTicket));
      
         if(barsOpen >= maxBarsOpen ||
            (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && decision == -1 && dlsignal == PRICE_DOWN) ||
            (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && decision == 1 && dlsignal == PRICE_UP))
           {
            CloseOrder(orderTicket);
            orderTicket = 0;
            orderOpenTime = 0;
           }
         else
           {
            UpdateSLTP(orderTicket, decision);
           }
        }

      В функцию ExecuteTrade()

      void ExecuteTrade(int decision, int dlsignal)
        {
         MqlTradeRequest request = {};
         MqlTradeResult result = {};
         double price;
         int decision1;
         
         
      
         if(decision == 1 && dlsignal == PRICE_UP)
           {
            price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            request.type = ORDER_TYPE_BUY;
            decision1 = 1;
           }
         else
            if(decision == -1 && dlsignal == PRICE_DOWN)
              {
               price = SymbolInfoDouble(Symbol(), SYMBOL_BID);
               request.type = ORDER_TYPE_SELL;
              decision1 = -1;
              }
         double sl = CalculateDynamicSL(price, decision1);
         double tp = CalculateDynamicTP(price, decision1);
      
      
      
         double adjustedLotSize = AdjustLotSizeForDrawdown();
         request.volume = adjustedLotSize;
      
         request.action = TRADE_ACTION_DEAL;
         request.symbol = Symbol();
      //request.volume = lotSize;
      //request.type = (decision == 1 && dlsignal == PRICE_UP) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
         request.price = price;
         request.sl = sl;
         request.tp = tp;
         request.deviation = 10;
         request.magic = 123456; // Magic number to identify orders from this EA
         request.comment = (decision == 1) ? "Buy Order" : "Sell Order";
      
         if(OrderSend(request, result))
           {
            Print((decision1 == 1 ? "Buy" : "Sell"), " Order Executed Successfully. Ticket: ", result.order);
            orderOpenTime = iTime(Symbol(), PERIOD_CURRENT, 0);
            orderTicket = result.order;
           }
         else
           {
            Print("Error executing Order: ", GetLastError());
           }
        }

      А теперь изменения в скрипт Nash equity.


      Изменения в Nash Equity

      Все изменения одинаковы, за исключением функции OnTick(), которая будет похожа на эту:

      void OnTick()
        {
      //--- check new day
         if(TimeCurrent()>=ExtNextDay)
           {
            GetMinMax();
            //--- set next day time
            ExtNextDay=TimeCurrent();
            ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
            ExtNextDay+=PeriodSeconds(PERIOD_D1);
           }
      
      //--- check new bar
         if(TimeCurrent()<ExtNextBar)
            return;
      //--- set next bar time
         ExtNextBar=TimeCurrent();
         ExtNextBar-=ExtNextBar%PeriodSeconds();
         ExtNextBar+=PeriodSeconds();
      //--- check min and max
         float close=(float)iClose(_Symbol,_Period,0);
         if(ExtMin>close)
            ExtMin=close;
         if(ExtMax<close)
            ExtMax=close;
      
         PredictPrice();
      
         if(!IsNewBar())
            return; // Solo procesar en nueva barra
      
         MarketRegime hmmRegime = NOT_PRESENT;
         MarketRegime logLikelihoodRegime = NOT_PRESENT;
         DetectMarketRegime(hmmRegime, logLikelihoodRegime);
      
      // Calcular señales para cada estrategia
         CalculateStrategySignals(_Symbol, TimeCurrent(), hmmRegime, logLikelihoodRegime);
      
      // Verificar si la estrategia de Nash Equilibrium ha generado una señal
         if(strategies[3].enabled && strategies[3].signal != 0)
           {
            if(strategies[3].signal > 0 && ExtPredictedClass==PRICE_UP)
              {
               OpenBuyOrder(strategies[3].name);
              }
            else
               if(strategies[3].signal < 0 && ExtPredictedClass==PRICE_DOWN)
                 {
                  OpenSellOrder(strategies[3].name);
                 }
           }
      
      // Actualizar stops de seguimiento si es necesario
         if(UseTrailingStop)
           {
            UpdateTrailingStops();
           }
        }



      Результаты
      CNA_DL

      Настройки

      Settings CNA_DL

      CNA_DL graph

      CNA_dl backtesting

      По сравнению с оригинальным советником (CNA_Final_v4):

      CNA_Final_v4 Graph

      CNA_Final_v4 Backtesting


      Сравнение CNA с глубоким обучением и без него

      1. Прибыльность: Советник без глубокого обучения значительно более прибыльный, но он также сопряжен с большим риском с более высокими просадками.
      2. Риск: Советник с глубоким обучением имеет меньшие просадки и кажется более консервативным в своем подходе.
      3. Частота торговли: Советник без глубокого обучения торгует гораздо чаще (1292 сделки против 58).
      4. Коэффициент выигрыша: У советника с глубоким обучением коэффициент выигрыша немного выше, но оба они ниже 50%.
      5. Сдвиг стратегии: Советник без глубокого обучения отдает предпочтение коротким сделкам, в то время как советник с глубоким обучением сбалансирован.
      6. Согласованность: Советник с глубоким обучением, по-видимому, более совместим с небольшими сериями последовательных выигрышей / проигрышей.

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

      SMOC_DL

      Settings SMOC_DL

      Graph SMOC_DL

      SMOC_DL Backtesting


      А теперь SMOC с Глубоким обучением

      Backtesting SMOC

      SMOC graph

      Сравнение SMOC_DL и SMOC:

      1. Прибыльность: Стратегия 2 прибыльна (4,32), в то время как Стратегия 1 убыточна (-7,33).
      2. Доходность с поправкой на риск: Стратегия 2 имеет положительный коэффициент Шарпа (1,69), что указывает на более высокую доходность с поправкой на риск по сравнению с отрицательным коэффициентом Шарпа Стратегии 1 (-3,77).
      3. Коэффициент выигрыша: У Стратегии 2 коэффициент выигрыша выше (59.38%) по сравнению со Стратегией 1 (54%).
      4. Количество сделок: Стратегия 2 заключает больше сделок (64), чем Стратегия 1 (50), что потенциально указывает на большее количество выявленных возможностей.
      5. Коэффициент прибыльности: Стратегия 2 имеет коэффициент прибыли выше 1 (1,13), в то время как Стратегия 1 ниже 1 (0,74), что говорит о том, что Стратегия 2 более эффективна при получении прибыли по сравнению с убытками.
      6. Просадки: Обе стратегии имеют одинаково низкие просадки (0,01% от суммы счета), что свидетельствует о хорошем управлении рисками.
      7. Распределение сделок: Обе стратегии отдают предпочтение краткосрочным сделкам, но Стратегия 2 имеет более сбалансированное распределение между краткосрочными и долгосрочными сделками.

      В целом, Стратегия 2, по-видимому, превосходит в большинстве аспектов, включая прибыльность, доходность с поправкой на риск, коэффициент выигрышей и коэффициент прибыли. Она также берет больше сделок, что потенциально указывает на лучшее выявление возможностей. Обе стратегии демонстрируют хорошее управление рисками при низких просадках. Исходя из этих данных, Стратегия 2 была бы предпочтительным выбором из двух вариантов. Подходящим подходом к использованию DL было бы использование других таймфреймов.

      NASH_DL

      Nash's settings

      Nash Deep Learning

      Nash DL backtesting

      А теперь без Глубокого обучения:

      Nash

      Nash Backtesting

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

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


      Заключение

      В настоящем исследовании изучалась интеграция моделей Глубокого обучения (DL) в три передовых торговых советника (EAs): Сетевой анализ причинно-следственных связей (CNA), Стохастическая оптимизация и оптимальное управление (SMOC), а также Теория игр Нэша. Процесс включал в себя создание ONNX-моделей с использованием Python и включение их в существующие MQL5-скрипты.

      Результаты такой интеграции в рамках различных стратегий были неоднозначными:

      1. Что касается стратегии CNA, то версия DL показала более консервативную торговлю с более низкой доходностью, но и с меньшим риском по сравнению с версией без DL. Несмотря на то, что он был менее прибыльным, он продемонстрировал лучшую последовательность и более сбалансированный подход к торговле.
      2. В случае SMOC версия DL значительно превзошла свой аналог без DL. Она показала более высокую прибыльность, более высокие коэффициенты выигрыша и превосходную доходность с поправкой на риск, что говорит о том, что модель DL эффективно улучшила процесс принятия решений в рамках стратегии.
      3. Согласно подходу Теории игр Нэша, версия без DL в целом показала лучшие результаты с более высокой общей прибыльностью и доходностью с поправкой на риск, несмотря на более высокую максимальную просадку. Версия DL торговалась более консервативно, но в целом принесла меньшую прибыль.

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

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

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

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

      Прикрепленные файлы |
      EURUSD_D1_2024.onnx (884.41 KB)
      CNA_DL.mq5 (199.34 KB)
      CNA_Final_v4.mq5 (179.13 KB)
      SMOC_DL.mq5 (49.01 KB)
      SMOC_final.mq5 (40.29 KB)
      NASH_v4.mq5 (125.94 KB)
      NASH_v4_DL.mq5 (136.65 KB)
      Как реализовать автоматическую оптимизацию в советниках MQL5 Как реализовать автоматическую оптимизацию в советниках MQL5
      Пошаговое руководство по автоматической оптимизации на MQL5 для советников. Мы рассмотрим надежную логику оптимизации, лучшие практики по выбору параметров, а также как реконструировать стратегии с помощью бэк-тестирования. Кроме того, будут рассмотрены методы более высокого уровня, такие как пошаговая форвард-оптимизация, которые улучшат ваш подход к трейдингу.
      Возможности Мастера MQL5, которые вам нужно знать (Часть 39): Индекс относительной силы Возможности Мастера MQL5, которые вам нужно знать (Часть 39): Индекс относительной силы
      RSI — популярный импульсный осциллятор, который измеряет темп и размер недавнего изменения цены ценной бумаги для оценки ситуаций переоценки или недооценки ее цены. Эти знания о скорости и масштабах имеют ключевое значение для определения точек разворота. Мы применим этот осциллятор в работе очередного пользовательского класса сигналов и изучим особенности некоторых из его сигналов. Однако начнем мы с того, что подведем итог нашему разговору о полосах Боллинджера.
      Возможности Мастера MQL5, которые вам нужно знать (Часть 40): Parabolic SAR Возможности Мастера MQL5, которые вам нужно знать (Часть 40): Parabolic SAR
      Parabolic Stop-and-Reversal (SAR) - это индикатор точек подтверждения и окончания тренда. Поскольку он отстает в определении трендов, его основной целью было позиционирование скользящих стоп-лоссов по открытым позициям. Мы рассмотрим, можно ли его использовать в качестве сигнала советника с помощью пользовательских классов сигналов советников, собранных с помощью Мастера.
      Оптимизация Королевской Битвой — Battle Royale Optimizer (BRO) Оптимизация Королевской Битвой — Battle Royale Optimizer (BRO)
      В статье описан инновационный подход в области оптимизации, сочетающий пространственную конкуренцию решений с адаптивным сужением пространства поиска, делая Battle Royale Optimizer перспективным инструментом для финансового анализа.