
Совместное использование PSAR, Хейкин-Аши и глубокого обучения для трейдинга
Введение
Поиск рубежа может стать разницей между успехом и неудачей. Имея в распоряжении так много данных, трейдеры для получения преимущества все чаще обращаются к таким передовым технологиям, как глубокое обучение. Этот путь предполагает сочетание традиционных инструментов технического анализа с современными моделями искусственного интеллекта (ИИ) для быстрой оценки и адаптации к рыночным условиям. Чтобы раскрыть этот потенциал, мы разработали скрипт на 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)
Теперь начинается самое интересное - создание нашей модели ИИ! Мы используем комбинацию различных методов ИИ:
- Сверточные нейронные сети (CNN): Они отлично распознают паттерны в последовательностях, точно так же, как наши глаза распознают закономерности в изображениях.
- Сети с длинной кратковременной памятью (LSTM): Они отлично подходят для понимания долгосрочных зависимостей в данных и идеально подходят для определения тенденций с течением времени.
- Плотные слои: Они помогают нашей модели делать окончательные прогнозы.
Мы также добавили несколько исключаемых слоев, чтобы наша модель не стала слишком самоуверенной и не "переобучила" данные.
# 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
Визуализации, сгенерированные скриптом, дают полезное представление о том, как работает стратегия. На первом графике показана взаимосвязь между рыночной ценой, прогнозами модели, а также сигналами на покупку и продажу. График Хейкин-Аши дает более четкое представление о тенденциях рынка, помогая вам понять, правильно ли выбранная стратегия отмечает моменты для входа в сделку и выхода из нее. Наконец, кривая эквити дает четкое представление о том, как менялся баланс счета с течением времени, выделяя как периоды роста, так и периоды спада.
Если кратко, данный скрипт предназначен для быстрого экспериментирования. Это помогает быстро оценить, есть ли у стратегии потенциал, не слишком углубляясь в тонкую настройку. Хотя он и показывает, что стратегия может приносить некоторую прибыль, скромный коэффициент Шарпа говорит о том, что еще многое предстоит сделать. Это делает его полезной начальной точкой — способом быстро определить, заслуживает ли стратегия более глубокой разработки и оптимизации.
Советник
Данный скрипт 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); } } } } } } }
Если кратко, этот скрипт служит быстрым и экспериментальным способом проверить жизнеспособность торговой стратегии. Это не идеальное решение, а скорее испытательный полигон, чтобы увидеть, может ли сочетание технического анализа и модели глубокого обучения выявить прибыльные торговые возможности. Хотя данный скрипт может и не гарантировать прибыль, он обеспечивает более тонкий и обоснованный подход к принятию торговых решений, сочетая человеческую интуицию с идеями машинного обучения.
Результаты
Результаты работы советника (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





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования