Создание самооптимизирующихся советников на MQL5 (Часть 17): Ансамблевый интеллект
Все алгоритмические торговые стратегии сложны в настройке и обслуживании, независимо от их сложности. Эта проблема актуальна как для новичков, так и для опытных специалистов. Новичкам приходится нелегко, пытаясь постоянно настраивать периоды расчётов в своих стратегиях, основанных на пересечении скользящих средних, в то время как эксперты с таким же упорством занимаются настройкой весов своих глубоких нейронных сетей. Серьёзные проблемы есть у обоих подходов.
Модели машинного обучения нестабильны и часто разваливаются в условиях реальной торговли. Их непрозрачная и сложная структура еще больше затрудняет поиск и устранение неисправностей, а также выявление узких мест в производительности. С другой стороны, стратегии, разработанные людьми, могут быть более устойчивыми, но для их запуска часто требуется ручная настройка — процесс, который может быть трудоемким в зависимости от используемого подхода. В статье предлагается ансамблевая схема, в которой модели с учителем и человеческая интуиция дополняют друг друга, быстрее компенсируя взаимные ограничения.
Для достижения этой цели мы разработали нашу стратегию и статистическую модель таким образом, чтобы они использовали одни и те же четыре технических индикатора. Мы выбрали стратегию на основе канала скользящих средних и и обучили модель Ridge Regression на тех же индикаторах. Благодаря этому нам удалось быстро определить оптимальную конфигурацию для всей системы.
Технические индикаторы позволяют нам централизованно управлять как человеческой интуицией, так и обученной моделью. Требование открывать позиции только в тех случаях, когда традиционный и статистический компоненты давали совпадающие результаты, позволило добиться прибыльности на основе двух систем, которые по отдельности были убыточными. В этом и заключается смысл нашей концепции ансамбля: наши стратегии, как показывает практика, способны взаимно корректировать друг друга быстрее, чем мы могли бы отдельно скорректировать каждую из них.
Наш подход позволяет статистической модели обучаться на тех же технических индикаторах, которые используются в стратегии, что делает ансамблевое объединение более практичным и помогает нам находить стабильные конфигурации, определение которых в противном случае заняло бы много времени. Благодаря такому централизованному управлению нам достаточно настроить всего несколько технических индикаторов, которые влияют на оба компонента, что позволяет быстро определить, какие скользящие средние имеют значение, независимо от их базовых периодов. Настройка периодов скользящих средних не потребовалась, хотя в статье будет показано, что исходная стратегия оказалась убыточной при использовании выбранных периодов, которые мы сохранили в демонстрационных целях. Система учится самокорректироваться.
Визуализация торговой стратегии
Стратегия основана на двух индикаторах скользящего среднего, которые накладываются соответственно на графики максимальных и минимальных цен. Индикаторы образуют канал, причем расстояние между ними зависит от текущей волатильности рынка. На рисунке 1 ниже представлена схема этой стратегии. Стратегия генерирует сигналы на покупку при пробое цены выше верхней границы канала, а для сигналов на продажу действует обратное правило. В белом поле в правом верхнем углу рисунка 1 видно, что стратегия сгенерировала два противоположных сигнала за короткий промежуток времени; в работе стратегии наблюдается заметный уровень шума.

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

Рисунок 2: Хотя в этой стратегии есть некоторые недостатки, в целом она выглядит обоснованной
Определение базового уровня эффективности
Для начала мы определим системные константы, которые будут оставаться неизменными на протяжении всего теста.
//+------------------------------------------------------------------+ //| EI.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define SYSTEM_TF PERIOD_D1 #define MA_SHIFT 0 #define MA_TYPE MODE_EMA #define ATR_PERIOD 14 #define PADDING 2
Далее мы подключим вспомогательные библиотеки, которые понадобятся нам для выполнения этого упражнения.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> CTrade Trade; TradeInfo *TradeHelper;
Глобальные переменные необходимы практически во всех приложениях; они нужны нам для отслеживания показаний технических индикаторов и времени.
//+------------------------------------------------------------------+ //| Define global variables | //+------------------------------------------------------------------+ int ma_h_handler,ma_l_handler,atr_handler; double ma_h[],ma_l[],atr[]; MqlDateTime tc,ts;
Мы зададим входное значение 20; если вы измените это значение, не забудьте внести соответствующие изменения в скрипт.
//+------------------------------------------------------------------+ //| Input varaibles | //+------------------------------------------------------------------+ input group "Technical Indicators" input int MA_PERIOD = 20;//Moving average period
При инициализации нашего приложения мы загрузим необходимые технические индикаторы и создадим новые экземпляры нужных классов.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators ma_h_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_HIGH); ma_l_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_LOW); atr_handler = iATR(Symbol(),SYSTEM_TF,ATR_PERIOD); TradeHelper = new TradeInfo(Symbol(),SYSTEM_TF); //--- Mark the time TimeLocal(tc); TimeLocal(ts); //--- return(INIT_SUCCEEDED); }
Если наше приложение больше не используется, мы освободим индикаторы, которые больше не применяем.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete TradeHelper; IndicatorRelease(ma_h_handler); IndicatorRelease(ma_l_handler); IndicatorRelease(atr_handler); }
Наше приложение будет выполнять рутинные задачи каждый час. Для этого проверяется промежуток времени с помощью специального объекта структуры времени MqlDateTime. Затем мы приступаем к обновлению буферов технического индикатора и сохраняем текущую цену закрытия. Наконец, мы проверяем наличие торгового сигнала: если цена закрытия вышла за пределы канала скользящих средних, мы открываем позиции, отражая уверенность в этом движении.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- TimeLocal(ts); if(ts.hour != tc.hour) { if(PositionsTotal()==0) { //--- Update the time TimeLocal(tc); //--- Update the indicator buffer CopyBuffer(ma_h_handler,0,0,1,ma_h); CopyBuffer(ma_l_handler,0,0,1,ma_l); CopyBuffer(atr_handler,0,0,1,atr); //--- Check if the current price is above or below the channel double c = iClose(Symbol(),SYSTEM_TF,0); if(c > ma_h[0]) Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING)); else if(c < ma_l[0]) Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING)); } } } //+------------------------------------------------------------------+
Наконец, мы отменяем определения всех системных констант, которые были определены ранее.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef SYSTEM_TF #undef MA_SHIFT #undef MA_TYPE #undef ATR_PERIOD #undef PADDING
Запустите тестовое приложение и установите соответствующие даты для тестирования. Для данного задания мы отобрали данные по курсу EURUSD за более чем трёхлетний период — с января 2022 года по январь 2025 года.

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

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

Рисунок 5: Наше текущее упражнение позволяет выбирать произвольные периоды для оценки
Как объяснялось во введении, значение 20 не было рентабельным. Однако, как мы увидим, наша статистическая стратегия может извлечь из этого уроки и помочь нам правильно отфильтровывать шумы, не прибегая при этом к постоянному перебору различных периодов для наших технических индикаторов.

Рисунок 6: Кривая капитала, построенная нашим торговым приложением, нестабильна и не вызывает у нас особого доверия
Полученные нами результаты тестирования соответствуют кривой капитала, которую мы наблюдали ранее — она отрицательная, что обычно является сигналом к тому, что нам следует отказаться от нашей идеи, — но на сегодняшний день мы продолжим работу с системой в неизменном виде.

Рисунок 7: Подробные статистические данные, полученные с помощью нашего тестового приложения, показывают, что у нас ещё есть возможности для улучшения
Получить исторические рыночные данные
Теперь давайте создадим скрипт для извлечения исторических рыночных данных и их записи в файл CSV. Мы будем использовать эти данные для разработки статистической стратегии в отношении рынка EURUSD.
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- Define our moving average indicator #define MA_PERIOD 5 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 5 //--- Forecast horizon //--- Our handlers for our indicators int ma_handle,ma_o_handle,ma_h_handle,ma_l_handle; //--- Data structures to store the readings from our indicators double ma_reading[],ma_o_reading[],ma_h_reading[],ma_l_reading[]; //--- File name string file_name = Symbol() + " Detailed Market Data As Series Moving Average.csv"; //--- Amount of data requested input int size = 3000; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { int fetch = size + (HORIZON * 2); //---Setup our technical indicators ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE); ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN); ma_h_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_HIGH); ma_l_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_LOW); //---Set the values as series CopyBuffer(ma_handle,0,0,fetch,ma_reading); ArraySetAsSeries(ma_reading,true); CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading); ArraySetAsSeries(ma_o_reading,true); CopyBuffer(ma_h_handle,0,0,fetch,ma_h_reading); ArraySetAsSeries(ma_h_reading,true); CopyBuffer(ma_l_handle,0,0,fetch,ma_l_reading); ArraySetAsSeries(ma_l_reading,true); //---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i=size;i>=1;i--) { if(i == size) { FileWrite(file_handle,"Time", //--- OHLC "True Open", "True High", "True Low", "True Close", //--- MA OHLC "True MA O", "True MA H", "True MA L", "True MA C" ); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), //--- OHLC iClose(_Symbol,PERIOD_CURRENT,i), iOpen(_Symbol,PERIOD_CURRENT,i), iHigh(_Symbol,PERIOD_CURRENT,i), iLow(_Symbol,PERIOD_CURRENT,i), //--- MA OHLC ma_o_reading[i], ma_h_reading[i], ma_l_reading[i], ma_reading[i] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef HORIZON #undef MA_PERIOD #undef MA_TYPE //+------------------------------------------------------------------+
Анализ исторических рыночных данных на Python
Загрузите наши стандартные библиотеки Python.#Load the libraries we need import pandas as pd import numpy as np
Прочитайте рыночные данные, которые мы ранее сохранили в формате CSV.
#Read in the data data = pd.read_csv("../EURUSD Detailed Market Data As Series Moving Average.csv") data
Определите горизонт прогнозирования.
#Define the forecast horizon HORIZON = 20
Удалите все исторические данные, которые пересекаются с периодом бэктеста.
#Drop the dates that overlap with the back test data = data.iloc[:-(365*3),:] _ = data.iloc[-(365*3):,:]
Обозначьте рыночные данные, чтобы мы могли построить модель, определяющую, по какую сторону скользящей средней, как ожидается, окажется цена закрытия. Именно это лежит в основе нашей стратегии с точки зрения человеческой интуиции.
#Label the data data['Target H'] = data['Close'].shift(-HORIZON) - data['MA H'].shift(-HORIZON) data['Target L'] = data['Close'].shift(-HORIZON) - data['MA L'].shift(-HORIZON) #Drop missing rows data = data.iloc[:-HORIZON,:]
Загрузите наши библиотеки ONNX. ONNX (сокращение от Open Neural Network Exchange) — это библиотека с открытым исходным кодом, которая помогает создавать и развертывать модели машинного обучения без переноса зависимостей из сред обучения.
from sklearn.linear_model import Ridge import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Обучите модель на всех обучающих данных.
model = Ridge(alpha=1e-3) model.fit(data.iloc[:,1:-2],data.loc[:,['Target H','Target L']])
Определите формы входных и выходных данных нашей модели ONNX.
initial_types = [('float_input',FloatTensorType([1,8]))] final_types = [('float_output',FloatTensorType([1,2]))]
Сохраните модель ONNX в виде прототипа ONNX.
onnx_proto = convert_sklearn(model=model,initial_types=initial_types,final_types=final_types,target_opset=12)Сохраните прототип ONNX в виде файла на диске.
onnx.save(onnx_proto,'EURUSD MA R.onnx')
Превосходство над базовым показателем
Теперь мы сосредоточимся на тех частях нашего кода, которые будут изменены, и опустим все остальные, которые останутся без изменений. Первое изменение, которое мы внесем, — это добавление двух новых определений системных констант, связанных с нашей моделью ONNX.
//+------------------------------------------------------------------+ //| EI.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define ONNX_FEATURES 8 #define ONNX_TARGETS 2
Теперь загрузим нашу модель ONNX.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD MA R.onnx" as const uchar onnx_buffer[];
Теперь мы создадим нашу модель ONNX на основе ранее определённого буфера и настроим структуру входных и выходных данных модели.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DATA_TYPE_FLOAT); if(onnx_model == INVALID_HANDLE) { Print("Failed to create ONNX model: ",GetLastError()); return(INIT_FAILED); } ulong input_shape[] = {1,ONNX_FEATURES}; ulong output_shape[] = {1,ONNX_TARGETS}; onnx_inputs = vectorf::Zeros(ONNX_FEATURES); onnx_output = vectorf::Zeros(ONNX_TARGETS); if(!OnnxSetInputShape(onnx_model,0,input_shape)) { Print("Failed to define ONNX input shape: ",GetLastError()); return(INIT_FAILED); } if(!OnnxSetOutputShape(onnx_model,0,output_shape)) { Print("Failed to define ONNX output shape: ",GetLastError()); return(INIT_FAILED); } //--- Mark the time TimeLocal(tc); TimeLocal(ts); //--- return(INIT_SUCCEEDED); }
Если наше приложение больше не используется, мы также должны освободить модель ONNX, которая нам больше не нужна.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(onnx_model); }
Помимо ранее определённых обновлений индикаторов, нам также потребуются текущие рыночные данные для нашей модели ONNX. Напомним, что наша модель прогнозирует, насколько цена закрытия отклонится от границ скользящей средней. Положительные отклонения свидетельствуют о бычьем тренде; отрицательные отклонения — о медвежьем тренде.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(ts.hour != tc.hour) { if(PositionsTotal()==0) { onnx_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TF,0); onnx_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TF,0); onnx_inputs[2] = (float) iLow(Symbol(),SYSTEM_TF,0); onnx_inputs[3] = (float) iClose(Symbol(),SYSTEM_TF,0); onnx_inputs[4] = (float) ma_o[0]; onnx_inputs[5] = (float) ma_h[0]; onnx_inputs[6] = (float) ma_l[0]; onnx_inputs[7] = (float) ma_c[0]; if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output)) { //--- Check if the current price is above or below the channel Print("Forecast: ",onnx_output); double c = iClose(Symbol(),SYSTEM_TF,0); if((c > ma_h[0]) && (onnx_output[0]>0) && (onnx_output[1]>0)) Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING)); else if((c < ma_l[0]) && (onnx_output[0]<0) && (onnx_output[1]<0)) Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING)); } else { Print("Failed to obtain a prediction from our ONNX model: ",GetLastError()); } } } } //+------------------------------------------------------------------+
Затем отменяем новые определения, которые мы создали для адаптации нашей модели ONNX.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef ONNX_FEATURES #undef ONNX_TARGETS //+------------------------------------------------------------------+
Теперь протестируем новую версию приложения, которую мы только что создали вместе, на том же самом периоде тестирования.

Рисунок 8: Затем мы выбираем те же даты тестирования, что и в первом тесте, для обеспечения согласованности
Как видно, приложение по-прежнему убыточно. Однако давайте более подробно рассмотрим статистику.

Рисунок 9: Наше приложение пока не вышло на прибыльность
Общая чистая прибыль увеличилась с -96 до -62 долларов, но у нас ещё есть большой потенциал для улучшения.

Рисунок 10: Подробные результаты анализа, который мы только что провели, по-прежнему не вселяют в нас уверенности в правильности нашей стратегии на данный момент
Дополнительные усовершенствования
Опираясь на наше предыдущее обсуждение свечных моделей, предложим для нашей статистической стратегии еще один источник сигналов. Эти данные о максимальных и минимальных ценах являются исходными данными для наших индикаторов скользящей средней. Ранее мы подробно рассмотрели свечу «бычье поглощение» и выяснили, как добиться с её помощью хороших результатов независимо от наших статистических моделей.
if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output)) { //--- Check if the current price is above or below the channel Print("Forecast: ",onnx_output); double c = iClose(Symbol(),SYSTEM_TF,0); //--- Check for any bullish engulfing candle sticks if((onnx_output[0]>0) && (onnx_output[1]>0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2))) Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING)); //--- Check for any bearish engulfing candle sticks else if((onnx_output[0]<0) && (onnx_output[1]<0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2))) Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING)); }
Давайте теперь повторим тест за тот же период, используя эту обновленную версию нашего приложения.

Рисунок 11: Запуск новой версии нашей торговой стратегии на том же трехлетнем тестовом периоде
Как видим, сейчас мы достигаем новых уровней рентабельности, о которых раньше могли только мечтать. Наше приложение начало набирать обороты. Но давайте более подробно рассмотрим результаты этого бэктеста.

Рисунок 12: Кривая капитала, построенная нашей новой обновленной версией торгового приложения, вселяет в нас уверенность
Отрадно видеть, что наше приложение теперь приносит чистую прибыль в размере 126,58 долларов, хотя за три года тестирования на исторических данных было открыто всего девять коротких позиций. Это недопустимо и может свидетельствовать о нереализованном потенциале, который мы пока не раскрыли.

Рисунок 13: Анализ подробных результатов, полученных с помощью нашей усовершенствованной торговой стратегии, показывает, что распределение сделок требует дальнейшей доработки
Последний шанс
Давайте теперь предпримем последнюю попытку усовершенствовать модель. Для начала мы внесем важные изменения в нашу модель. Мы будем прогнозировать наши технические индикаторы на несколько шагов вперед, при этом каждый индикатор прогнозируется на 1 и на 20 шагов вперёд, что даст нам в общей сложности четыре целевых значения.
#Label the data data['Target H'] = data['MA H'].shift(-1) data['Target L'] = data['MA L'].shift(-1) data['Target H 2'] = data['MA H'].shift(-HORIZON) data['Target L 2'] = data['MA L'].shift(-HORIZON) #Drop missing rows data = data.iloc[:-HORIZON,:]
Затем мы обучим модель Ridge Regression с параметром alpha = 0.001. Это определяет, насколько быстро незначимые коэффициенты должны сходиться к нулю, позволяя нашей модели сосредоточиться на важных параметрах. Затем мы подбираем нашу модель.
model = Ridge(alpha=1e-3) model.fit(data.iloc[:,1:-4],data.loc[:,['Target H','Target L','Target H 2','Target L 2']])
Теперь определены формы входных и выходных данных модели.
initial_types = [('float_input',FloatTensorType([1,8]))] final_types = [('float_output',FloatTensorType([1,4]))]
В заключение сохраним нашу модель ONNX в файл.
onnx.save(onnx_proto,'EURUSD MA MFH R.onnx')
Внедрение наших улучшений в MQL5
Теперь мы готовы реализовать наши улучшения в MQL5. Для начала мы изменим размер выходных данных нашей модели ONNX.
//+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define ONNX_TARGETS 4
Затем мы загружаем нашу недавно обновленную многоэтапную модель прогнозирования.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD MA MFH R.onnx" as const uchar onnx_buffer[];
Теперь мы можем получить новый прогноз от нашей модели и сравнить его с нашей человеческой интуицией. Когда скользящая средняя по ценам закрытия находится выше скользящей средней по ценам открытия, это указывает на бычий настрой рынка. С другой стороны, если модель ONNX предполагает, что со временем как верхняя, так и нижняя скользящие средние будут расти, это укрепляет нашу уверенность и позволяет открывать длинные позиции. В отношении продаж действует обратное условие .
if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output)) { //--- Check if the current price is above or below the channel Print("Forecast: ",onnx_output); double c = iClose(Symbol(),SYSTEM_TF,0); if((ma_o[0]<ma_c[0]) && (onnx_output[0]<onnx_output[2]) && (onnx_output[1]<onnx_output[3])) Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING)); else if((ma_o[0]>ma_c[0]) && (onnx_output[0]>onnx_output[2]) && (onnx_output[1]>onnx_output[3])) Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING)); }
Давайте теперь протестируем усовершенствованную версию нашего торгового приложения за тот же тестовый период.

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

Рисунок 15: Кривая капитала, построенная на основе нашей окончательной версии торгового приложения, достигает новых максимумов, которых нам ранее не удавалось достичь
Кроме того, наши новые показатели эффективности находятся на хорошем уровне. Наше приложение теперь приносит общую прибыль в размере 173,72 доллара, что намного лучше, чем убыток в 96 долларов, с которого мы начинали, и соотношение открытых длинных и коротких позиций наконец-то стало приемлемым.
Рисунок 16: Подробные результаты, полученные с помощью нашей усовершенствованной версии торгового приложения, вселяют в нас уверенность в правильности внесенных нами на данный момент изменений
Заключение
На этом мы заканчиваем наше сегодняшнее обсуждение. В данной статье рассмотрены способы управления нестабильностью алгоритмических торговых стратегий, основанных на моделях с учителем. Наше решение заключалось в том, чтобы построить статистические модели, основанные на тех же показателях и рыночных данных, что и традиционная торговая стратегия, а затем объединить эти две стратегии так, чтобы они работали как единое целое. Эта процедура избавила нас от необходимости настраивать параметры в той традиционной стратегии, с которой мы начинали, и позволила получить конечную стратегию, отличающуюся большей устойчивостью. Читатели могут легко расширить это решение, добавив в него свои предпочтительные индикаторы, а не только те, что приведены в нашем примере.
| Название файла | Описание файла |
|---|---|
| Загрузить данные MA.mq5 | Скрипт MQL5, который мы использовали для получения исторических рыночных данных из терминала MetaTrader 5. |
| EI Baseline.mq5 | Данное приложение послужило эталоном для оценки прибыльности классической стратегии «канал скользящей средней». |
| EI.mq5 | Наша первая попытка превзойти контрольный показатель — обратите внимание, что эта версия приложения не была прибыльной. |
| EI 2.mq5 | Наша первая удачная попытка превзойти эталонный индекс; обратите внимание, что данная версия приложения была ориентирована на длинные позиции. |
| EI 3.mq5 | Лучшая версия стратегии «канал скользящих средних», разработанная нами, которая не только превзошла по результатам классическую стратегию, но и обеспечивала относительно объективные сделки. |
| MA Channel AI 3.ipynb | Jupyter Notebook, который мы создали вместе для анализа исторических рыночных данных, полученных с помощью нашего скрипта MQL5. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20238
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Повторное использование нарушенных ордер-блоков в качестве блоков смягчения (SMC)
Разработка инструментария для анализа Price Action (Часть 34): Построение прогнозных моделей на основе необработанных рыночных данных с помощью усовершенствованного пайплайна загрузки данных
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования