English Deutsch 日本語
preview
Совместное использование PSAR, Хейкин-Аши и глубокого обучения для трейдинга

Совместное использование PSAR, Хейкин-Аши и глубокого обучения для трейдинга

MetaTrader 5Примеры | 22 апреля 2025, 12:21
200 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Введение

Поиск рубежа может стать разницей между успехом и неудачей. Имея в распоряжении так много данных, трейдеры для получения преимущества все чаще обращаются к таким передовым технологиям, как глубокое обучение. Этот путь предполагает сочетание традиционных инструментов технического анализа с современными моделями искусственного интеллекта (ИИ) для быстрой оценки и адаптации к рыночным условиям. Чтобы раскрыть этот потенциал, мы разработали скрипт на Python, предназначенный для сочетания прогностических возможностей глубокого обучения с проверенными торговыми индикаторами. Данный скрипт не предназначен для создания полноценной торговой системы; скорее, это инструмент для быстрого экспериментирования - способ быстро проверить, есть ли у торговой стратегии потенциал быть прибыльной. Параллельно, в скрипте MetaTrader 5 используются аналогичные принципы, чтобы перенести этот подход в реальную торговую среду, сокращая разрыв между теоретическим бэк-тестированием и реальным применением.


Создание модели глубокого обучения ONNX

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

Наш скрипт начинается с импорта всех необходимых инструментов - представьте, что вы собираете ингредиенты для сложного рецепта. Мы используем такие библиотеки, как TensorFlow и Keras, которые являются основой многих современных приложений ИИ. Мы также используем MetaTrader 5 для получения реальных данных с рынка Форекс.

import MetaTrader5 as mt5
import tensorflow as tf
import numpy as np
import pandas as pd
import tf2onnx
import keras

Далее настраиваем некоторые важные параметры. Рассматриваем исторические данные за 120 дней по паре EURUSD. Почему 120? Это активная точка, которая дает нам достаточно информации, чтобы выявить паттерны, не перегружая нашу модель.

Затем мы используем MetaTrader 5 для получения этих данных. Представьте это так, как будто вы отправляете путешественника во времени в прошлое, чтобы собрать информацию о ценах за последние 120 дней. Как только у нас появятся эти данные, мы аккуратно приводим их в формат, понятный нашему искусственному интеллекту.

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*3)

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

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

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

Мы не хотим, чтобы наша модель запоминала данные, поэтому разделили ее на две части: обучающие данные (80%) и тестовые данные (20%). Это все равно, что дать нашему студенту - искусственному интеллекту, учебник для изучения (обучающие данные), а затем отдельный тест для проверки его знаний (тестовые данные).

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

Теперь начинается самое интересное - создание нашей модели ИИ! Мы используем комбинацию различных методов ИИ:

  1.  Сверточные нейронные сети (CNN): Они отлично распознают паттерны в последовательностях, точно так же, как наши глаза распознают закономерности в изображениях.
  2.  Сети с длинной кратковременной памятью (LSTM): Они отлично подходят для понимания долгосрочных зависимостей в данных и идеально подходят для определения тенденций с течением времени.
  3.  Плотные слои: Они помогают нашей модели делать окончательные прогнозы.

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

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

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

После тренировки тестируем работу нашей модели. Чтобы увидеть, насколько наши прогнозы близки к фактическим ценам, мы используем такие показатели, как RMSE (среднеквадратичная ошибка). Это похоже на оценку результатов тестирования нашего ИИ.

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

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

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

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

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

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


Скрипт на Python для тестирования стратегии

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

Скрипт запускается с подключения к MetaTrader 5 для получения исторических ценовых данных по паре EUR/USD. В нем используются почасовые данные для фиксации краткосрочных движений и применяются свечи Хейкин-Аши, чтобы сгладить шум на рынке. Это помогает более четко определять тенденции, что крайне важно при быстром тестировании стратегии. Затем он накладывается на несколько хорошо известных технических индикаторов, таких как PSAR, SMA, RSI и ATR. Эти индикаторы служат для быстрой проверки рыночных условий, предоставляя модельный контекст для принятия решений о покупке и продаже.

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import onnxruntime as ort
from sklearn.preprocessing import MinMaxScaler
from ta.trend import PSARIndicator, SMAIndicator
from ta.momentum import RSIIndicator
from ta.volatility import AverageTrueRange
import matplotlib.pyplot as plt

# Inicializar conexión con MetaTrader5
if not mt5.initialize():
    print("Inicialización fallida")
    mt5.shutdown()

def get_historical_data(symbol, timeframe, start_date, end_date):
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time', inplace=True)
    return df

def calculate_heikin_ashi(df):
    ha_close = (df['open'] + df['high'] + df['low'] + df['close']) / 4
    ha_open = (df['open'].shift(1) + df['close'].shift(1)) / 2
    ha_high = df[['high', 'open', 'close']].max(axis=1)
    ha_low = df[['low', 'open', 'close']].min(axis=1)
    
    df['ha_close'] = ha_close
    df['ha_open'] = ha_open
    df['ha_high'] = ha_high
    df['ha_low'] = ha_low
    return df

def add_indicators(df):
    # Calcular Heikin Ashi
    df = calculate_heikin_ashi(df)
    
    # PSAR con parámetros ajustados
    psar = PSARIndicator(df['high'], df['low'], df['close'], step=0.02, max_step=0.2)
    df['psar'] = psar.psar()
    
    # Añadir SMA
    sma = SMAIndicator(df['close'], window=50)
    df['sma'] = sma.sma_indicator()
    
    # Añadir RSI
    rsi = RSIIndicator(df['close'], window=14)
    df['rsi'] = rsi.rsi()
    
    # Añadir ATR para medir volatilidad
    atr = AverageTrueRange(df['high'], df['low'], df['close'], window=14)
    df['atr'] = atr.average_true_range()
    
    # Añadir filtro de tendencia simple
    df['trend'] = np.where(df['close'] > df['sma'], 1, -1)
    
    return df

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

def prepare_data(df, window_size=120):
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_data = scaler.fit_transform(df[['close']])
    
    X = []
    for i in range(window_size, len(scaled_data)):
        X.append(scaled_data[i-window_size:i])
    
    return np.array(X), scaler

def load_onnx_model(model_path):
    return ort.InferenceSession(model_path)

def predict_with_onnx(model, input_data):
    input_name = model.get_inputs()[0].name
    output_name = model.get_outputs()[0].name
    return model.run([output_name], {input_name: input_data})[0]

def backtest(df, model, scaler, window_size=120, initial_balance=10000):
    scaled_data = scaler.transform(df[['close']])
    
    predictions = []
    for i in range(window_size, len(scaled_data)):
        X = scaled_data[i-window_size:i].reshape(1, window_size, 1)
        pred = predict_with_onnx(model, X.astype(np.float32))
        predictions.append(scaler.inverse_transform(pred.reshape(-1, 1))[0, 0])
    
    df['predictions'] = [np.nan]*window_size + predictions
    
    # Nueva lógica de trading
    df['position'] = 0
    long_condition = (
        (df['close'] > df['predictions']) & 
        (df['close'] > df['psar']) & 
        (df['close'] > df['sma']) & 
        (df['rsi'] < 60) &  # Condición RSI menos estricta
        (df['ha_close'] > df['ha_open']) &
        (df['ha_close'].shift(1) > df['ha_open'].shift(1)) &
        (df['trend'] == 1)  # Solo comprar en tendencia alcista
    )
    short_condition = (
        (df['close'] < df['predictions']) & 
        (df['close'] < df['psar']) & 
        (df['close'] < df['sma']) & 
        (df['rsi'] > 40) &  # Condición RSI menos estricta
        (df['ha_close'] < df['ha_open']) &
        (df['ha_close'].shift(1) < df['ha_open'].shift(1)) &
        (df['trend'] == -1)  # Solo vender en tendencia bajista
    )
    
    df.loc[long_condition, 'position'] = 1  # Compra
    df.loc[short_condition, 'position'] = -1  # Venta
    
    # Implementar stop-loss y take-profit adaptativos
    sl_atr_multiple = 2
    tp_atr_multiple = 3
    
    for i in range(window_size, len(df)):
        if df['position'].iloc[i-1] != 0:
            entry_price = df['close'].iloc[i-1]
            current_atr = df['atr'].iloc[i-1]
            if df['position'].iloc[i-1] == 1:  # Posición larga
                sl_price = entry_price - sl_atr_multiple * current_atr
                tp_price = entry_price + tp_atr_multiple * current_atr
                if df['low'].iloc[i] < sl_price or df['high'].iloc[i] > tp_price:
                    df.loc[df.index[i], 'position'] = 0
            else:  # Posición corta
                sl_price = entry_price + sl_atr_multiple * current_atr
                tp_price = entry_price - tp_atr_multiple * current_atr
                if df['high'].iloc[i] > sl_price or df['low'].iloc[i] < tp_price:
                    df.loc[df.index[i], 'position'] = 0
    
    df['returns'] = df['close'].pct_change()
    df['strategy_returns'] = df['position'].shift(1) * df['returns']
    
    # Calcular balance
    df['cumulative_returns'] = (1 + df['strategy_returns']).cumprod()
    df['balance'] = initial_balance * df['cumulative_returns']
    
    return df

Когда дело доходит до результатов, скрипт дает быстрый обзор эффективности стратегии. В этом случае она достигла общую доходность в 1,35%, что означает, что за период тестирования 10 000 долларов превратились в 10 135,02 доллара. Хотя это и не является впечатляющим фактом, это положительный результат, свидетельствующий о том, что стратегия имеет определенные достижения. Коэффициент Шарпа, распространенный показатель доходности с поправкой на риск, составил 0,39. Эта цифра указывает на то, что, несмотря на достигнутые результаты, они были сопряжены с изрядной долей риска. Для быстрого тестирования этот коэффициент является полезным способом оценить, стоит ли продолжать использование данной стратегии в дальнейшем. 

Retorno total: 1.35%
Ratio de Sharpe: 0.39
Balance final: $10135.02

Graph Price and strategy

Heiken Ashi

Баланс

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

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


Советник

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

Скрипт ориентирован на валютную пару EUR/USD и использует H6 (6-часовой) таймфрейм. Все начинается с настройки нескольких знакомых технических индикаторов, таких как RSI, SMA, PSAR и ATR. Это обычные инструменты, которые трейдеры используют для оценки динамики рынка, направления тренда и волатильности. Кроме того, в нем используются свечи Хейкин-Аши, которые помогают сгладить ценовое движение и облегчают выявление трендов, снижая рыночный шум. Эта первоначальная настройка закладывает основу для достижения того, чего добивается скрипт: систематического поиска возможностей для покупки и продажи.

int    handleRSI, handleSMA, handlePSAR, handleATR;
double rsiBuffer[], smaBuffer[], psarBuffer[], atrBuffer[];
double haOpen[], haClose[], haHigh[], haLow[];
CTrade trade;
   handleRSI = iRSI(Symbol, Timeframe, RSIPeriod, PRICE_CLOSE);
   handleSMA = iMA(Symbol, Timeframe, SMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   handlePSAR = iSAR(Symbol, Timeframe, PSARStep, PSARMaximum);
   handleATR = iATR(Symbol, Timeframe, ATRPeriod);

   if(handleRSI == INVALID_HANDLE || handleSMA == INVALID_HANDLE ||
      handlePSAR == INVALID_HANDLE || handleATR == INVALID_HANDLE)
     {
      Print("Error creating indicators");
      return(INIT_FAILED);
     }

   ArraySetAsSeries(rsiBuffer, true);
   ArraySetAsSeries(smaBuffer, true);
   ArraySetAsSeries(psarBuffer, true);
   ArraySetAsSeries(atrBuffer, true);

   ArrayResize(haOpen, 3);
   ArrayResize(haClose, 3);
   ArrayResize(haHigh, 3);
   ArrayResize(haLow, 3);
   ArraySetAsSeries(haOpen, true);
   ArraySetAsSeries(haClose, true);
   ArraySetAsSeries(haHigh, true);
   ArraySetAsSeries(haLow, true);
   IndicatorRelease(handleRSI);
   IndicatorRelease(handleSMA);
   IndicatorRelease(handlePSAR);
   IndicatorRelease(handleATR);

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

#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

#resource "/Files/EURUSD_D1_2024.onnx" as uchar ExtModel[]
//--- 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);
     }
//--- 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;
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;
     }
  }



//+------------------------------------------------------------------+
//| Gets Min and Max values                                          |
//+------------------------------------------------------------------+
void GetMinMax(void)
  {
   vectorf close;
   close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,0,SAMPLE_SIZE);
   ExtMin=close.Min();
   ExtMax=close.Max();
  }

Торговая логика довольно прямолинейна, но в то же время сложна. Для получения сигнала на покупку (длинную позицию) требуется совпадение нескольких условий: текущая цена должна быть выше как PSAR, так и SMA; RSI должен быть ниже 60 (что указывает на то, что рынок не перекуплен), а свечи Хейкин-Аши должны указывать на восходящий тренд. Критически важно, что модель глубокого обучения также должна прогнозировать восходящее движение цены. Если все эти факторы совпадают, скрипт открывает позицию на покупку. Аналогичным образом, он ищет противоположные условия для открытия позиции на продажу (короткую позицию). Этот многоуровневый подход направлен на то, чтобы отфильтровать шум и ложные сигналы, гарантируя, что сделки будут совершаться только тогда, когда множество индикаторов и модель согласуют направление.

void CalculateHeikinAshi()
{
   double close[], open[], high[], low[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);

   int copied = CopyClose(Symbol(), Timeframe, 0, 3, close);
   copied = MathMin(copied, CopyOpen(Symbol(), Timeframe, 0, 3, open));
   copied = MathMin(copied, CopyHigh(Symbol(), Timeframe, 0, 3, high));
   copied = MathMin(copied, CopyLow(Symbol(), Timeframe, 0, 3, low));

   if(copied < 3)
   {
      Print("Not enough data for Heikin Ashi calculation");
      return;
   }

   // Calculate Heikin Ashi values for the last 3 candles
   for(int i = 2; i >= 0; i--)
   {
      haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4;
      
      if(i == 2)
      {
         haOpen[i] = (open[i] + close[i]) / 2;
      }
      else
      {
         haOpen[i] = (haOpen[i+1] + haClose[i+1]) / 2;
      }
      
      haHigh[i] = MathMax(high[i], MathMax(haOpen[i], haClose[i]));
      haLow[i] = MathMin(low[i], MathMin(haOpen[i], haClose[i]));
   }

   // Debug print
   Print("Heikin Ashi values:");
   for(int i = 0; i < 3; i++)
   {
      Print("Candle ", i, ": Open=", haOpen[i], " High=", haHigh[i], " Low=", haLow[i], " Close=", haClose[i]);
   }
}
   // Copy indicator data
   if(CopyBuffer(handleRSI, 0, 0, 3, rsiBuffer) <= 0 ||
      CopyBuffer(handleSMA, 0, 0, 3, smaBuffer) <= 0 ||
      CopyBuffer(handlePSAR, 0, 0, 3, psarBuffer) <= 0 ||
      CopyBuffer(handleATR, 0, 0, 3, atrBuffer) <= 0)
   {
      Print("Failed to copy indicator data");
      return;
   }

   CalculateHeikinAshi();

   if(ArraySize(haOpen) < 3 || ArraySize(haClose) < 3)
   {
      Print("Not enough Heikin Ashi data");
      return;
   }

   PredictPrice();
   int trend = GetTrend();

   bool longCondition = close > psarBuffer[1] &&
                        close > smaBuffer[1] &&
                        rsiBuffer[1] < 60 &&
                        haClose[1] > haOpen[1] &&
                        haClose[2] > haOpen[2] &&
                        ExtPredictedClass == PRICE_UP &&
                        trend == 1;

   bool shortCondition = close < psarBuffer[1] &&
                         close < smaBuffer[1] &&
                         rsiBuffer[1] > 40 &&
                         haClose[1] < haOpen[1] &&
                         haClose[2] < haOpen[2] &&
                         ExtPredictedClass == PRICE_DOWN &&
                         trend == -1;

   // Debug printing
   Print("--- Debug Info ---");
   Print("Close: ", close, " PSAR: ", psarBuffer[1], " SMA: ", smaBuffer[1]);
   Print("RSI: ", rsiBuffer[1], " HA Close[1]: ", haClose[1], " HA Open[1]: ", haOpen[1]);
   Print("HA Close[2]: ", haClose[2], " HA Open[2]: ", haOpen[2]);
   Print("ExtPredictedClass: ", ExtPredictedClass, " Trend: ", trend);
   Print("Long Condition: ", longCondition, " Short Condition: ", shortCondition);

   if(longCondition)
   {
      Print("Long Condition Met");
      if(PositionsTotal() == 0)
      {
         double atrStopLoss = SLATRMultiple * atrBuffer[1];
         double minStopLoss = GetMinimumStopLoss();
         double sl = close - MathMax(atrStopLoss, minStopLoss);
         double tp = close + TPATRMultiple * atrBuffer[1];
         
         bool result = trade.Buy(0.01, Symbol(), 0, sl, tp);
         if(result)
            Print("Buy order placed successfully. SL: ", sl, " TP: ", tp);
         else
            Print("Failed to place buy order. Error: ", GetLastError());
      }
      else
      {
         Print("Position already open. Skipping buy order.");
      }
   }
   else if(shortCondition)
   {
      Print("Short Condition Met");
      if(PositionsTotal() == 0)
      {
         double atrStopLoss = SLATRMultiple * atrBuffer[1];
         double minStopLoss = GetMinimumStopLoss();
         double sl = close + MathMax(atrStopLoss, minStopLoss);
         double tp = close - TPATRMultiple * atrBuffer[1];
         
         bool result = trade.Sell(0.01, Symbol(), 0, sl, tp);
         if(result)
            Print("Sell order placed successfully. SL: ", sl, " TP: ", tp);
         else
            Print("Failed to place sell order. Error: ", GetLastError());
      }
      else
      {
         Print("Position already open. Skipping sell order.");
      }
   }
   
   ManageTrailingStop();
}

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

double GetMinimumStopLoss()
{
   double spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   double minStopLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   return MathMax(spread * 2, minStopLevel); // Usamos el doble del spread como mínimo
}

// Agregar esta función para manejar el trailing stop
void ManageTrailingStop()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(PositionSelectByTicket(PositionGetTicket(i)))
      {
         if(PositionGetString(POSITION_SYMBOL) == Symbol())
         {
            double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentStopLoss = PositionGetDouble(POSITION_SL);
            double currentTakeProfit = PositionGetDouble(POSITION_TP);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_BID) - TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits());
               if(newStopLoss > currentStopLoss + TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT))
               {
                  if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit))
                  {
                     Print("Trailing stop ajustado para posición larga. Nuevo SL: ", newStopLoss);
                  }
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_ASK) + TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits());
               if(newStopLoss < currentStopLoss - TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT))
               {
                  if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit))
                  {
                     Print("Trailing stop ajustado para posición corta. Nuevo SL: ", newStopLoss);
                  }
               }
            }
         }
      }
   }
}

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


Результаты

Настройки

Входные параметры

Тестирование на истории

Graph EA

Результаты работы советника (EA) дают представление о его работе в течение периода тестирования на исторических данных. Начав с первоначального депозита в размере 10 000 долларов США, стратегия достигла скромную общую чистую прибыль в размере 17 единиц, указывая на то, что, хотя она и приносила прибыль, последняя была относительно небольшой по сравнению с первоначальными инвестициями. Кривая баланса отражает этот постепенный рост, демонстрируя устойчивую, хотя и медленную, восходящую траекторию с течением времени.

Одним из особых показателей здесь является профит-фактор, составляющий 4,39. Это твердая цифра, предполагающая, что за каждую единицу принятого риска стратегия приносила 4,39 единицы вознаграждения. Это означает, что советник был эффективен в максимизации прибыли по сравнению с убытками. Фактор восстановления, равный 4,48, еще раз подтверждает это, показывая, что стратегия смогла эффективно восстанавливаться после просадок, что является положительным признаком ее устойчивости. Однако стоит отметить коэффициент Шарпа, который составляет 5,25. Хотя этот коэффициент относительно высок и, как правило, указывает на хорошую доходность с поправкой на риск, общая небольшая прибыль предполагает, что стратегия, возможно, предполагала минимальные риски, что привело к ограниченной абсолютной прибыли.

Изучив торговую статистику, советник в общей сложности совершил 16 сделок, равномерно распределив их между длинными и короткими позициями. Коэффициент выигрыша по долгосрочным сделкам был выше и составил 62,5%, в то время как по краткосрочным сделкам коэффициент выигрыша составил 37,5%. Это несоответствие указывает на то, что стратегия была более успешной в отражении восходящих движений рынка. Интересно, что прибыльные сделки составляли ровно 50% от общего числа сделок, что подчеркивает тот факт, что прибыльность советника была обусловлена не высоким коэффициентом выигрыша, а скорее соотношением его победителей и проигравших. Сделки с наибольшей и средней прибылью были положительными, что говорит о том, что советнику удалось зафиксировать прибыль при благоприятном движении рынка. Кривая баланса отражает эти результаты, показывая периоды просадки, за которыми следует рост, что свидетельствует об осторожном, но устойчивом подходе к торговле.

Показатели просадки показывают, что советник поддерживал низкий уровень подверженности риску. Абсолютная просадка баланса составила 2 единицы, что довольно мало, а относительная - всего 0,02%. Эта очень низкая просадка говорит о том, что стратегия советника была консервативной и ставила во главу угла не агрессивное стремление к прибыли, а сохранение капитала. Такой консервативный подход может быть полезен на волатильных рынках, но также объясняет относительно скромную общую чистую прибыль.

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


Заключение

Исследование возможностей интеграции моделей глубокого обучения с традиционным техническим анализом показало многообещающие результаты. С помощью скрипта на Python мы увидели, как системный подход с использованием исторических данных, ключевых индикаторов и машинного обучения может помочь в определении потенциальных торговых возможностей. Результаты были скромными, но последовательными, демонстрируя, что такое сочетание может дать положительный результат, хотя и с некоторыми ограничениями. Аналогичным образом, советник MetaTrader 5 (EA) подтвердил этот осторожный подход. Несмотря на то, что результаты бэк-тестирования советника показали небольшой, но устойчивый рост при минимальной просадке. Это говорит о стратегии, сконцентрированной не на максимизации прибыли, а на управлении рисками. Такая консервативность делает его привлекательным для трейдеров, отдающих предпочтение устойчивому росту с низким уровнем риска. Однако для тех, кто стремится к более высокой доходности, необходимы дальнейшие доработки и оптимизация. В конечном счете, эти инструменты служат основой — начальной точкой для трейдеров, позволяющей сочетать человеческую интуицию с аналитическими возможностями искусственного интеллекта, предлагая тонкий подход к навигации в сложном мире торговли на рынке Форекс.

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

Прикрепленные файлы |
EURUSD_D1_2024.onnx (884.41 KB)
004.py (8.06 KB)
PHD_Final.mq5 (27.7 KB)
005.py (8.14 KB)
Одномерный сингулярный спектральный анализ Одномерный сингулярный спектральный анализ
Статья рассматривает теоретические и практические аспекты метода сингулярного спектрального анализа (SSA), который представляет собой эффективный метод анализа временных рядов, позволяющий представить сложную структуру ряда в виде разложения на простые компоненты, такие как тренд, сезонные (периодические) колебания и шум.
Анализ нескольких символов с помощью Python и MQL5 (Часть I): Производители интегральных схем NASDAQ Анализ нескольких символов с помощью Python и MQL5 (Часть I): Производители интегральных схем NASDAQ
В статье мы рассмотрим, как использовать ИИ для оптимизации размера позиции и количества ордеров, чтобы максимизировать доходность портфеля. Мы покажем, как алгоритмически определить оптимальный портфель и адаптировать его к вашим ожиданиям по доходности или уровню устойчивости к риску. Мы используем библиотеку SciPy и язык MQL5 для создания оптимального и диверсифицированного портфеля, используя все имеющиеся у нас данные.
Как опередить любой рынок (Часть V): Альтернативные данные FRED EURUSD Как опередить любой рынок (Часть V): Альтернативные данные FRED EURUSD
В статье использованы альтернативные ежедневные данные Федерального резервного банка Сент-Луиса по обобщенному индексу доллара США и набор других макроэкономических показателей для прогнозирования будущего обменного курса EURUSD. К сожалению, хотя данные, по-видимому, имеют почти идеальную корреляцию, нам не удалось получить никаких существенных преимуществ в точности нашей модели, что, наводит нас на мысль, что инвесторам, возможно, лучше использовать обычные рыночные котировки.
Анализ настроений в Twitter с помощью сокетов Анализ настроений в Twitter с помощью сокетов
Этот инновационный торговый бот интегрирует платформу MetaTrader 5 с языком Python в целях использования анализа настроений в социальных сетях в режиме реального времени для автоматизированного принятия торговых решений. Путем анализа настроений в Twitter, связанных с конкретными финансовыми инструментами, бот преобразует тенденции социальных сетей в действенные торговые сигналы. Он использует архитектуру «клиент-сервер» с сокетной связью, обеспечивая бесперебойное взаимодействие между торговыми возможностями MetaTrader 5 и вычислительной мощностью Python. Система демонстрирует потенциал объединения финансовой математики с обработкой текстов на естественном языке, предлагая передовой подход к алгоритмической торговле, использующей альтернативные источники данных. Бот не только демонстрирует серьезные перспективы, но и указывает на области для дальнейшего совершенствования, включая более продвинутые методы анализа настроений и улучшенные стратегии управления рисками.