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

Индикаторы на Python
В Python имеется множество библиотек для технического анализа, которые можно легко импортировать и использовать для реализации различных индикаторов. Проблема заключается в разнообразии стандартов и отсутствии определенных индикаторов в ряде библиотек. Например, в библиотеке технического анализа pandas отсутствует осциллятор DeMarker, хотя он доступен в модуле 'ta', или модуле технического анализа. С другой стороны, конверты хотя и присутствуют в библиотеке технического анализа pandas, отсутствует в библиотеке ta в своем исходном виде. Вместо них ta предлагает связанные индикаторы полос Боллинджера и канала Дончиана.
Самое забавное, что разработка собственного индикатора "с нуля" в данный момент требует примерно столько же усилий (если не меньше), сколько установка одной из этих библиотек для использования стандартных функций. Таким образом, мы реализуем собственные функции для осциллятора Демарка и конвертов, что, теоретически, должно позволить нашему Python работать немного быстрее благодаря меньшему количеству ссылок на модули.
Индикатор Демарка
Индикатор Демарка — это осциллятор, отслеживающий состояния перекупленности и перепроданности актива за определенный период времени. Как уже упоминалось в предыдущей статье, значение находится в диапазоне от 0 до 1, при этом значения выше 0,7 указывают на перекупленность, а значения ниже 0,3 — на перепроданность. Реализация в виде пользовательской функции на Python выглядит так:
def DeMarker(df, period=14): """ Calculate DeMarker indicator for a DataFrame with OHLC prices Args: df: Pandas DataFrame with columns ['open', 'high', 'low', 'close'] period: Lookback period (default 14) Returns: Pandas Series with DeMarker values """ # Calculate DeMax and DeMin demax = df['high'].diff().clip(lower=0) demin = -df['low'].diff().clip(upper=0) # Smooth the values using SMA demax_sma = demax.rolling(window=period).mean() demin_sma = demin.rolling(window=period).mean() # Calculate DeMarker demarker = demax_sma / (demax_sma + demin_sma) return pd.DataFrame({'main': demarker})
Входными данными для нашей функции являются датафреймы df и period. df — это датафрейм pandas, содержащий данные о ценах открытия, максимума, минимума и закрытия, а period — это период анализа для вычислений осциллятора Демарка. Функция инкапсулирует логику осциллятора Демарка, обеспечивая при этом модульность и возможность повторного использования.
Расчет DeMax показывает разницу между последовательными высокими ценами (df[‘high’].diff()). Если разница в этом буфере положительная, что указывает на рост максимумов, значение остается в буфере, в противном случае значение обнуляется. Обнуление выполняется функцией clip (clip(lower=0)). Аналогично, если разница в минимальных ценах отрицательная (т.е. текущий минимум ниже предыдущего минимума), это значение сохраняется в буфере как абсолютное, в противном случае оно также обнуляется путем обрезки (clip(upper=0)).
Отслеживание этих абсолютных изменений важно, поскольку DeMax измеряет восходящую динамику цен, отмечая рост максимальных цен, в то время как DeMin измеряет нисходящую динамику цен, регистрируя снижение минимальных цен. Эти два показателя являются ключевыми для осциллятора Демарка, поскольку они определяют, насколько сильно цена изменяется относительно предыдущих периодов. В Python при использовании функции diff() крайне важно убедиться, что входной датафрейм содержит достаточное количество точек, и помнить, что первая строка всегда будет содержать значение NaN.
После этого мы сглаживаем значения в каждом буфере. Сглаживание уменьшает шум в каждом ряду данных и делает значения индикатора менее чувствительными к краткосрочным колебаниям цен. Метод rolling(window=period).mean() вычисляет среднее значение за указанный период, что вносит задержку, которая часто соответствует назначению индикатора — выявлению макротрендов. Начальные значения периода 1 будут иметь значение NaN из-за недостатка данных для скользящего окна, и их следует обработать путем удаления или заполнения нормализованными значениями. Выбор периода также влияет на чувствительность осциллятора Демарка: чем короче период, тем выше чувствительность.
Затем мы вычисляем значение осциллятора, разделив среднее значение DeMax на его значение плюс среднее значение DeMin. Это нормализует значение индикатора до диапазона от 0 до 1, что упрощает интерпретацию. По сути, это соотношение измеряет относительную силу восходящих движений цен по отношению к общей силе как восходящих, так и нисходящих движений цен. Значения, близкие к 1, указывают на сильный бычий импульс, а значения, близкие к 0, подразумевают более сильный медвежий импульс.
При реализации крайне важно исключить деление на ноль на этом этапе. Однако это редкое явление, поскольку цены постоянно меняются, особенно на пиках и впадинах. Затем функция возвращает значения осциллятора Демарка в виде датафрейма pandas с одним столбцом, который мы решили обозначить как main. Формат датафрейма обеспечивает совместимость с другими аналитическими инструментами на основе pandas, а также позволяет легко интегрировать их в более крупную торговую систему.
Конверты
Ценовой конверт (price envelope) — индикатор поддержки/сопротивления, который строит две полосы, верхнюю и нижнюю, вокруг скользящей средней цены. Полосы смещены на фиксированную величину отклонения в процентах, что может помочь трейдерам получить приблизительные расположения зон поддержки и сопротивления, когда цена касается этих полос. Реализация в Python:
def Envelopes(df, period=14, deviation=0.1): """ Calculate Price Envelopes for a DataFrame with OHLC prices Args: df: Pandas DataFrame with columns ['open', 'high', 'low', 'close'] period: MA period (default 20) deviation: Percentage deviation from MA (default 0.05 for 5%) Returns: DataFrame with columns ['upper', 'ma', 'lower'] """ # Calculate moving average (typically using close prices) ma = df['close'].rolling(window=period).mean() # Calculate upper and lower envelopes upper = ma * (1 + deviation) lower = ma * (1 - deviation) return pd.DataFrame({'upper': upper, 'ma': ma, 'lower': lower})
Приведенный выше код предоставляет гибкий способ вычисления конвертов, с настраиваемыми параметрами периодов и отклонений для различных стратегий. Первым шагом в расчете конверта является вычисление скользящего среднего. Мы используем простую скользящую среднюю цен закрытия за входной период. Скользящая средняя выступает в качестве центральной линии конвертов и средней цены тренда. Вычисление буфера rolling(window=period).mean() возвращает вектор/буфер со средними значениями, но первые значения периода 1, как и ожидалось, будут равны NaN. Их необходимо обрабатывать надлежащим образом.
Затем мы вычисляем значения буферов верхней и нижней полосы. Расчеты для верхнего конверта производятся путем умножения скользящей средней на (1 + отклонение). Например, если наше отклонение составляет 10%, то верхняя граница будет равна 110% от скользящей средней. Поскольку значение отклонения по умолчанию составляет 0,1, это эквивалентно 10%. Расчеты нижней границы диапазона производятся путем умножения скользящей средней на (1 - отклонение), которое в нашем случае составляет 90%.
Верхняя и нижняя полосы определяют ценовой диапазон, в пределах которого ожидается колебание цен при "нормальных" условиях. Когда цены касаются любой из этих полос, это создает предпосылки для прорыва или разворота, поскольку мы рассматриваем эти полосы как уровни поддержки и сопротивления. Эти две ситуации указывают на продолжение тренда или на колебания рынка в зоне перекупленности/перепроданности.
Параметр отклонения контролирует ширину конвертов. Более высокое процентное отклонение означает более широкие полосы, что может подходить для волатильных активов, в то время как меньшее отклонение создает гораздо более узкую полосу для стабильных активов. Отклонение должно быть положительным, чтобы избежать некорректных полос. Полосы конверта симметричны относительно скользящей средней при условии одинаковой волатильности выше и ниже тренда. Для асимметричных полос можно использовать полосы Боллинджера.
Возвращаемый датафрейм pandas содержит три столбца - upper (верхняя), lower (нижняя) и ma (скользящая средняя). Этот формат датафрейма обеспечивает легкий доступ ко всем трем компонентам, что позволяет осуществлять визуализацию, генерацию сигналов или дальнейший анализ.
Признаки в Python
Под признаками мы подразумеваем векторизованные представления сигналов от двух индикаторов - осциллятора Демарка и конвертов. Эти признаки затем служат входными данными для нашей модели машинного обучения, которая представляет собой рекуррентную нейронную сеть, использующую ядро белого шума - white noise kernel (подробнее об этом позже). Эта реализация машинного обучения на Python основана на признаках, которые мы впервые рассмотрели в предыдущей статье, всего их было 10. Из этих 10 только 6 прошли форвард-тест. 0, 1, 5, 6, 7 и 8. Именно эти признаки мы будем тестировать с помощью нашей рекуррентной нейронной сети (Recurrent Neural Network, RNN). Таким образом, нам нужно вручную реализовать их на Python, прежде чем передать в рекуррентную нейронную сеть.
Признак 0
Как мы видели в предыдущей статье, признак 0 (или паттерн 0) генерирует сигналы на основе пересечения индикатором Демарка порогов перекупленности или перепроданности и одновременного пересечения ценой верхней или нижней полосы конверта. Реализуем это в Python следующим образом:
def feature_0(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1: feature[:, 0] = ((dem_df['main'] <= 0.3) & (price_df['close'] > env_df['lower']) & (price_df['close'].shift(1) <= env_df['lower'].shift(1)) & (price_df['close'].shift(2) >= env_df['lower'].shift(2))).astype(int) feature[:, 1] = ((dem_df['main'] >= 0.7) & (price_df['close'] < env_df['upper']) & (price_df['close'].shift(1) >= env_df['upper'].shift(1)) & (price_df['close'].shift(2) <= env_df['upper'].shift(2))).astype(int) # Set first 2 rows to 0 (no previous values to compare) feature[0, :] = 0 feature[1, :] = 0 return feature
Первая строка кода в функции создает заполненный нулями массив NumPy размером (len(dem_df), 2), который хранит бычьи и медвежьи сигналы. Функция инициализирует массив, гарантируя, что все строки по умолчанию будут равны нулю. Размер массива должен соответствовать длине фрейма данных, чтобы избежать смещения индексов.
Индекс бычьей проверки равен 0 - проверка будет произведена в первую очередь. Как утверждалось в предыдущей статье, согласно нашей реализации, бычий сигнал генерируется, когда осциллятор Демарка находится в зоне перепроданности (обычно ниже 0,3); текущая цена закрытия находится выше нижнего конверта; предыдущая цена закрытия была на уровне нижнего конверта или ниже него; и два периода назад цена закрытия была на уровне нижнего конверта или выше него.
Этот бычий сигнал, полученный в результате проверки, сочетает в себе состояние перепроданности, обозначенное осциллятором Демарка, с прорывом цены выше нижнего предела, что подразумевает потенциальное продолжение или разворот тренда. Условия shift-1 и shift-2 гарантируют, что цена взаимодействовала с нижним конвертом, как это было определено в предыдущей статье. Также можно добавить некоторые дополнительные меры. К ним могут относиться многопериодные проверки, позволяющие уменьшить количество ложных срабатываний за счет требования наличия определенного ценового паттерна. Кроме того, проверка env_df['lower'] и price_df['close'] на наличие NaN позволит избежать некорректных сравнений.
Медвежий столбец обозначает сигнал, сгенерированный, когда осциллятор Демарка находится в зоне перекупленности; текущая цена закрытия ниже верхнего конвнрта; предыдущая цена закрытия была на уровне или выше верхнего конверта; и два периода назад цена закрытия была на уровне или ниже верхнего конверта. Паттерн отражает потенциальные развороты или коррекции после состояния перекупленности. При использовании необходимо обеспечить достаточный объем истории для обработки сдвигов.
С этой целью мы установили начальные значения строк для первых двух значений равными нулю. Это происходит потому, что мы используем значения shift-1 и shift-2.
Оператор return возвращает массив NumPy, содержащий наши паттерны сигналов. Это обеспечивает формат, подходящий для ввода данных в нашу RNN. Взаимодействие многопериодного ценового конверта (shift(1) и shift(2)) добавляет слой временного подтверждения, фактически дополнительный фильтр, что делает его более уникальным по сравнению с простыми сигналами пересечения.
Признак 1
Паттерн генерирует сигналы, когда индикатор Демарка находится в крайних зонах перекупленности или перепроданности, и цена остается за пределами верхней/нижней полос конвертов в течение нескольких последовательных периодов. Реализация в Python:
def feature_1(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1: feature[:, 0] = ((dem_df['main'] > 0.7) & (price_df['close'] > env_df['upper']) & (price_df['close'].shift(1) > env_df['upper'].shift(1))).astype(int) feature[:, 1] = ((dem_df['main'] < 0.3) & (price_df['close'] < env_df['lower']) & (price_df['close'].shift(1) < env_df['lower'].shift(1))).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
Наш первый шаг, как и в случае с паттерном 0, — это определение размера массива и его инициализация нулями. При инициализации по умолчанию сигналы отсутствуют, что лучше, чем наличие NaN. Далее мы переходим к определению значений для каждого индекса. Для первого индекса/столбца мы проверяем наличие бычьих условий. Код проверяет, находится ли индикатор Демарка в зоне перекупленности (>0,7) и находится ли текущая цена закрытия выше верхнего конверта, а также была ли предыдущая цена закрытия выше верхнего конверта.
Это важно, потому что позволяет выявить сильный бычий импульс в ситуациях, когда цена удерживается выше верхнего конверта, несмотря на перекупленность по индикатору Демарка. Как утверждалось в предыдущей статье, это указывает на продолжение тренда, а не на его разворот. Затем наш код устанавливает следующее значение индекса, проверяя наличие медвежьих условий. В коде проверяется, находится ли индикатор Демарка в зоне перепроданности (<0,3), ниже ли текущая цена закрытия нижнего конверта, а также была ли предыдущая цена закрытия также ниже нижнего конверта.
Это зеркальный вариант бычьего сигнала. Он отражает сильный медвежий импульс с устойчивым движением цены ниже нижнего конверта. Это свидетельствует о продолжении нисходящего тренда, как уже отмечалось в предыдущей статье. Поскольку мы используем сравнения со сдвигом, нам необходимо установить значения первой строки равными 0. В данном случае мы устанавливаем значение только для первой строки, поскольку наше сравнение выполняется только для одного индекса. Оператор return возвращает массив сигналов в формате NumPy.
Эта функция фиксирует устойчивые прорывы цен за пределы полос конверта, что подтверждается экстремальными значениями индикатора Демарка, свидетельствующими о сильном продолжении тренда. Это, в основном, сигнализирует о возможности присоединиться к существующему тренду, а не предвещает разворот. Ситуация отличается от признака 0 своим фокусом на последовательных периодах вне полос. При этом паттерн пересечения не требуется.
Признак 5
Признак 5 — следующий паттерн из тех, которые прошли форвард-тест. Подобно двум описанным выше функциям, он получает сигналы от экстремальных значений индикатора Демарка, однако использует направление полос конвертов, вместо того, чтобы фокусироваться на взаимодействии цены с полосами. Реализация в Python:
def feature_5(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1: feature[:, 0] = ((dem_df['main'] > 0.7) & (env_df['upper'] > env_df['upper'].shift(1))).astype(int) feature[:, 1] = ((dem_df['main'] < 0.3) & (env_df['lower'] < env_df['lower'].shift(1))).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
Первым делом, как и в случае с двумя другими функциями, необходимо инициализировать выходной массив нулями и установить его размер равным 2. Этому соответствует первая строка кода в нашей функции, приведенной выше. Затем мы устанавливаем первое значение индекса, которое, как и другие параметры, проверяет наличие бычьего тренда. Требование здесь относительно простое: если цена Демарка перекуплена (>0,7) и верхняя граница растет, то это бычий сигнал. Далее мы устанавливаем второе значение индекса, которое проверяет наличие медвежьего тренда. Как и следовало ожидать, значение индикатора Демарка ниже 0,3 и снижение нижней полосы конверта указывают на медвежий сигнал. Затем мы, как и ожидалось, установили значения первой строки равными нулю, чтобы избежать некорректных сравнений, вызванных значениями NaN, которые возникают при сравнении сдвигов.
Признак 6
Признак 6 (или паттерн 6), как было описано в предыдущей статье, генерирует сигналы на основе изменений импульса индикатора Демарка и волновых ценовых формаций на полосах конвертов. Реализация в Python:
def feature_6(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1: feature[:, 0] = ((dem_df['main'] > dem_df['main'].shift(1)) & (price_df['low'].shift(1) <= env_df['lower'].shift(1)) & (price_df['low'].shift(2) >= env_df['lower'].shift(2)) & (price_df['low'].shift(3) <= env_df['lower'].shift(3)) & (price_df['low'].shift(4) >= env_df['lower'].shift(4))).astype(int) feature[:, 1] = ((dem_df['main'] < dem_df['main'].shift(1)) & (price_df['high'].shift(1) >= env_df['upper'].shift(1)) & (price_df['high'].shift(2) <= env_df['upper'].shift(2)) & (price_df['high'].shift(3) >= env_df['upper'].shift(3)) & (price_df['high'].shift(4) <= env_df['upper'].shift(4))).astype(int) # Set first 4 rows to 0 (no previous values to compare) feature[0, :] = 0 feature[1, :] = 0 feature[2, :] = 0 feature[3, :] = 0 return feature
Протокол инициализации аналогичен тому, что мы уже рассматривали выше, с отличиями в виде заданных значений массива, как и ожидалось. Для первого индекса, проверяющего наличие бычьего тренда, мы присваиваем значение 1 (эквивалентно значению true), если выполняется условие длинной позиции. Это условие представляет собой нарастающее снижение уровня индикатора Демарка в сочетании с паттерном закрытия цены относительно нижнего конверта в течение четырех предыдущих периодов чередования значений ниже и выше. Второй индекс проверяет наличие медвежьего тренда, отслеживая снижение индикатора Демарка, а также, подобно бычьему тренду, подтверждая формирование паттерна закрытия M на верхней полосе.
Далее выполняется установка начальных значений строк в ноль, что необходимо для предотвращения некорректных сравнений индикаторов. Если не присваивать значения нулям, будут выполняться некорректные сравнения, поскольку эти значения по умолчанию являются значениями NaN, возникающими в результате использования сравнений со сдвигом. Таким образом, присвоение нуля охватывает 4 строки от 0 до 3, поскольку мы использовали сдвиг для индексов от 1 до 4.
Принак 7
Как утверждалось в предыдущей статье, эта особенность генерирует сигналы на основе значений Демарка при различных временных задержках и пересечениях ценой полос конвертов. Это смещает акцент на изменения динамики. Реализация в Python:
def feature_7(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1:DEM(X()) >= 0.5 && DEM(X() + 2) <= 0.3 && Close(X()) > ENV_UP(X()) && Close(X() + 1) <= ENV_UP(X() + 1) feature[:, 0] = ((dem_df['main'] >= 0.5) & (dem_df['main'].shift(2) <= 0.3) & (price_df['close'] >= env_df['upper']) & (price_df['close'].shift(1) <= env_df['upper'].shift(1))).astype(int) feature[:, 1] = ((dem_df['main'] <= 0.5) & (dem_df['main'].shift(2) >= 0.8) & (price_df['close'] <= env_df['lower']) & (price_df['close'].shift(1) >= env_df['lower'].shift(1))).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
Инициализация устанавливается на 2 и заполняется нулями, при этом первый индекс используется для проверки наличия длинного сигнала. Наш код помечает бычий тренд как истинный, если текущий индикатор Демарка нейтрален или бычий (>=0,5), индикатор Демарка двух периодов назад был перепродан (<=0,3), а текущая цена закрытия находится на уровне или ниже верхнего конверта. Эти многочисленные требования призваны зафиксировать изменение импульса от перепроданности к нейтральным или бычьим условиям, подтвержденное прорывом выше верхнего конверта. Это обычно указывает на сильный разворот или начало тренда. Условие shift(2) вводит задержку, требующую недавнего условия перепроданности.
Проверка на медвежий тренд при индексе 1 выполняется, если текущий индикатор Демарка нейтральный или медвежий (<= 0,5), индикатор Демарка два периода назад был перекуплен (>= 0,8), текущая цена закрытия находится на уровне или ниже нижнего конверта, и, наконец, предыдущая цена закрытия была на уровне или выше нижнего конверта. В заключение мы присваиваем нулю только первую строку, поскольку используем только один индекс сдвига.
Признак 8
Наш последний признак получает сигналы, когда индикатор Демарка находится в экстремальных зонах, и минимум/максимум цены значительно выходит за пределы полос конверта, что означает экстремальные движения цены. Реализация в Python:
def feature_8(dem_df, env_df, price_df): """ """ # Initialize empty array with 2 dimensions and same length as input feature = np.zeros((len(dem_df), 2)) # Dimension 1:DEM(X()) > 0.7 && Low(X()) > ENV_UP(X()) feature[:, 0] = ((dem_df['main'] > 0.7) & (price_df['low'] > env_df['upper'])).astype(int) feature[:, 1] = ((dem_df['main'] < 0.3) & (price_df['high'] < env_df['lower'])).astype(int) # Set first row to 0 (no previous values to compare) # feature[0, :] = 0 return feature
Начнем с инициализации выходного массива NumPy нулями и присвоения ему размера 2, затем установим значение индекса равным 0. Этот индекс, как и все остальные описанные выше паттерны, указывает на бычий тренд. В данном случае бычий сигнал возникает, когда индикатор Демарка перекуплен (>0,7) и текущий минимум цены находится выше верхнего конверта. Паттерн обычно указывает на сильное бычье движение, поскольку минимальная цена за период превышает верхний конверт. Это обычно указывает на значительный восходящий импульс.
Медвежья проверка на индексе 1, подтверждается, если индикатор Демарка находится в зоне перепроданности (<0,3), а текущий максимум находится ниже нижнего конверта. Как и в случае с бычьим трендом, описанным выше, оба эти сценария встречаются редко, однако, согласно нашим тестам, проведенным в предыдущей статье, нам удалось обнаружить несколько таких сделок. Однако в реальных ситуациях, учитывая их редкость, для обеспечения безопасности может быть применен дополнительный фильтр подтверждения.
RNN на Python
Наша рекуррентная нейронная сеть (RNN), получающая векторизованные выходные данные индикаторов, указанных выше, из признаков 0, 1, 5, 6, 7 и 8, представляет собой модуль нейронной сети PyTorch, который сочетает в себе стандартную RNN с механизмом внесения шума для повышения устойчивости или моделирования стохастических процессов. Он разработан для задач регрессионного анализа и выдает одно выходное значение для каждой входной последовательности. Этот шумовой эффект применяется к скрытым состояниям рекуррентной нейронной сети, модулируется проекционным слоем, сигмоидой и сигмоидной функцией активации для контроля его воздействия.
Входными данными для этой рекуррентной нейронной сети является тензор формы (batch_size, input_size). Данная архитектура включает слой RNN, за которым следует линейная проекция для подавления шума, а затем заключительный полносвязный слой для вывода выходных данных. Введение шума (noise-injection) осуществляется с помощью ядра белого шума в скрытые состояния рекуррентной нейронной сети. Это можно сделать в процессе выполнения или предоставить в виде предварительно вычисленного тензора. В результате для каждой последовательности получается одно значение регрессии, форма которого - (batch_size, ). Реализация в Python:
# Define the network as a class class WhiteNoiseRNN(nn.Module): def __init__(self, input_size=5, hidden_size=64, num_layers=1): super().__init__() self.hidden_size = hidden_size self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True) self.noise_proj = nn.Linear(hidden_size, hidden_size) # Projects noise to hidden space self.fc = nn.Linear(hidden_size, 1) # Single output for regression def forward(self, x, noise_std=0.05): """ Args: x: Input tensor of shape (batch_size, seq_len, input_size) noise_std: Either: - float: Standard deviation for generated noise - tensor: Pre-computed noise (must match rnn_out shape: batch_size × seq_len × hidden_size) """ # Ensure proper input dimensions if x.dim() == 2: x = x.unsqueeze(1) # (batch, features) → (batch, 1, features) # RNN processing rnn_out, _ = self.rnn(x) # (batch_size, seq_len, hidden_size) # Noise injection if isinstance(noise_std, torch.Tensor): noise_std = noise_std.float() if noise_std.dim() == 1: noise_std = noise_std.view(-1, 1, 1) # (batch,) → (batch, 1, 1) noise = noise_std.expand_as(rnn_out) elif noise_std > 0: noise = torch.randn_like(rnn_out) * noise_std else: noise = 0 if isinstance(noise, torch.Tensor): projected_noise = self.noise_proj(noise) rnn_out = rnn_out + torch.sigmoid(rnn_out) * projected_noise return self.fc(rnn_out[:, -1, :]) # (batch_size,)
Как видно из приведенного выше примера, класс WhiteNoiseRNN определен как подкласс nn.Module и инициализируется тремя входными параметрами: размером входных данных, размером скрытого слоя и количеством слоев. На этом этапе определяются архитектура сети и гиперпараметры.
Благодаря наследованию от nn.Module, могут быть легко использованы функции автоматического вычисления градиентов и управления моделями в PyTorch, например, для обучения и оценки. Выбор размера входных данных определяется размерностью признаков входных данных. В нашем случае все признаки имеют 2 измерения, поэтому это значение будет равно 2.
Скрытый размер определяет вместимость модели. Более высокие значения повышают выразительность модели, но увеличивают риск переобучения и увеличения вычислительных затрат. Для большинства простых задач достаточно установить количество слоев равным 1, однако увеличение этого значения приводит к проблемам с исчезновением градиента.
Мы вызываем метод инициализации super() для обращения к родительскому классу, nn.Module, и обеспечиваем правильную настройку функциональности модуля PyTorch, такой как отслеживание параметров и управление устройствами. Включение этого вызова в качестве стандартной процедуры позволяет избежать многих ошибок инициализации. Параметр hidden-size сохраняется в виде переменной экземпляра для возможного использования в других методах.
Мы инициализируем переменную класса rnn как слой RNN с заданным размером входных данных, размером скрытых слоев и количеством слоев. Установка параметра batch-first в значение true гарантирует, что входные и выходные тензоры будут иметь форму, в которой размер обработанного пакета будет являться первым измерением внутри формы. Затем мы определяем слой проекции шума, который проецирует шум в ту же размерность, что и скрытые состояния RNN. Это позволяет преобразовывать шум, будь то путем масштабирования или вращения и т. д., перед добавлением его в скрытые состояния, так что внедрение шума становится обучаемым процессом. Это улучшает способность модели адаптироваться к воздействию шума во время обучения. Линейный слой добавляет параметры, которые увеличивают сложность модели, поэтому требуется достаточное обучение.
Затем мы определяем полносвязный слой для привязки конечного скрытого состояния RNN к единственному выходному значению. В результате получается результат регрессии, при котором размерность скрытого состояния сводится к одному скалярному значению. Это имеет решающее значение для работы модели, особенно в случаях, когда требуется прогнозирование непрерывного значения, как в нашем случае.
Метод forward определяет прямой проход сети, обрабатывающий входные данные x и применяющий шум со стандартным отклонением noise_std. Эта функция реализует основные вычислительные операции, включая объединение данных, обработку с помощью RNN, внесение шума и прогнозирование выходных данных. Всегда крайне важно убедиться, что переменная x имеет правильную форму и предварительно обработана перед вводом. Значение параметра noise_std по умолчанию, равное 0,05, является гиперпараметром, который необходимо настроить в зависимости от желаемого уровня шума. При использовании предварительно рассчитанного шума необходимо проверить его форму и тип, чтобы избежать ошибок во время выполнения.
Первым действием в прямой функции является изменение формы x путем добавления последовательности длиной 1. Это обеспечивает совместимость с RNN, которая ожидает размерность последовательности даже для входных данных, полученных на одном временном шаге. Это также делает модель гибкой как для одношаговых, так и для многошаговых входных данных.
Далее мы обрабатываем RNN, пропуская входной сигнал x через сеть для получения скрытых состояний для каждого временного шага. Результаты этого процесса двояки. Сначала мы получаем rnn_out, тензор, содержащий скрытые состояния; во-вторых, мы получаем _, который представляет собой скрытое состояние последнего слоя, которое мы игнорируем в данной ситуации, поскольку оно нам не нужно. Эта обработка имеет решающее значение, поскольку RNN улавливает временные зависимости во входной последовательности, формируя основу последовательной обработки модели. Эти скрытые состояния являются основными механизмами для внесения шума и прогнозирования выходных данных.
После этого мы переходим к предварительно рассчитанной обработке шума, что проверяется в верхнем условном операторе if. Для обеспечения согласованности мы преобразуем тензор в число с плавающей запятой, изменяем форму одномерных тензоров для передачи и расширяем шум, чтобы он соответствовал форме rnn_out. В качестве альтернативы, если генерируется шум, мы вводим случайность в скрытые состояния, что делает их более устойчивыми к стохастическому процессу моделирования. Шум масштабируется параметром noise_std, который контролирует его амплитуду. В противном случае, если шум отсутствует и значение noise_std равно нулю или отрицательно, то шуму также присваивается значение ноль. Это позволяет модели работать без шума, что может быть полезно для детерминированных прогнозов или при отладке.
После присвоения значения шума мы проецируем шум через линейный слой noise_proj, чтобы выровнять его с пространством скрытых состояний. Это позволяет модулировать воздействие шума, используя сигмоидную функцию выходных данных рекуррентной нейронной сети, и добавлять ее к скрытым состояниям. Как уже упоминалось, эта линейная проекция делает процесс введения шума обучаемым, позволяя модели адаптировать воздействие шума в процессе обучения. torch.sigmoid(rnn_out) масштабирует шум в диапазоне от 0 до 1, гарантируя, что он не перегрузит скрытые состояния. Утверждается, что такой подход повышает устойчивость за счет введения контролируемого стохастика, который может предотвратить переобучение.
Затем мы выбираем скрытое состояние последнего временного шага и пропускаем его через полносвязный слой, чтобы получить один выходной сигнал для каждой последовательности. Скрытое состояние на последнем временном шаге суммирует информацию из последовательности, что это подходит для задач регрессии. Слой fc сопоставляет скрытое состояние с желаемым выходным значением.
Тестовые запуски
После определения параметров нашей сети проведем тесты для шести паттернов, которые были использованы в предыдущей статье: 0, 1, 5, 6, 7 и 8. До публикации последней статьи мы экспериментировали с более длинными входными данными для анализа сети, поскольку в них сигналы индикаторов не были привязаны к бычьему или медвежьему тренду.
В данной статье мы этого не делали, поскольку все паттерны имеют четко выраженный бычий и медвежий проверочные индексы. Это может объяснить, почему в последней статье процент пройденных форвард-тестов был выше, чем в предыдущей, где мы этого не учитывали. Тестирование проводится на GBPUSD. Тестовые запуски проводились в 2023 году на 4-часовом таймфрейме с использованием Python. Обучение Python дает нам рекомендации относительно того, стоит ли открывать длинные или короткие позиции.
Поскольку импортированная в MetaTrader 5 модель ONNX затем используется в созданном мастером советнике, который применяет взвешенные условия для длинных и коротких позиций, эти значения также необходимо оптимизировать, и это также делается для 2023 года.
После обучения и оптимизации мы провели тестовые запуски с 01.01.2023 по 01.01.2025, и получили следующие результаты для всех 6 паттернов/признаков:


Признак 0 не прошел форвард-тест!


Признак 1 не прошел форвард-тест


Признак 5 прошел форвард-тест


Признак 6 не прошел форвард-тест


Признак 7 прошел форвард-тест


Признак 8 прошел форвард-тест
Заключение
Мы изучили паттерны, полученные в результате объединения индикатора Демарка и конвертов. Осциллятор импульса и индикатор поддержки/сопротивления — их взаимодополняющая пара была впервые протестирована в предыдущей статье, где сделки совершались на основе исходных паттернов. В данной статье мы обработали эти паттерны с помощью рекуррентной нейронной сети, которая использует ядро белого шума в процессе обучения для обработки тех же самых индикаторных паттернов. Нам еще предстоит рассмотреть нейронную сеть, которая объединит все паттерны. Возможно, этот вопрос будет рассмотрен в последующих статьях.
| Имя | Описание |
|---|---|
| wz64.mq5 | Созданный Мастером советник, в заголовке которого отображаются использованные файлы |
| SignalWZ_64.mqh | Файл класса пользовательских сигналов |
| 64_0.onnx | Экспортированная ONNX-модель для признака 0 |
| 64_1.onnx | Экспортированный ONNX для признака 1 |
| 64_5.onnx | " для признака 5 |
| 64_6.onnx | " для признака 6 |
| 64_7.onnx | " для признака 7 |
| 64_8.onnx | ' для признака 8 |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18033
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Машинное обучение и Data Science (Часть 40): Использование уровней Фибоначчи в данных машинного обучения
Разработка торговых стратегий с использованием осцилляторов Parafrac и Parafrac V2: Оценка эффективности при одиночном входе
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования