Переосмысливаем классические стратегии (Часть 14): Высоковероятные ситуации
В наших предыдущих обсуждениях мы проанализировали, как можно переосмыслить торговлю на пересечениях скользящих средних, зафиксировав периоды двух рассматриваемых индикаторов скользящих средних. Мы продемонстрировали, что таким образом мы можем эффективно осуществлять разумный уровень контроля над величиной запаздывания в нашей торговой стратегии. Мы обнаружили, что, применяя одну скользящую среднюю к уровню цены открытия, а другую к уровню цены закрытия, мы получили гораздо более чувствительную форму традиционной стратегии пересечения скользящих средних. Наша новая модель обеспечивает некоторые гарантии, которые мы не могли получить от традиционной стратегии. Статью с необходимым обсуждением можно найти здесь.
Сегодня мы продолжаем изучать вопрос о том, есть ли смысл пытаться повысить производительность с помощью нашей переосмысленной версии пересечения скользящих средних. Тщательно смоделировав взаимосвязь между нашей стратегией пересечения скользящих средних и рынком EURUSD, мы, надеюсь, сможем понять, в чем разница между рыночными условиями, в которых наша стратегия показывает лучшие результаты, и рыночными условиями, которые оказываются слишком сложными для нее. Наша цель – разработать торговую стратегию, которая учится прекращать торговлю при обнаружении неблагоприятных рыночных условий.
Обзор торговой стратегии
Большинство членов трейдерского сообщества сходятся во мнении, что трейдерам следует активно искать торговые ситуации с высокой вероятностью успеха. Однако существует мало формальных определений того, что именно представляет собой торговая ситуация с высокой вероятностью успеха. Как эмпирически измерить вероятность, связанную с той или иной торговой ситуацией? В зависимости от того, кого вы спросите, вы получите разные определения того, как можно выявлять подобные возможности и ответственно ими пользоваться.
В данной статье предлагается алгоритмическая структура, позволяющая отказаться от старых определений и обратиться к числовым определениям, основанным на фактических данных, чтобы наши торговые стратегии могли самостоятельно и последовательно выявлять и выгодно использовать эти данные.
Мы хотим смоделировать взаимосвязь между нашей конкретной торговой стратегией и любым торговым символом, который мы выбрали для торговли. Для этого мы можем получить рыночные данные, которые полностью описывают рынок и все параметры, составляющие нашу торговую стратегию, — всё это из терминала MetaTrader 5.
Затем мы построим статистическую модель, чтобы определить, будет ли стратегия генерировать прибыльные сигналы, или же сигналы, генерируемые нашей стратегией, скорее всего, будут убыточными.
Вероятность, оцененная нашей моделью, становится той вероятностью, которую мы связываем с конкретным сигналом. Таким образом, теперь мы можем начать говорить о "высоковероятных ситуациях" (high probability setups) более научным и эмпирическим образом, то есть основываясь на доказательствах и соответствующих рыночных данных.
Эта структура, по сути, позволяет нам разрабатывать торговые стратегии, которые "осознают цель" и явно предписывают совершать только те действия, которые, как ожидается, будут выгодными. Мы начинаем формализовать необходимые компоненты для написания алгоритмических торговых стратегий, которые пытаются оценить наиболее вероятные последствия своих действий. Такую структуру можно рассматривать как логику обучения с подкреплением, реализуемую в контролируемом режиме.
Начало работы в MQL5
Наша текущая задача сосредоточена на изучении взаимосвязи между нашей торговой стратегией и символом, которым мы хотим торговать. Для достижения этой цели мы будем получать данные о росте цен по четырем основным показателям (открытие, максимум, минимум и закрытие), а также об изменениях двух наших индикаторов скользящих средних.
Обратите внимание, что для маркировки данных нам также понадобятся исходные реальные значения двух показателей и цена закрытия. В итоге мы запишем наши данные в CSV-файл с 10 столбцами, а затем перейдем к изучению взаимосвязи нашей стратегии с этим конкретным символом, и на каждом этапе будем сравнивать этот результат с результатами идентичной модели, пытающейся напрямую предсказать рыночную цену.
Это позволит нам определить, какой целевой показатель легче изучить. Изящество нашего подхода заключается в том простом факте, что независимо от того, какой целевой показатель легче прогнозировать, оба они указывают нам, куда будет двигаться цена.
/+-------------------------------------------------------------------+ //| 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 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- Our handlers for our indicators int ma_handle,ma_o_handle; //--- Data structures to store the readings from our indicators double ma_reading[],ma_o_reading[]; //--- File name string file_name = Symbol() + " Reward Modelling.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); //---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); //---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","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2"); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), ma_reading[i], ma_o_reading[i], iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iLow(_Symbol,PERIOD_CURRENT,i) - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iClose(_Symbol,PERIOD_CURRENT,i) - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)), ma_reading[i] - ma_reading[(i + HORIZON)], ma_o_reading[i] - ma_o_reading[(i + HORIZON)] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Анализ данных
Для начала давайте загрузим данные о рынке в Jupyter Notebook, чтобы провести численный анализ эффективности стратегии.
import pandas as pd
Укажем, на какой срок в будущем мы хотели бы прогнозировать наши прибыли или убытки.
HORIZON = 10 Считаем данные и добавим необходимые столбцы. В первом столбце, Target (цель), указана традиционная доходность рынка, в данном случае — доходность рынка EURUSD за 10 дней. В столбце Class (класс) значение равно 1 для бычьих дней, в противном случае — 0. В третьем столбце Action (действие) указывается, какое действие должна была бы совершить наша торговая стратегия: 1 соответствует покупке, а -1 — продаже. Столбец Reward (награда) рассчитывается как поэлементное произведение столбцов Target и Action. Это умножение даст положительные результаты только в том случае, если наша стратегия:
- Выбрала действие -1, и классический целевой показатель оказался меньше 0 (это означает, что наша стратегия привела к продажам, а будущие уровни цен снизились).
- Выбрала действие 1, и классический целевой показатель превысил 0 (это означает, что наша стратегия привела к покупке, и будущие уровни цен выросли).
data = pd.read_csv("..\EURUSD Reward Modelling.csv") data['Target'] = 0 data['Class'] = 0 data['Action'] = 0 data['Reward'] = 0 data['Trade Signal'] = 0
Теперь заполним нашу классическую целевую точку — изменение цены закрытия EURUSD за 10 дней.
data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close'] data.dropna(inplace=True)
Нам необходимо обозначить классы, чтобы мы могли быстро различать бычьи и медвежьи дни на наших графиках.
data.loc[data['Target'] > 0,'Class'] = 1
Теперь давайте укажем, какие действия предприняла бы наша стратегия.
data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1 data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1
И какую прибыль или убыток принесла бы наша стратегия, если бы мы предприняли эти действия.
data['Reward'] = data['Target'] * data['Action']
Теперь давайте заполним торговый сигнал, который подскажет нам, следует ли нам действовать или подождать.
data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1 data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1
Мы можем взглянуть на данные. Мы быстро увидим, что четко отделить рыночные движения довольно сложно. Оранжевые точки обозначают торговые сигналы, на которые нам следовало отреагировать, а синие — сигналы, которые нам следовало проигнорировать. Тот факт, что оранжевые и синие точки на этом графике накладываются друг на друга, математически указывает на то, что прибыльные и нерентабельные торговые сигналы могут формироваться практически в одинаковых условиях. Наши статистические модели могут выявлять сходства и различия, которые мы не способны распознать самостоятельно или для распознавания которых потребовались бы значительные трудозатраты.
import numpy as np import seaborn as sns import matplotlib.pyplot as plt sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal') plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market') plt.grid()

Рис. 1. Пересечения наших скользящих средних, по-видимому, испытывают трудности с разделением ценового движения
Давайте быстро оценим, проще ли прогнозировать нашу новую целевую задачу, чем традиционную.
from sklearn.linear_model import RidgeClassifier from sklearn.model_selection import TimeSeriesSplit,cross_val_score
Линейные модели особенно полезны для получения надежных и недорогих приближений. Давайте создадим объект для разделения временного ряда, чтобы мы могли провести перекрестную проверку линейного классификатора.
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = [] Проведем перекрестную проверку модели, сначала на исходной целевой переменной, а затем на новой целевой переменной, которая пытается оценить прибыль/убыток, генерируемый нашей стратегией. Поскольку мы выполняем задачу классификации, установим метод оценки равным accuracy (точность).
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy')))) scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))
Построение столбчатой диаграммы результатов быстро показывает, что модель, прогнозирующая прибыль/убыток стратегии, превосходит модель, пытающуюся напрямую прогнозировать рынок. Модель, пытавшаяся напрямую прогнозировать результаты, не достигла порога в 50%, в то время как наша новая целевая модель помогает нам преодолеть этот порог, пусть и в незначительном размере.
sns.barplot(scores,color='black') plt.axhline(np.max(scores),linestyle=':',color='red') plt.title('Forcasting Market Returns vs Forecasting Strategy Reward') plt.ylabel('Percentage Accuracy Levels %') plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

Рис. 2. Наши линейные модели показывают, что нам, возможно, будет выгоднее прогнозировать прибыль или убыток, полученные в результате нашей стратегии
Мы можем точно рассчитать процентное увеличение эффективности. Похоже, мы получаем прирост точности на 7,6%, прогнозируя взаимосвязь между стратегией и рынком, по сравнению с прямым прогнозированием рынка.
scores = (((scores / scores[0]) - 1) * 100) scores[1]
7.595993322203687
Удалим все данные, которые пересекаются с периодом нашего тестирования, чтобы наше тестирование представляло собой реальную имитацию реальных рыночных условий.
#Drop all the data that overlaps with your backtest period data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:] data

Рис. 3. Убедимся, что даты в нашем фрейме данных не перекрывают даты, по которым мы будем проводить тестирование
Линейная модель позволила нам с уверенностью утверждать, что прогнозирование прибыли/убытка, получаемого в результате применения стратегии, может быть для нас более эффективным, чем прямое прогнозирование цены. Однако в наших тестах торговых стратегий мы будем использовать более гибкий алгоритм обучения, чтобы гарантировать, что модель получает как можно больше полезной информации.
from sklearn.ensemble import GradientBoostingRegressor model = GradientBoostingRegressor()
Обозначим входные данные и целевое значение.
X = ['Open','High','Low','Close','MA Close 2','MA Open 2'] y = 'Trade Signal'
Подберем модель.
model.fit(data.loc[:,X],data.loc[:,y])
Подготовим к экспорту модель в ONNX.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Определим размер входных данных модели.
initial_types = [("float input",FloatTensorType([1,6]))]
Сохраним модель.
onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(onnx_proto,"EURUSD Reward Model.onnx")
Начало работы с MQL5
Мы готовы приступить к разработке нашего торгового приложения. Мы разработаем две версии стратегии, чтобы оценить эффективность изменений в процедуре обучения нашей статистической модели. Обе версии торгового алгоритма будут протестированы на исторических данных в идентичных условиях. Давайте ознакомимся с этими конкретными условиями, чтобы нам не приходилось без необходимости дублировать одну и ту же информацию.
Первая важная настройка — это символ, поскольку, как мы уже обсуждали, в этом примере мы торгуем валютной парой EURUSD. Мы будем торговать данным символом на дневном таймфрейме в течение 5 лет, с 1 января 2020 года по 1 апреля 2025 года. Это даст нам достаточно времени для оценки полезности нашей стратегии. Как вы помните, на рис. 3 мы фактически удалили все имеющиеся у нас рыночные данные за период после 29 декабря 2019 года.

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

Рис. 5. Настройки, которые мы будем использовать для имитации рыночных условий, имеют существенное значение
Теперь начнем создавать приложение, которое мы сможем протестировать, чтобы оценить целесообразность предложенных изменений в обучении. Начнем с оценки эффективности нашей стратегии без использования разработанного нами нового подхода к моделированию, позволяющего выявлять ситуации с высокой вероятностью успеха. По сути, первый вариант нашей торговой стратегии заключается в применении дискреционной торговой стратегии, заключающейся в торговле на пересечениях скользящих средних, когда они происходят. Это даст нам ориентир, с которым мы будем сравнивать предложенную нами стратегию моделирования вознаграждений. Для начала импортируем торговую библиотеку.
//+------------------------------------------------------------------+ //| Reward Modelling.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" //+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Определим полезные системные константы. Эти константы соответствуют другим константам, которые мы использовали как в скрипте MQL5, так и в скрипте Python.
//+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PERIOD 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- How far into the future we should forecast
Настроим наши глобальные переменные и технические индикаторы.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int fetch = HORIZON + 1; //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int ma_handle,ma_o_handle; double ma_reading[],ma_o_reading[]; int position_timer;
Каждому обработчику событий назначен соответствующий метод, который будет вызван при срабатывании обработчика. Такой стиль проектирования упростит поддержку и расширение вашего кода в будущем, если у вас появятся новые идеи. Написание кода непосредственно в обработчике событий потребует от разработчика тщательного анализа множества строк кода перед внесением каких-либо изменений, чтобы убедиться, что ничего не сломается. В то же время, при использовании нашего шаблона проектирования разработчику достаточно лишь добавить вызов нужной функции поверх уже предоставленных, если он хочет расширить функциональность приложения.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!setup()) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
Теперь рассмотрим каждый метод по очереди. Первой функцией, которую мы настроим, будет функция, отвечающая за загрузку наших технических индикаторов и сброс таймера позиции. Таймер позиции необходим для того, чтобы гарантировать удержание каждой сделки в течение всего времени, пока установлен постоянный коэффициент горизонта планирования.
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---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); position_timer = 0; return(true); }
Метод release просто освобождает технические индикаторы, которые мы не используем. В MQL5 рекомендуется выполнять очистку после работы.
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(ma_handle); IndicatorRelease(ma_o_handle); return; }
Метод update получает текущие уровни цен и копирует их в буферы наших индикаторов. Кроме того, система будет отслеживать, как долго открыта наша текущая позиция, чтобы закрыть ее вовремя.
//+------------------------------------------------------------------+ //| Update system parameters | //+------------------------------------------------------------------+ void update(void) { //--- Time stamps static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_D1,0); //--- We are on a new day if(time_stamp != current_time) { time_stamp = current_time; if(PositionsTotal() == 0) { //--- Copy indicator values CopyBuffer(ma_handle,0,0,fetch,ma_reading); CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading); //---Set the values as series ArraySetAsSeries(ma_reading,true); ArraySetAsSeries(ma_o_reading,true); find_setup(); position_timer = 0; } //--- Forecasts are only valid for HORIZON days if(PositionsTotal() > 0) { position_timer += 1; } //--- Otherwise close the position if(position_timer == HORIZON) Trade.PositionClose(Symbol()); } return; }
И наконец, функция find setup (поиск настроек). Наши настройки определяются всякий раз, когда мы наблюдаем пересечения скользящих средних. Если цена открытия пересекает цену закрытия сверху вниз, это регистрируется как сигнал на продажу. В противном случае, у нас будет сигнал на покупку.
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); vector ma_o,ma_c; ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1); ma_c.CopyIndicatorBuffer(ma_handle,0,0,1); if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } return; }
Не забудьте отменить определение системных констант, которые мы определили ранее.
//+------------------------------------------------------------------+ //| Undefine system constatns | //+------------------------------------------------------------------+ #undef HORIZON #undef MA_PERIOD #undef MA_TYPE
Мы уже рассматривали настройки, которые будем использовать для тестирования на исторических данных. Наша цель — оценить эффективность нашей стратегии без использования разработанных нами методов статистического моделирования. Просто загрузите советник, а затем запустите тестирование, используя настройки, описанные на рисунках 4 и 5.

Рис. 6. Установление эталонного значения
Наша версия торговой стратегии оказалась прибыльной, хотя и незначительно. Разница между средней прибылью и убытком составляет всего 0,9 доллара, и только 51% всех совершаемых сделок оказываются прибыльными, что не очень обнадеживает. Коэффициент Шарпа составляет 0,62. Возможно, его удастся, если мы модернизируем нашу систему.

Рис. 7. Подробный анализ эффективности нашей дискреционной торговой стратегии
Теперь, проанализировав кривую баланса и эквити, полученную в результате применения этой версии торговой стратегии, мы сразу же увидим очевидные недостатки. Стратегия нестабильна и изменчива. Фактически, спустя 4 года после начала тестирования стратегии, к февралю 2024 года, баланс практически вернулся к исходному значению. Она пыталась вырваться из убыточной серии сделок, начавшейся в конце 2020 года и продолжавшейся 4 года, вплоть до 2024 года. Для нас, как для алгоритмических трейдеров, это неважный результат.

Рис. 8. Визуализация кривой прибыли и убытков, построенной на основе нашей версии торговой стратегии
Повышение эффективности работы советника
Теперь давайте усовершенствуем нашу торговую стратегию, наделив наше приложение возможностью имитировать человеческий мыслительный процесс, заключающийся в обдумывании последствий своих действий, прежде чем принимать решение.
Для начала импортируем нашу модель ONNX в приложение в качестве ресурса из системного файлового каталога.
//+------------------------------------------------------------------+ //| System resources | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];
Для использования нашей модели потребуется добавить несколько дополнительных глобальных переменных. В первую очередь нам нужны переменные для представления обработчика модели и объема данных, которые мы должны получить.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int fetch = HORIZON + 1;
Далее нам необходимо настроить модель ONNX в функции setup. В приведенном ниже примере кода мы намеренно опустили фрагменты кода, которые не изменились в обеих версиях приложения. Мы просто создаем модель ONNX из ее буфера, проверяем модель, а затем соответствующим образом указываем размер входных и выходных данных. Если какой-либо этап процесса завершится неудачей, мы полностью прервем инициализацию.
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---Omitted code that hasn't changed //--- Setup the ONNX model onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT); //--- Validate the ONNX model if(onnx_model == INVALID_HANDLE) { Comment("Failed to create ONNX model"); return(false); } //--- Register the ONNX model I/O parameters ulong input_shape[] = {1,6}; ulong output_shape[] = {1,1}; if(!OnnxSetInputShape(onnx_model,0,input_shape)) { Comment("Failed to set input shape"); return(false); } if(!OnnxSetOutputShape(onnx_model,0,output_shape)) { Comment("Failed to set output shape"); return(false); } return(true); }
Кроме того, после завершения работы приложения нам потребуется освободить системные ресурсы, которые мы больше не используем.
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { //--- Omitted code segments that haven't changed OnnxRelease(onnx_model); return; }
Мы дадим приложению указание сначала получить прогноз от нашей торговой модели, прежде чем принимать решение о совершении сделки. Если прогноз нашей модели превышает 0,5, это означает, что наш алгоритм ожидает, что сигнал, сгенерированный нашей стратегией, будет прибыльным, и дает нам разрешение на совершение сделки. Если это не так, то мы подождем, пока неблагоприятные рыночные условия не пройдут.
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { //--- Skipped parts of the code base that haven't changed //--- Prepare the model's inputs vectorf model_input(6); model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0) - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0) - iLow(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0) - iClose(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]); model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]); //--- Prepare the model's output vectorf model_output(1); //--- We failed to run the model if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output)) Comment("Failed to obtain a forecast"); //--- Everything went fine else { Comment("Forecast: ",model_output[0]); //--- Our model forecasts that our strategy is likely to be profitable if(model_output[0] > 0.5) { if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } } } return; }
Для корректного сравнения двух стратегий запустите приложение в режиме тестирования на истории, используя настройки, указанные на рисунках 4 и 5.

Рис. 9. Проведение второго тестирования на истории с использованием нашей пересмотренной версии торговой стратегии, моделирующей прибыль и убытки
Наш коэффициент Шарпа увеличился с 0,62 в контрольном показателе, рассчитанном по той же стратегии, до 1,07 в текущей версии, что представляет собой увеличение на 72%. Наша общая чистая прибыль выросла на 38% — с USD 117,13 до 162,75, благодаря применению новой алгоритмически определенной стратегии "высоковероятной ситуации". Доля убыточных сделок снизилась на 17% — с 48,78% до 40,48%. И наконец, общее количество сделок, совершенных с помощью этой стратегии, сократилось со 164 до 126, что означает, что наша новая система принесла на 38% больше прибыли, используя всего 76% от общего числа сделок старой системы. Фактически, это означает, что мы стали эффективнее, поскольку получаем большую прибыль, принимая на себя меньший риск.

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

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

Рис. 12. Кривая доходности, построенная на основе исходной версии нашей торговой стратегии, скопирована для более удобного сравнения с новыми результатами
Заключение
Мы рассмотрели новый подход к задаче алгоритмической торговли с использованием статистических моделей с обучением с учителем. Теперь читатель обладает знаниями, необходимыми для моделирования взаимосвязей между своими частными торговыми стратегиями и рынками, на которых он торгует. Это дает читателю конкурентное преимущество перед участниками рынка, пытающимися напрямую прогнозировать рынок, что, как мы показали, не всегда является лучшим вариантом.
| Имя файла | Описание файла |
|---|---|
| Reward Modelling Benchmark.mq5 | Традиционная версия нашей торговой стратегии, которая не предполагает учета последствий своих действий. Она учитывает все торговые возможности в равной степени и исходит из предположения, что каждая сделка должна быть прибыльной. |
| Reward Modelling.mq5 | Усовершенствованная версия торговой стратегии, которая целенаправленно оценивает последствия своих действий перед совершением каких-либо сделок. |
| EURUSD Reward Model.onnx | Статистическая модель ONNX оценивает вероятность того, что сигнал, сгенерированный нашей стратегией, окажется прибыльным. |
| Reward Modelling.ipynb | Jupyter Notebook для анализа исторических данных о рынке и построения статистической модели. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17756
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Создание торговой панели администратора на MQL5 (Часть X): Интерфейс из внешних ресурсов
Нейросети в трейдинге: Потоковые модели с остаточной высокочастотной адаптацией (Окончание)
Как создать и адаптировать RL-агент с LLM и квантовым кодированием в алгоритмическом трейдинге на MQL5
Трейдинг с экономическим календарем MQL5 (Часть 7): Подготовка к тестированию стратегий с анализом новостей
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования