English Español Deutsch 日本語
preview
Машинное обучение и Data Science (Часть 27): Сверточные нейросети (CNN) в торговых роботах для MetaTrader 5

Машинное обучение и Data Science (Часть 27): Сверточные нейросети (CNN) в торговых роботах для MetaTrader 5

MetaTrader 5Эксперты | 27 января 2025, 13:31
144 2
Omega J Msigwa
Omega J Msigwa

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

Джеффри Хинтон

Содержание


Для понимания статьи необходимо базовое представление о языке Python, искусственных нейросетяхмашинном обучении и ONNX в MQL5.


Что такое сверточные нейронные сети (CNNS)?

Сверточные нейронные сети (CNN) — это класс алгоритмов глубокого обучения, специально разработанных для обработки структурированных данных в виде сетки, таких как изображения, аудиоспектрограммы и данные временных рядов. Они особенно хорошо подходят для задач с визуальными данными, поскольку могут автоматически и адаптивно изучать пространственные иерархии признаков на основе входных данных.

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

Ключевые компоненты сверточных нейросетей: сверточные слои, функции активации, слои пулинга, полносвязанные слои и Dropout-слои. Чтобы лучше разобраться в сверточных нейронных сетях, давайте разберем каждый компонент и выясним, в чем суть.

Иллюстрация сверточной нейронной сети


Сверточные слои

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

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

from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))

Фильтры/Ядра

Фильтры (или ядра) представляют собой небольшие обучаемые квадратные матрицы (обычно размером 3x3, 5x5 и т. д.), которые скользят по входным данным для поиска локальных закономерностей.

Как они работают

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

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

Рассмотрим простой фильтр размером 3x3 и входное изображение размером 5x5 пикселей. Фильтр скользит по изображению, вычисляя операцию свертки для создания карты признаков.

conv-net kernel



Шаг

Также в сверточном слое используется шаг. Это размер шага, на который фильтр перемещается по входным данным. Параметр определяет, насколько сильно смещается фильтр на каждом этапе процесса свертки.

Как они работают

При шаге 1 фильтр перемещается на одну единицу за раз. Так мы получаем сильно перекрывающуюся и подробную карту объектов. Это позволяет получить более крупную выходную карту признаков.

При шаге 2 или более, получается менее подробной, но и меньшая по размеру выходная карта объектов. Это эффективно уменьшает пространственные размеры выхода и выполняет даунсэмплинг входных данных.

Например, если у вас есть фильтр размером 3x3 и входное изображение 5x5 с шагом 1, фильтр будет перемещаться на один пиксель за раз, создавая выходную карту признаков размером 3x3. При шаге 2 фильтр будет перемещаться на два пикселя за раз, создавая выходную карту признаков размером 2x2.


Дополнение (Padding)

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

Типы дополнения

Согласно библиотеки Keras, существуют три типа дополнения (учитывается регистр): (с учетом регистра)

  1. valid — дополнение не применяется.
  2. same — входные данные дополняются таким образом, чтобы размер выхода совпадал с размером входа при шаге 1.
  3. causal — используется для временных данных, чтобы выход на временном шаге 𝑡 не зависел от будущих входов.

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

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

Например, рассмотрим фильтр 3x3 и входное изображение 5x5. При valid-дополнении (без дополнения) выходная карта признаков будет размером 3x3. При same к границе входных данных можно добавить рамку из нулей, увеличив размеры до 7x7. В этом случае выходная карта признаков будет 5x5, сохраняя размеры входного изображения.

Ниже представлен Python-код для сверточного слоя.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )


Функции активации

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

Функции активации применяются к каждому элементу данных, чтобы ввести в модель нелинейность. Наиболее часто используемые функции активации в сверточных нейронных сетях (CNN) включают ReLU (Rectified Linear Unit), Sigmoid и TanH.


Слои подвыборки (Pooling Layers)

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

Как они работают

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

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

Python

from tensorflow.keras.layers import Conv1D, MaxPooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
MaxPooling1D(pool_size=2)

Этот слой берет максимальное значение из каждого двухэлементного окна.

max pooling

Дополнительная информация.

Average pooling берет среднее значение из набора значений внутри области фильтра. Используется реже, чем Max pooling.

Python

from tensorflow.keras.layers import Conv1D, AveragePooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(AveragePooling1D(pool_size=2))
AveragePooling1D(pool_size=2)

Этот слой берет среднее значение из каждого двухэлементного окна.

average pooling

Дополнительная информация.

Для чего используется 1D-сверточный слой?

Для сверточных нейронных сетей (CNN) существуют слои Conv1D, Conv2D и Conv3D. 1D-сверточный слой (Conv1D) предназначен для обработки одномерных данных, таких как последовательности или временные ряды. Это делает его подходящим для задач, связанных с временными рядами или последовательными данными. Другие типы сверточных слоев (Conv2D и Conv3D) слишком сложны для такого рода задач.


Полносвязные слои

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

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()

Слой Flatten преобразует карту признаков (например, из слоя подвыборки) в одномерный вектор, чтобы его можно было передать в полносвязные (dense) слои.

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

Слой dense в сети CNN




Слои Dropout

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

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

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()


Можно ли использовать сверточные нейросети (CNN) для программ финансового анализа и торговли?

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

Однако использование таких сетей для анализа табличных данных, таких как финансовая информация, может показаться необычным по сравнению с другими типами нейронных сетей, такими как полносвязные сети (Feed Forward Neural Networks, FFNN), рекуррентные сети (RNN), долгосрочная кратковременная память (LSTM) или модули с управляемым рекуррентным доступом (GRU). Тем не менее, существуют веские основания и преимущества применения сетей CNN и в данном контексте.


1. CNN хорошо извлекают локальные паттерны из данных

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

2. Обучение иерархическим признакам

Многочисленные слои в CNN позволяют изучать сложные иерархические признаки. На начальных слоях выявляются простые паттерны, а более глубокие слои объединяют их в более сложные и абстрактные представления. Это может быть полезным для анализа сложных структур в финансовых данных.

3. Устойчивость к шуму и избыточным данным

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

4. Эффективная работа с многомерными временными рядами

Финансовые данные часто представляют собой многомерные временные ряды, включающие, например, цены акций и объемы торгов. CNN способны эффективно находят взаимодействия и зависимости между этими временными рядами, что делает их подходящими для прогнозирования многомерных временных рядов.

5. Вычислительная эффективность для данных высокой размерности

Финансовые данные могут иметь высокую размерность (много признаков). Благодаря совместному использованию весов и локальной связности CNN являются более эффективными с точки зрения вычислений, чем полносвязные сети, и способны масштабироваться для работы с данными высокой размерности.

6. Сквозное обучение

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

7. Свертки полезны для определенных типов данных

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

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


Создание сверточной нейросети (CNN) на Python

Этот процесс включает несколько шагов:

  1. Сбор данных
  2. Подготовка данных для модели CNN
  3. Обучение модели CNN
  4. Сохранение модели CNN в формате ONNX

1. Сбор данных

Для создания модели мы будем использовать данные временных рядов, которые были рассмотрены в предыдущих статьях.

Набор данных для прогнозирования временных рядов

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

Python code

open_price = df['TARGET_OPEN']
close_price = df['TARGET_CLOSE']

# making the target variable

target_var = []
for i in range(len(open_price)):
    if close_price[i] > open_price[i]: # if the price closed above where it opened
        target_var.append(1) # bullish signal
    else:
        target_var.append(0) # bearish signal

        
new_df = pd.DataFrame({
    'OPEN': df['OPEN'],
    'HIGH': df['HIGH'],
    'LOW': df['LOW'],
    'CLOSE': df['CLOSE'],
    'TARGET_VAR': target_var
})

print(new_df.shape)

После подготовки целевой переменной на основе TARGET_OPEN и TARGET_CLOSE (соответственно значения открытия и закрытия, смещенные на один бар вперед), мы создали упрощенный набор данных под названием new_df. Этот набор включает в себя только 4 независимые переменные (OPEN, HIGH, LOW) и одну зависимую переменную под названием TARGET_VAR.


2. Подготовка данных для модели CNN

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

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

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

# Example data preprocessing function

def preprocess_data(df, window_size):
    X, y = [], []
    for i in range(len(df) - window_size):
        X.append(df.iloc[i:i+window_size, :-1].values)
        y.append(df.iloc[i+window_size, -1])
    return np.array(X), np.array(y)


window_size = 10


X, y = preprocess_data(new_df, window_size)
print(f"x_shape = {X.shape}\ny_shape = {y.shape}")

Результаты

x_shape = (990, 10, 4)
y_shape = (990,)

Поскольку наши данные собирались ежедневно, размер окна 10 указывает на то, что мы будем обучать модель CNN для понимания закономерностей в течение 10 дней.


Затем нам необходимо разделить данные на обучающую и тестовую выборки.

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)

# Standardize the data
scaler = StandardScaler()

X_train = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape)
X_test = scaler.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape)

print(f"x_train\n{X_train.shape}\nx_test\n{X_test.shape}\n\ny_train {y_train.shape} y_test {y_test.shape}")

Результаты

x_train
(792, 10, 4)
x_test
(198, 10, 4)

y_train (792,) y_test (198,)


Наконец, для задачи классификации необходимо применить one-hot-кодирование к целевой переменной

from tensorflow.keras.utils import to_categorical

y_train_encoded = to_categorical(y_train)
y_test_encoded = to_categorical(y_test)

print(f"One hot encoded\n\ny_train {y_train_encoded.shape}\ny_test {y_test_encoded.shape}")

Результаты

One hot encoded

y_train (792, 2)
y_test (198, 2)


3. Обучение модели CNN

Именно здесь выполняется большая часть работы.

# Defining the CNN model

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )

model.add(MaxPooling1D(pool_size=2))
model.add(Flatten()) 
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='softmax'))  # For binary classification (buy/sell signal)

model.summary()

# Compiling the model

optimizer = Adam(learning_rate=0.001)

model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])

# Training the model

early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

history = model.fit(X_train, y_train_encoded, epochs=100, batch_size=16, validation_split=0.2, callbacks=[early_stopping])

plt.figure(figsize=(7.5, 6))
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()
plt.savefig("training loss cuver-cnn-clf.png")
plt.show()

Результаты

Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv1d_2 (Conv1D)               │ (None, 5, 64)          │           832 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling1d_2 (MaxPooling1D)  │ (None, 2, 64)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_2 (Flatten)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 100)            │        12,900 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 100)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_5 (Dense)                 │ (None, 2)              │           202 │
└─────────────────────────────────┴────────────────────────┴───────────────┘

Обучение остановилось на 34-й эпохе.

40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5105 - loss: 0.6875 - val_accuracy: 0.4843 - val_loss: 0.6955
Epoch 32/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5099 - loss: 0.6888 - val_accuracy: 0.5283 - val_loss: 0.6933
Epoch 33/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.4636 - loss: 0.6933 - val_accuracy: 0.5283 - val_loss: 0.6926
Epoch 34/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5070 - loss: 0.6876 - val_accuracy: 0.5346 - val_loss: 0.6963

Кривая потерь при обучении и проверке CNN

Модель оказалась точной примерно в 57% случаев при прогнозировании вне выборки.

y_pred = model.predict(X_test) 

classes_in_y = np.unique(y)
y_pred_binary = classes_in_y[np.argmax(y_pred, axis=1)]

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred_binary)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.savefig("confusion-matrix CNN")  # Display the heatmap


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

Результаты

7/7 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
Classification Report
               precision    recall  f1-score   support

           0       0.53      0.24      0.33        88
           1       0.58      0.83      0.68       110

    accuracy                           0.57       198
   macro avg       0.55      0.53      0.50       198
weighted avg       0.55      0.57      0.52       198

Наша модель CNN достаточно хороша для создания советника. Но прежде чем мы начнем писать код советника, нам нужно сохранить обученную нами модель в формате ONNX.


4. Сохранение модели CNN в формате ONNX.

Процесс довольно прост. Нам нужно сохранить модель сверточной нейросети в формате .onnx, а параметры метода масштабирования — в бинарных файлах.

import tf2onnx

onnx_file_name = "cnn.EURUSD.D1.onnx"

spec = (tf.TensorSpec((None, window_size, X_train.shape[2]), tf.float16, name="input"),)
model.output_names = ['outputs']

onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13)

# Save the ONNX model to a file
with open(onnx_file_name, "wb") as f:
    f.write(onnx_model.SerializeToString())
    
    
# Save the mean and scale parameters to binary files
scaler.mean_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_mean.bin")
scaler.scale_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_scale.bin")


Создание торгового робота на основе сверточной нейросети (CNN)

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

MQL5 | ConvNet EA.mq5

#resource "\\Files\\cnn.EURUSD.D1.onnx" as uchar onnx_model[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_scale.bin" as double scaler_stddev[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_mean.bin" as double scaler_mean[]

Далее нужно их инициализировать: масштабатор и модель ONNX.

#include <MALE5\Convolutioal Neural Networks(CNNs)\Convnet.mqh>
#include <MALE5\preprocessing.mqh>

CConvNet cnn;
StandardizationScaler scaler;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

vector classes_in_y = {0,1}; //we have to assign the classes manually | it is essential that their order is preserved as they can be seen in python code, HINT: They are usually in ascending order
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (!cnn.Init(onnx_model)) //Initialize the ONNX model
     return INIT_FAILED;
     
//--- Initializing the scaler with values loaded from binary files 

   scaler = new StandardizationScaler(scaler_mean, scaler_stddev); //load the scaler
      
   return(INIT_SUCCEEDED);
  }

Отлично, этого достаточно, чтобы модель заработала. Теперь давайте создадим функцию для извлечения данных, аналогично тому, как мы использовали независимые переменные во время обучения. Мы использовали четыре переменные: значения OHLC (Open, High, Low, Close) с предыдущего закрытого бара и до 10 баров назад, что и было размером окна. Также используем все тот же дневной таймфрейм.

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

input ENUM_TIMEFRAMES timeframe = PERIOD_D1;
input int magic_number = 1945;
input int slippage = 50;
matrix GetXVars(int bars, int start_bar=1)
 {
   vector open(bars), 
          high(bars),
          low(bars), 
          close(bars);

//--- Getting OHLC values
   
   open.CopyRates(Symbol(), timeframe, COPY_RATES_OPEN, start_bar, bars);
   high.CopyRates(Symbol(), timeframe, COPY_RATES_HIGH, start_bar, bars);
   low.CopyRates(Symbol(), timeframe, COPY_RATES_LOW, start_bar, bars);
   close.CopyRates(Symbol(), timeframe, COPY_RATES_CLOSE, start_bar, bars);
   
//---

   matrix data(bars, 4); //we have 10 inputs from cnn | this value is fixed
   
//--- adding the features into a data matrix
   
   data.Col(open, 0);
   data.Col(high, 1);
   data.Col(low, 2);
   data.Col(close, 3);
   
   return data;
 }

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

void OnTick()
  {
//---
   
   if (NewBar()) //Trade at the opening of a new candle
    {
      matrix input_data_matrix = GetXVars(cnn_data_window); //get data for the past 10 days(default)
      input_data_matrix = scaler.transform(input_data_matrix); //applying StandardSCaler to the input data
      
      int signal = cnn.predict_bin(input_data_matrix, classes_in_y); //getting trade signal from the RNN model
     
      Comment("Signal==",signal);
     
   //---
     
      MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==1) //if the signal is bullish
       {
          if (!PosExists(POSITION_TYPE_BUY)) //There are no buy positions
           {
             if (!m_trade.Buy(lotsize, Symbol(), ticks.ask, 0, 0)) //Open a buy trade
               printf("Failed to open a buy position err=%d",GetLastError());

             ClosePosition(POSITION_TYPE_SELL); //close opposite trade
           }
       }
      else if (signal==0) //Bearish signal
        {
          if (!PosExists(POSITION_TYPE_SELL)) //There are no Sell positions
            if (!m_trade.Sell(lotsize, Symbol(), ticks.bid, 0, 0)) //open a sell trade
               printf("Failed to open a sell position err=%d",GetLastError());
               
               ClosePosition(POSITION_TYPE_BUY); 
        }
      else //There was an error
        return;
    }
  }

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

Я протестировал эту стратегию на том же символе, на котором она была обучена:EURUSD, в течение десяти лет. С 01.01.2014 по 27.05.2024 на 4-часовом графике в режиме по ценам открытия на каждом баре.

Конфигурация тестера для советнинка convNet EA

Результаты тестирования стратегии оказались впечатляющими.

Отчет тестера стратегии ConvNet EA

График тестирования советника ConvNet EA

Советник на базе сверточной нейросети делал точные прогнозы в 58% случаев, в результате чего он получил чистую прибыль в размере 503 долларов.


Заключение

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

Как видно из отчета тестера стратегий, основанный на нейросети CNN советник показал хорошие результаты, сделав точные предсказания. Я уверен, что многие традиционные модели, предназначенные для табличных данных, такие как линейная регрессия, машины опорных векторов, наивный байесовский классификатор и другие, не смогли бы достичь такой предсказательной точности, особенно учитывая, что CNN-модели использовали всего 4 независимых переменные (OHLC). На моем опыте, немногие модели могут достичь таких результатов, имея всего несколько переменных.

С наилучшими пожеланиями.


За развитием этой модели машинного обучения и других из этой серии статей можно следить в моем репозиторий на GitHub.

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

Название файла
Тип файла Описание и использование
 ConvNet EA.mq5
 Expert          Торговый робот для загрузки модели сверточной нейросети в ONNX и тестирования полученной торговой стратегии в MetaTrader 5.
 cnn.EURUSD.D1.onnx
 ONNX  Модель сверточной сети CNN в формате ONNX.
 cnn.EURUSD.D1.standard_scaler_mean.bin  
 cnn.EURUSD.D1.standard_scaler_scale.bin
 Бинарные файлы   Бинарные файлы для стандартного масштабатора
 preprocessing.mqh
 Включаемый файл
 Библиотека стандартного масштабатора
 ConvNet.mqh
 Включаемый файл   Библиотека для загрузки и развертывания модели CNN в формате ONNX 
 cnn-for-trading-applications-tutorial.ipynb
 Скрипт Python/Блокнот Jupyter   Содержит весь код Python, обсуждаемый в этой статье. 


Источники и ссылки


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

Прикрепленные файлы |
Attachments.zip (132.38 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
npats2007
npats2007 | 29 янв. 2025 в 16:47

5.5 сделок в год по H4 это мало. Очень мало.

Ryan L Johnson
Ryan L Johnson | 29 янв. 2025 в 21:31

Это самое краткое объяснение применения CNN в трейдинге, которое я когда-либо видел, причем по большей части на простом языке и с диаграммами. Затем оно сведено к коду на MQL5. Обратите внимание, что код не ограничивается таймфреймом H4.

Отличная работа, сэр! 👍

Биржевые данные без посредников: подключаем MetaTrader 5 к MOEX через ISS API Биржевые данные без посредников: подключаем MetaTrader 5 к MOEX через ISS API
В статье предложено решение для интеграции MetaTrader 5 с веб-сервисом MOEX ISS. Прилагаются утилиты для автоматической генерации исходных кодов на основе справочника API и индекса основных элементов сервиса.
Разработка системы репликации (Часть 60): Нажатие кнопки воспроизведения в сервисе (I) Разработка системы репликации (Часть 60): Нажатие кнопки воспроизведения в сервисе (I)
Мы уже давно работаем только над индикаторами, но теперь пришло время снова заставить сервис работать, и мы видим, как строится график на основе предоставленных данных. Однако, поскольку не всё так просто, придется быть внимательным, чтобы понять то, что ждет нас впереди.
Возможности Мастера MQL5, которые вам нужно знать (Часть 29): Темпы обучения и многослойные перцептроны Возможности Мастера MQL5, которые вам нужно знать (Часть 29): Темпы обучения и многослойные перцептроны
Мы завершаем рассмотрение чувствительности темпа обучения к производительности советников изучением адаптируемых темпов обучения. Темпы должны быть настроены для каждого параметра в слое в процессе обучения, поэтому нам необходимо оценить потенциальные преимущества по сравнению с ожидаемыми потерями производительности.
Машинное обучение и Data Science (Часть 26): Решающая битва в прогнозирование временных рядов — LSTM против GRU Машинное обучение и Data Science (Часть 26): Решающая битва в прогнозирование временных рядов — LSTM против GRU
В предыдущей статье мы рассмотрели простую рекуррентную нейронную сеть, которая, несмотря на свою неспособность понимать долгосрочные зависимости в данных, смогла разработать прибыльную стратегию. В этой статье мы поговорим о долгой кратковременной памяти (Long-Short Term Memoryю LSTM) и об управляемом рекуррентном блоке (Gated Recurrent Unit, GRU). Эти два подхода были разработаны для преодоления недостатков простой рекуррентной нейронной сети.