Переосмысливаем классические стратегии (Часть 20): Современная интерпретация стохастического осциллятора
Стохастический осциллятор - это хорошо известный технический индикатор, традиционно используемый для определения потенциальных разворотов рынка. В своей классической форме он измеряет динамику движения цены в пределах определенного диапазона. Когда ценовое движение достигает экстремальных значений, рынок обычно считается перекупленным или перепроданным. Согласно этой традиционной интерпретации, трейдеры ищут возможности для продажи в условиях перекупленности и возможности для покупки в условиях перепроданности, исходя из предположения, что цены в конечном итоге вернутся к равновесию.
Этот подход успешно используется на протяжении многих лет. Однако в этой статье предполагается, что у стохастического осциллятора, возможно, есть недооцененные возможности — в частности, он может более эффективно функционировать как индикатор следования за трендом, а не исключительно как инструмент для торговли на возврате к среднему. Мы показываем, что при небольших корректировках правил интерпретации осциллятор может быть перепрофилирован в полезный метод определения доминирующих рыночных трендов.
Чтобы подтвердить это, мы пересматриваем сигналы индикатора и бросаем вызов классическим правилам. Мы представляем альтернативную систему, которая поощряет покупку в условиях перекупленности и продажу в условиях перепроданности — идея, которая на первый взгляд может показаться нелогичной. Тем не менее, как мы демонстрируем, стохастический осциллятор более универсален, чем принято считать, и не ограничен каким-то одним способом интерпретации.
В ходе более раннего анализа мы также обнаружили, что осциллятор был более предсказуемым, чем непосредственные ценовые движения. Это понимание побудило нас глубже изучить индикатор в поисках дополнительных, ранее не использованных значений.
Чтобы полностью разобраться в этом вопросе, позвольте представить пять различных версий основанной на стохастике стратегии. Хотя наша последняя попытка извлечь максимальное значение из индикатора не увенчалась успехом, этот результат подчеркивает ключевой момент: четыре из пяти стратегий показали хорошие результаты. Это подсказывает нам пересмотреть то, насколько хорошо мы на самом деле понимаем индикатор, который, по мнению многих трейдеров, они уже знают.
Поскольку мы оцениваем несколько версий стратегии, некоторые параметры должны оставаться согласованными во всех из них. Каждая версия будет совершать сделки по одной за раз, гарантируя, что различия в прибыльности будут вызваны только изменениями в правилах торговли. Все стратегии будут использовать одинаковый размер позиции, и каждый бэктест будет проводиться в течение идентичного исторического периода с 2021 по 2025 год.

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

Рисунок 2: Условия тестирования, в которых мы будем тестировать наши стратегии, имитируют реальные рыночные условия
Установление базового уровня
Начнем с определения важных глобальных переменных, которые понадобятся для нашего обсуждения.
//+------------------------------------------------------------------+ //| Stochastic Strategy.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int stoch_handler,atr_handler; double stoch_main_reading[],stoch_signal_reading[],atr_reading[]; double bid,ask;
Затем импортируем торговую библиотеку Trade, которая поможет в управлении нашими позициями.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Когда торговая система инициализируется в первый раз, мы определяем технические индикаторы, необходимые для нашей стратегии.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators atr_handler = iATR(Symbol(),PERIOD_D1,14); stoch_handler = iStochastic(Symbol(),PERIOD_D1,5,3,3,MODE_SMA,STO_LOWHIGH); //--- return(INIT_SUCCEEDED); }
Когда торговая система больше не используется, мы высвобождаем технические индикаторы, на которые полагались.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(atr_handler); IndicatorRelease(stoch_handler); }
Каждый раз при поступлении новых уровней цен обновляются соответствующие индикаторные буферы и глобальные переменные. Сначала мы реализуем традиционную интерпретацию стохастического осциллятора: покупка в условиях перепроданности и продажа в условиях перекупленности.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Keep track of the time datetime current_time = iTime(Symbol(),PERIOD_D1,0); static datetime time_stamp; if(current_time != time_stamp) { //--- Update the time time_stamp = current_time; //--- Update our technical indicators CopyBuffer(stoch_handler,0,0,1,stoch_main_reading); CopyBuffer(stoch_handler,1,0,1,stoch_signal_reading); CopyBuffer(atr_handler,0,0,1,atr_reading); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); //--- Trading rules if(PositionsTotal() == 0) { if(stoch_main_reading[0] < 20) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2))); if(stoch_main_reading[0] > 80) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2))); } } } //+------------------------------------------------------------------+
Кривая эквити, полученная с помощью этого эталона, является волатильной и почти не внушает доверия к надежности предложенной стратегии. Обычно от такой стратегии отказываются, но мы предполагаем, что надежда не потеряна.

Рисунок 3: Кривая эквити, полученная в результате традиционной интерпретации стохастического осциллятора, представляется ненадежной
Кроме того, стратегия приводила к убыточным сделкам в 49% случаев. Это приводило к снижению эквити инвесторов за период проведения бэктеста.

Рисунок 4: Подробная статистика классической стратегии оставляет значительные возможности для усовершенствования
Усовершенствования по сравнению с базовым уровнем
В предлагаемом нами решении мы повторно интерпретируем индикатор как инструмент идентификации тренда. Доминирующий тренд, который мы определяем на любом рынке, будет определяться тем, где находятся текущие цены относительно наблюдаемого рыночного диапазона. Мы начинаем с создания векторов для записи ранее наблюдавшихся максимумов и минимумов.
vector high,low; Затем вычислим среднюю точку торгового диапазона за предыдущие 90 дневных свечей. Значение 90 выбрано произвольно, поскольку оно соответствует типичным бизнес-циклам институциональных участников рынка, доминирующих на рынке Форекс.
//--- Calculate the middle of the trading range high.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_HIGH,0,90); low.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_LOW,0,90); double mid = ((high.Mean() + low.Mean())/2);
Если ни одна позиция не открыта, мы сначала стараемся открывать длинные позиции на уровнях перекупленности. Кроме того, нам требуется дополнительное подтверждение, если мы увидим цену закрытия выше средней точки наблюдаемого диапазона максимумов и минимумов.
//--- Trading rules if(PositionsTotal() == 0) { if((stoch_main_reading[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid)) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2))); if((stoch_main_reading[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid)) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2))); }
Кривая эквити, построенная в этих условиях, теперь демонстрирует существенный рост по сравнению с волатильным и неэффективным эталоном, с которого мы начинали.

Рисунок 5: Кривая эквити, полученная с помощью нашей усовершенствованной торговой системы стохастического осциллятора
Полученные подробные статистические данные свидетельствуют о значительном улучшении ситуации. Общая чистая прибыль резко возросла до 184,35 долларов по сравнению с контрольным результатом в -26,22 долларов. Кроме того, доля прибыльных сделок выросла с 49% при первоначальной попытке до 55% в этот раз.

Рисунок 6: Подробные статистические данные, полученные нами в результате работы пересмотренной стратегии, демонстрируют, что внесенные нами изменения были значимыми
Достижение более высоких уровней эффективности
Мы можем внести больше существенных усовершенствований в стратегию, тщательно проводя анализ свечей на более младших таймфреймах. Причина в том, что тренд, наблюдаемый на дневном таймфрейме, также должен отражаться в спреде между ценами открытия и закрытия на более младшем таймфрейме. Это поможет нам определить значимые точки входа.
vector open,close; На 1-часовом таймфрейме мы скопируем последние 12 свечей, чтобы определить доминирующий тренд дня.
//--- Calculate the current trend on the lower time frame open.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_OPEN,0,12); close.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_CLOSE,0,12);В совокупности наши пересмотренные торговые правила образуют фильтр, состоящий из трех требований. Каждое из этих требований подкрепляет идею о том, что на рынке может преобладать один доминирующий тренд.
//--- Trading rules if(PositionsTotal() == 0) { if((stoch_main_reading[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid) && (open.Mean() < close.Mean())) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2))); if((stoch_main_reading[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid) && (open.Mean() > close.Mean())) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2))); }
Кривая эквити, полученная в результате работы этой усовершенствованной стратегии, теперь растет более плавно, в отличие от неровной и волатильной кривой эквити, полученной в результате нашей первоначальной попытки.

Рисунок 7: Результаты нашей пересмотренной стратегии значительно превосходят наши предыдущие лучшие показатели
Общая чистая прибыль далее увеличилась до 223 долларов, в то время как коэффициент Шарпа улучшился до 0,88 с предыдущего значения в 0,69. Общее количество сделок сократилось со 123 до 118. Очевидным признаком повышения эффективности является способность достигать той же цели с меньшими усилиями. Внесенные изменения, по-видимому, успешно привели к достижению этого результата. Кроме того, процент выигрышных сделок вырос до нового максимума - 56%.

Рисунок 8: Подробные результаты, полученные с помощью третьей итерации нашей стратегии стохастических осцилляторов
Алгоритмическое обнаружение торговых правил с помощью стохастического осциллятора
До сих пор в ходе нашего обсуждения мы вручную определяли торговые правила и рыночные фильтры для руководства открытием сделок. Хотя этот процесс является ценным упражнением в творческом мышлении и рыночных рассуждениях, существует естественный предел того, как далеко может завести нас одна только человеческая интуиция.
В рыночных данных могут существовать значимые закономерности, правила и логика принятия решений, которые не сразу понятны интуитивно или легко распознаются человеческим разумом. Чтобы исследовать эту возможность, мы теперь обратим наше внимание на алгоритмические методы обнаружения дополнительных правил, в частности, правил интерпретации стохастического осциллятора.
Для этого мы напишем скрипт на MQL5, который извлекает исторические рыночные данные по паре EUR/USD вместе со значениями индикатора Stochastic Oscillator. Затем эти данные будут экспортированы в формате CSV. Набор данных будет включать в себя стандартные ценовые ряды открытия, максимума, минимума и закрытия (OHLC), за которыми следуют буферы стохастического индикатора: линия %K (основная) и линия %D (сигнальная).
Наконец выполним инженерию признаков стохастических данных вручную, чтобы обогатить набор данных. Это включает в себя вычисление средней точки между линиями %K и %D, измерение расстояния между основным показанием и пороговыми уровнями 80 и 20 и получение дополнительных наблюдений, которые помогают уловить поведение индикатора. Вместе эти признаки образуют многомерное представление стохастического осциллятора, позволяющее проводить более сложный алгоритмический анализ.
//+------------------------------------------------------------------+ //| Fetch Data Stochastic 2 | //| 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 //--- File name string file_name = Symbol() + " Stochastic Strategy.csv"; int stoch_handler = iStochastic(Symbol(),PERIOD_CURRENT,5,3,3,MODE_EMA,STO_LOWHIGH); double stoch_main[],stoch_signal[]; double stoch_o,stoch_h,stoch_l; //--- Amount of data requested input int size = 365; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { //--- Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); //--- CopyBuffer(stoch_handler,0,0,size,stoch_main); stoch_o = stoch_main[0]; stoch_h = stoch_main[0]; stoch_l = stoch_main[0]; ArraySetAsSeries(stoch_main,true); CopyBuffer(stoch_handler,1,0,size,stoch_signal); ArraySetAsSeries(stoch_signal,true); for(int i=size;i>=1;i--) { if(i == size) { FileWrite(file_handle, //--- Time "Time", //--- OHLC "Open", "High", "Low", "Close", //--- Stochastic Readings "Stochastic Main", "Stochastic Signal", //--- Feature Engineering Stochastic Oscilator "Stoch Main - Signal", "Stoch M-S Mid", "Stoch - 80", "Stoch - 20", "Stoch O", "Stoch H", "Stoch L", "Stoch O-C", "Stoch H-C", "Stoch L-C" ); } else { //--- Set features stoch_h = (stoch_h < stoch_main[i]) ? stoch_main[i] : stoch_h; stoch_l = (stoch_l > stoch_main[i]) ? stoch_main[i] : stoch_l; FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), //--- OHLC iOpen(_Symbol,PERIOD_CURRENT,i), iHigh(_Symbol,PERIOD_CURRENT,i), iLow(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), //--- Stochastic Readings stoch_main[i], stoch_signal[i], //--- Stochastic Feature Engineering stoch_main[i] - stoch_signal[i], ((stoch_main[i] + stoch_signal[i])/2), (stoch_main[i] - 80), (stoch_main[i] - 20), stoch_o, stoch_h, stoch_l, stoch_o - stoch_main[i], stoch_h - stoch_main[i], stoch_l - stoch_main[i] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Анализ наших рыночных данных на Python
Теперь, когда мы записали наш CSV-файл с историческими рыночными данными, мы готовы проанализировать данные на Python. Сначала импортируем необходимые нам библиотеки Python.
import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt
Затем считаем записанный нами CSV-файл.
data = pd.read_csv("./EURUSD Stochastic Strategy.csv")
data Отделим обучающий набор наблюдений от тестового набора, который зарезервирован для бэктестинга в MetaTrader 5.
train = data.iloc[:-(365 * 5),:] test = data.iloc[-(365 * 5):,:]
Загрузим нужные нам библиотеки машинного обучения.
from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score,TimeSeriesSplit
Определим горизонт прогнозирования.
HORIZON = 5 Создадим объект кросс-валидации временных рядов, который поможет нам оценить точность каждой рассматриваемой модели.
tscv = TimeSeriesSplit(n_splits=10,gap=HORIZON) Пометим наш набор данных. Мы заинтересованы в прогнозировании показаний основного стохастического осциллятора.
data['Target'] = data['Stochastic Main'].shift(-HORIZON) data = data.iloc[:-HORIZON,:]
Давайте сравним точность, полученную при использовании классических столбцов OHLC, новых стохастических признаков, которые мы разработали, и, наконец, их комбинации.
X_classic = data.iloc[:,1:7].columns X_new = data.iloc[:,7:-1].columns X_all = data.iloc[:,1:-1].columns y = 'Target'
Мы будем вести учет наблюдаемых уровней точности по мере изменения входных параметров, которые мы вводим в нашу модель.
scores = []
Сохраним базовую модель неизменной, чтобы гарантировать, что изменения в погрешности происходят на основе выбранных нами входных параметров.
model = LinearRegression()
Запишем точность, связанную с каждым возможным набором доступных нам входных параметров.
scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_classic],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error')))) scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_new],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error')))) scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_all],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error'))))
При строительстве графика уровней точности, полученных с помощью различных возможных наборов входных параметров, мы четко видим, что разработанные нами пользовательские признаки стохастического осциллятора обеспечивают самые низкие уровни погрешности, которые мы нашли возможными.
sns.barplot(np.abs(scores),color='black') plt.axhline(np.min(scores),linestyle=':',color='red') plt.xticks([0,1,2],['OHLC Features','Custom Features','All Features'])

Рисунок 9: Созданные нами пользовательские признаки помогли нам наилучшим образом спрогнозировать основной буфер стохастического осциллятора
В большинстве динамичных процессов не все регистрируемые переменные одинаково информативны. Определение того, какие переменные содержат больше всего информации, может помочь в будущем построении признаков, позволяя нам сосредоточиться на создании более богатых и разнообразных вариаций наиболее важных признаков. Чтобы количественно оценить это, мы используем метод оценки взаимной информации для регрессионной задачи. Взаимная информация (MI) - это показатель статистической зависимости, который отражает как линейные, так и нелинейные взаимосвязи, что делает его хорошо подходящим для оценки любой формы зависимости между двумя переменными.
from sklearn.feature_selection import mutual_info_regression
Выполним статистический тест. Для тестирования требуются новые признаки стохастического осциллятора, которые мы сгенерировали, и текущее целевое значение, которое мы выбрали.
scores = mutual_info_regression(data.loc[:,X_new],data.loc[:,'Target'])
Похоже, что из 10 пользовательских признаков, которые мы создали, только 3, по-видимому, не имеют никакого отношения к целевому значению. Об этом говорят значения MI для некоторых признаков, близкие к нулю. Таким образом, похоже, что существует значимая взаимосвязь, которую мы обнаружили алгоритмически на основе данных, созданных в нашем MQL5-скрипте.
sns.barplot(scores,color='black') plt.axhline(np.mean(scores),color='red',linestyle=':')

Рисунок 10: Показатели MI, которые мы наблюдали, говорят нам о том, что созданный нами набор данных имеет смысл и взаимосвязь, которые мы можем изучить
Экспортирование в ONNX
Теперь мы готовы экспортировать нашу обученную статистическую модель в формат Open Neural Network Exchange, также известный как ONNX. ONNX позволяет внедрять модели машинного обучения, не требуя использования оригинальной платформы обучения или языка программирования, что делает их переносимыми на разные платформы и среды. Для начала загрузим библиотеки ONNX, необходимые для этого процесса.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Укажем форму входа нашей модели.
initial_types = [('float input',FloatTensorType([1,len(X_new)]))]
Обучим модель на обучающих данных.
model = GradientBoostingRegressor()
model.fit(train.loc[:,X_new],train.iloc[:,-1]) Сохраним модель как прототип ONNX.
onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) Сохраним прототип ONNX на диск в виде ONNX-файла.
onnx.save(onnx_proto,'EURUSD Stochastic GBR AI.onnx')
Реализация усовершенствований
Давайте теперь изменим эту торговую стратегию, включив в нее наш ONNX-файл.
//+------------------------------------------------------------------+ //| Stochastic AI.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD Stochastic GBR AI.onnx" as const uchar onnx_proto[];
Определим глобальные переменные, которые будем использовать для управления нашей моделью ONNX в нашей торговой системе.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vectorf model_inputs,model_outputs; long model;
Укажем системные константы. Эти константы будут определять количество входных и выходных параметров, которые будет иметь наша модель.
//+------------------------------------------------------------------+ //| System Definitions | //+------------------------------------------------------------------+ #define MODEL_INPUT_SHAPE 10 #define MODEL_OUTPUT_SHAPE 1
Когда наша торговая система будет инициализирована, мы загрузим свои технические индикаторы, а также будем отслеживать пользовательские признаки стохастического осциллятора, которые мы создали в обучающем наборе, такие как максимальные и минимальные значения за все время. Затем мы настроим нашу ONNX-модель и убедимся, что она настроена надлежащим образом и работает.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our indicators atr_handler = iATR("EURUSD",PERIOD_D1,14); stoch_handler = iStochastic(Symbol(),PERIOD_CURRENT,5,3,3,MODE_EMA,STO_LOWHIGH); stoch_o = 22.69153; stoch_h = 98.551023; stoch_l = 1.372058; //--- Setup the ONNX model model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT); //--- Define the model parameter shape ulong input_shape[] = {1,MODEL_INPUT_SHAPE}; ulong output_shape[] = {1,MODEL_OUTPUT_SHAPE}; if(!OnnxSetInputShape(model,0,input_shape)) { Print("ONNX Model Error: Incorrect Input Shape ",GetLastError()); return(INIT_FAILED); } if(!OnnxSetOutputShape(model,0,output_shape)) { Print("ONNX Model Error: Incorrect Output Shape ",GetLastError()); return(INIT_FAILED); } model_inputs = vectorf::Zeros(MODEL_INPUT_SHAPE); model_outputs = vectorf::Zeros(MODEL_OUTPUT_SHAPE); if(model != INVALID_HANDLE) { return(INIT_SUCCEEDED); } //--- return(INIT_FAILED); }
Когда наша торговая система больше не будет использоваться, мы освобождаем технические индикаторы и модель ONNX, которые больше не используем.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free up memory we are no longer using when the application is off IndicatorRelease(atr_handler); IndicatorRelease(stoch_handler); OnnxRelease(model); }
Если будут получены новые уровни цен, мы выполним наши правила торговли вручную, а затем дополним их торговыми сигналами, которым обучена наша статистическая модель.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- When price levels change datetime current_time = iTime("EURUSD",PERIOD_D1,0); static datetime time_stamp; //--- Update the time if(current_time != time_stamp) { time_stamp = current_time; //--- Calculate the middle of the trading range produced over the last business cycle high.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_HIGH,0,90); low.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_LOW,0,90); double mid = ((high.Mean() + low.Mean())/2); //--- Calculate the current trend on the lower time frame open.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_OPEN,0,12); close.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_CLOSE,0,12); //--- Fetch indicator current readings CopyBuffer(atr_handler,0,0,1,atr_reading); CopyBuffer(stoch_handler,0,0,1,stoch_main); CopyBuffer(stoch_handler,1,0,1,stoch_signal); //--- Setting model inputs stoch_h = (stoch_h < stoch_main[0]) ? stoch_main[0] : stoch_h; stoch_l = (stoch_l > stoch_main[0]) ? stoch_main[0] : stoch_l; model_inputs[0] = (float)(stoch_main[0] - stoch_signal[0]); model_inputs[1] = (float)(((stoch_main[0] + stoch_signal[0])/2)); model_inputs[2] = (float)((stoch_main[0] - 80)); model_inputs[3] = (float)((stoch_main[0] - 20)); model_inputs[4] = (float)(stoch_o); model_inputs[5] = (float)(stoch_h); model_inputs[6] = (float)(stoch_l); model_inputs[7] = (float)(stoch_o - stoch_main[0]); model_inputs[8] = (float)(stoch_h - stoch_main[0]); model_inputs[9] = (float)(stoch_l - stoch_main[0]); ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK); bid = SymbolInfoDouble("EURUSD",SYMBOL_BID); //--- If we have no open positions if(PositionsTotal() == 0) { if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs))) { Comment("Failed to obtain a forecast from our model: ",GetLastError()); } else { Comment("Forecast: ",model_outputs); //--- Trading rules if((model_outputs[0] > stoch_main[0]) && (stoch_main[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid) && (open.Mean() < close.Mean())) { //--- Buy signal Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),""); } else if((model_outputs[0] < stoch_main[0]) && (stoch_main[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid) && (open.Mean() > close.Mean())) { //--- Sell signal Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),""); } } } } } //+------------------------------------------------------------------+
Наконец, удалим определение всех системных определений, которые вводим в MQL5. Это обычная процедура для разработчиков.
#undef MODEL_INPUT_SHAPE #undef MODEL_OUTPUT_SHAPE
Когда мы наблюдаем кривую эквити, полученную в результате нашей пересмотренной стратегии, нам кажется, что мы добавили слишком много шума в систему. Кривая утратила свой прежний плавный восходящий тренд и теперь выглядит более волатильной, чем мы бы предпочли.

Рисунок 11: Кривая эквити, которую мы получили в результате последней итерации нашей торговой стратегии, содержит зашумленные сигналы
Хотя стратегия остается прибыльной, ее эффективность значительно снизилась по сравнению с предыдущим пиком в 223 доллара. Это не означает, что стохастический осциллятор не имеет ценности в качестве основы для статистических торговых стратегий; скорее, это подчеркивает необходимость более тщательной и скрупулезной методологии со стороны практикующего трейдера. Кроме того, мы можем наблюдать, что стратегия не открывала длинных сделок и у модели сформировалось смещение в пользу коротких сделок.
Для новых читателей эти результаты могут показаться неожиданными. При разработке нашей статистической модели мы наблюдали явные усовершенствования в показателях ошибок с использованием пользовательских стохастических признаков. Однако читатели, знакомые с предыдущими материалами, уже видели такую картину.
Как показано в нашей серии статей, Преодоление ограничений машинного обучения (Часть 1): Из-за недостаточного количества совместимых показателей статистические показатели, используемые для обучения моделей, часто не отражают реальные цели торговли. Следовательно, усовершенствования статистической погрешности не приводят к достоверному повышению эффективности торговли.
На практике современное статистическое моделирование часто представляет собой процесс, сродни методу проб и ошибок. Поэтому читатели, которые добиваются плохих результатов в торговле, несмотря на тщательный анализ, не должны интерпретировать это как отражение недостатка у них навыков. Ссылка на соответствующую статью находится здесь. На данный момент разумно заключить, что чрезмерный шум, возможно, был непреднамеренно внесен в торговую систему, и поэтому мы возвращаемся к версии 3 как к наиболее эффективной версии торговой системы.

Рисунок 12: Подробный статистический анализ нашей последней итерации нашей стохастической торговой стратегии
Заключение
В этой статье демонстрируется, как классический технический индикатор может применяться не только в рамках традиционного использования. Читатели получают представление о том, как знакомые стратегии могут принести новую ценность, если взглянуть на них через другую аналитическую призму, а также как новые парадигмы и правила торговли могут появиться в результате продуманного процесса проб и ошибок. В конечном счете, каждый технический индикатор в терминале MetaTrader 5 содержит скрытый потенциал. Задача заключается в автоматическом выявлении значимых интерпретаций, которые остаются скрытыми от посторонних глаз.
| Название файла | Описание файла |
|---|---|
| Stochastic_Strategy.mq5 | Традиционная версия стратегии стохастического осциллятора. Она оказалась убыточной во время бэктестинга. |
| Stochastic_Strategy_2.mq5 | Наша первая итерация торговой системы, которая использовала дневной диапазон для определения четких направлений тренда. |
| Stochastic_Strategy_3.mq5 | Самая прибыльная версия нашей торговой системы, в которой в дополнение к предыдущим изменениям использовался анализ более младших таймфреймов. |
| Stochastic_AI.mq5 | Вторая по прибыльности версия нашей торговой стратегии, по-видимому, содержала слишком много шума в своих сигналах. |
| Stochastic_Strategy.ipynb | Ноутбук Jupyter, который мы написали для проведения анализа наших исторических рыночных данных по паре EURUSD и пользовательских признаков. |
| Fetch_Data_Stochastic_2.mq5 | Скрипт в MQL5, который мы написали для получения данных OHLC по паре EURUSD и других пользовательских наблюдений на стохастическом осцилляторе. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20530
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Принятие торговых решений с учётом неопределённости (Окончание)
От начального до среднего уровня: Объекты (III)
Разработка инструментария для анализа Price Action (Часть 61): Структурные пробои наклонных трендовых линий с подтверждением по трем свингам
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования