Возможности Мастера MQL5, которые вам нужно знать (Часть 66): Использование паттернов FrAMA и индекса силы с ядром скалярного произведения
Введение
В нашей предыдущей статье, где мы представили эту пару индикаторов как источник сигналов для входа в позицию для советника, результаты форвард-тестов не обнадежили. Мы привели несколько причин, а также отметили, что обучение и оптимизация, которые мы проводим, рассчитаны всего на 1 год, при этом для любого паттерна крайне важно проводить максимально тщательное тестирование на большом объеме исторических данных. В продолжение этой статьи мы, как всегда, рассмотрим паттерны, прошедшие форвард-тест. Применим к ним машинное обучение.
При применении алгоритмов машинного обучения в MQL5 OpenCL всегда является доступным вариантом, однако для этого часто требуется наличие графического процессора (GPU). При этом библиотека кода Python стала довольно обширной, и многие преимущества можно получить, используя только процессор. Именно это мы и исследуем в этой серии статей. В этой работе, как и в некоторых предыдущих, мы пишем код для наших нейронных сетей на Python, потому что программирование и обучение на Python очень эффективны.
Из десяти паттернов, которые мы оптимизировали или обучили в предыдущей статье, только шестой и девятый прошли форвард-тест. Поэтому мы проводим дальнейшие испытания с использованием нейронной сети, как и в предыдущих статьях, с той разницей, что используем сверточную нейронную сеть (convolution neural network, CNN). В CNN будет реализовано ядро скалярного произведения (dot product kernel). Однако сначала, как всегда в реализациях на Python, мы определяем функции-индикаторы, которые нам необходимы для подачи сигналов в нашу сеть.
Функция фрактальной адаптивной скользящей средней (FrAMA)
FrAMA — это динамическая скользящая средняя, которая адаптирует свое сглаживание в зависимости от фрактальной размерности ценовых движений. Это, как правило, делает ее более чувствительной к значительным изменениям цен и менее восприимчивой к шуму. Мы реализуем эту функцию на Python следующим образом:
def FrAMA(df, period=14, price_col='close'): """ Calculate Fractal Adaptive Moving Average (FrAMA) for a DataFrame with price data. Args: df: Pandas DataFrame with a price column (default 'close'). period: Lookback period for fractal dimension (default 20). price_col: Name of the price column (default 'close'). Returns: Pandas DataFrame with a single column 'main' containing FrAMA values. """ prices = df[price_col] frama = pd.Series(index=prices.index, dtype=float) for t in range(period, len(prices)): # 1. High-Low range (volatility proxy) high = prices.iloc[t-period:t].max() low = prices.iloc[t-period:t].min() range_hl = high - low # 2. Fractal Dimension (simplified) fd = 1.0 + (np.log(range_hl + 1e-9) / np.log(period)) # Avoid log(0) # 3. Adaptive EMA smoothing factor alpha = 2.0 / (period * fd + 1) # 4. Update FrAMA (recursive EMA) frama.iloc[t] = alpha * prices.iloc[t] + (1 - alpha) * frama.iloc[t-1] return pd.DataFrame({'main': frama})
Функция, описанная выше, обеспечивает гибкость ввода данных, поскольку принимает фрейм данных pandas с настраиваемыми параметрами столбца цены и периода. Она использует индикатор волатильности, представляющий собой диапазон максимумов и минимумов, для оценки волатильности рынка, а также вычисляет упрощенная фрактальная размерность для измерения сложности цен. Функция обновляет FrAMA посредством рекурсивного EMA.
Если мы проанализируем наш код, то первым делом извлечем указанный столбец с ценой из входного фрейма данных. Процедура извлечения гарантирует, что функция будет работать с любым столбцом цен, указанным пользователем. Мы избегаем жесткого кодирования и обеспечиваем совместимость. При использовании этого подхода может быть целесообразно улучшить нашу функцию, проверив наличие столбца с ценой во входном фрейме данных и убедившись, что данные о ценах являются корректными, без значений NaN или пропущенных значений, учитывая, что FrAMA чувствителен к входным данным о ценах.
Далее мы инициализируем пустую серию pandas с тем же индексом, поскольку это предварительно выделяет память для значений FrAMA, обеспечивая выравнивание с индексом входного фрейма данных для эффективного итеративного обновления. При использовании этого метода FrAMA требуется окно ретроспективного анализа для вычислений, что означает, что первые значения являются NaN и должны быть обработаны или обнулены. Также важно обеспечить согласованность индекса цен, например, с учетом соотношения цены и времени, чтобы избежать несоответствия.
Таким образом, мы переходим в цикл for, где вычисляем диапазон максимумов и минимумов за рассматриваемый период, чтобы оценить волатильность. Для каждого временного шага t мы берем максимальную и минимальную цены за период t и вычисляем их разницу. Диапазон высоких и низких значений служит индикатором волатильности рынка, что важно для определения фрактальной размерности. Больший диапазон значений означает более высокую волатильность, что влияет на адаптивные свойства FrAMA. В практической реализации механизм обработки ошибок, отслеживающий значения NaN, может в некоторой степени помочь в решении проблемы отсутствующих данных.
Затем мы вычисляем упрощенную фрактальную размерность, используя логарифмическое отношение диапазона высоких и низких значений. Эквивалентный член 1e-9 эпсилон предотвращает деление на ноль или логарифм нуля в случае, если диапазон hl равен нулю. Фрактальная размерность, fd, измеряет сложность или трендовость цен. Более высокое значение fd, близкое к 2, означает, что мы находимся на нестабильном, шумном рынке. С другой стороны, более низкое значение fd, близкое к 1, указывает на трендовый рынок. Таким образом, этот параметр определяет адаптивность FrAMA. 1e9 обеспечивает численную устойчивость, который гарантирует, что значение будет достаточно малым, чтобы не искажать результаты. Кроме того, используемая здесь фрактальная размерность является упрощенной версией; для более сложных форматов можно использовать методы подсчета ячеек. После этого мы вычисляем коэффициент сглаживания, альфа.
Это сглаживающий коэффициент для экспоненциального скользящего среднего, основанный на фрактальной размерности и периоде. Это важно, потому что адаптивная альфа определяет, насколько быстро FrAMA реагирует на новые цены. Более высокое значение fd, достигаемое на более шумном рынке, снижает альфа, замедляя работу EMA по фильтрации шума. Более низкое значение fd, получаемое на трендовых рынках, увеличивает альфа, делая FrAMA более чувствительной к изменениям цен.
Таким образом, эта формула обеспечивает баланс между отзывчивостью и плавностью работы. Период можно регулировать для управления чувствительностью. Важно убедиться, что период знаменателя * fd + 1 положителен, чтобы избежать проблем с делением.
Следующая строка кода обновляет значение FrAMA в момент времени t, используя формулу EMA, которая представляет собой взвешенную комбинацию текущей цены и предыдущего значения FrAMA. Это важно, потому что рекурсивный расчет EMA является основой FrAMA и создает сглаженную адаптивную скользящую среднюю, которая подстраивается под рыночные условия посредством альфа. Внесенные в данную реализацию изменения включают, во-первых, проверку на отсутствие значений NaN для значений FrAMA, а также проверку граничных случаев буфера FrAMA.
Последняя строка кода функции FrAMA возвращает значения FrAMA в виде фрейма данных pandas с одним столбцом main. Это стандартизирует формат вывода для обеспечения совместимости с другими инструментами технического анализа или библиотеками для построения графиков. Использование имени столбца main является общепринятой практикой для индикаторов с одним буфером. При интеграции в различные системы, где это имя является точкой вызова, его можно изменить на FrAMA или что-то более конкретное. Также необходимо убедиться, что возвращаемый фрейм данных соответствует индексу входного фрейма данных.
Функция осциллятора индекса силы (Force Index Oscillator)
Осциллятор измеряет силу колебаний цены, объединяя изменения цены и торгового объема. Для выделения трендов или разворотов используется экспоненциальная скользящая средняя (EMA). Реализация в Python:
def ForceIndex(df, period=14, price_col='close', volume_col='tick_volume'): """ Calculate Force Index for a DataFrame with price and volume data. Args: df: Pandas DataFrame with columns for price and volume (default 'close' and 'volume'). period: Smoothing window for EMA (default 13). price_col: Name of the price column (default 'close'). volume_col: Name of the volume column (default 'volume'). Returns: Pandas DataFrame with a single column 'main' containing Force Index values. """ closes = df[price_col] volumes = df[volume_col] # 1. Raw Force Index = Price Delta * Volume price_delta = closes.diff() raw_force = price_delta * volumes # 2. Smooth with EMA alpha = 2.0 / (period + 1) force_index = pd.Series(index=closes.index, dtype=float) for t in range(1, len(raw_force)): if pd.isna(raw_force.iloc[t]): force_index.iloc[t] = np.nan else: force_index.iloc[t] = alpha * raw_force.iloc[t] + (1 - alpha) * force_index.iloc[t-1] return pd.DataFrame({'main': force_index})
Наша функция, как и FrAMA, предлагает гибкость ввода данных, поскольку принимает настраиваемые столбцы с ценами и объемами. Она использует расчет необработанного значения силы, сочетая изменение цены и объем, для оценки давления покупателей и продавцов. Также она выполняет сглаживание EMA для уменьшения шума и выделения продолжительных движений. Обрабатывает значения NaN. Наконец, программа стандартизирует выходные данные, возвращая фрейм данных для упрощения интеграции.
Если мы проанализируем строки, то первым делом извлечем цену закрытия и объем из входного фрейма данных серии pandas. Это важно, поскольку позволяет выделить релевантные данные для вычислений, обеспечивая гибкость в названиях столбцов и эффективную обработку. Возможные изменения могут включать проверку наличия столбцов price_col и volume_col в фрейме данных для предотвращения ошибок; а также обеспечение того, чтобы данные об объеме были неотрицательными и данные о ценах были корректными, поскольку индекс силы зависит от обоих параметров.
Далее мы устанавливаем разницу цен равной разнице между ценами закрытия. Используемый нами в Python метод возвращает буфер, в котором вычисляется разница между последовательными ценами закрытия для измерения движения цены. Это важно, поскольку изменение цены отражает направление и величину взвешенного по объему движения рынка, что является ключевым компонентом индекса силы. Необходимо обрабатывать значения NaN для первого индекса путем преобразования их в ноль, а также учитывать случаи отсутствия значений.
Далее мы определяем необработанное значение индекса силы, который представляет собой произведение разницы цен и наших объемов. Python значительно упрощает реализацию, умножая буферы/массивы так, как если бы они были скалярными значениями. Произведение отражает силу ценового движения. Оно сочетает в себе динамику цен и объем для количественной оценки давления покупателей или продавцов. Значительное изменение цены при большом объеме обычно свидетельствует о более сильной уверенности рынка. При выполнении этого векторного произведения, помимо приведенного выше кода, рекомендуется убедиться, что буферы объема и изменения цены выровнены по индексу, чтобы избежать ошибок в расчетах. Следует также отметить, что этот исходный индекс силы может быть зашумленным, поэтому сглаживание можно применить на более позднем этапе.
Далее мы устанавливаем значение альфа. Это расчет коэффициента сглаживания EMA на основе указанного периода с использованием стандартной формулы EMA 2/(N + 1). Это важно, поскольку определяет вес новых данных по сравнению с историческими данными в сглаженном индексе силы. Меньшее значение альфа, полученное за больший период, как правило, приводит к более плавным результатам. Мы используем наиболее популярный период по умолчанию, равный 14. Однако корректировки следует вносить в зависимости от используемого таймфрейма. Как правило, более короткие периоды хорошо подходят для внутридневной торговли, в то время как более длинные периоды могут использоваться для дневных или более крупных таймфреймов. Также важно убедиться, что значение периода положительное, чтобы избежать ошибок деления на ноль.
Затем мы инициализируем фактический буфер индекса силы, используя входной фрейм данных pandas. Это позволяет заполнить пустой буфер тем же индексом, что и данные о цене, для хранения сглаженных значений индекса силы. Это важно, потому что служит для предварительного выделения памяти, а также обеспечивает выравнивание индексов, что облегчает итеративные вычисления EMA. Ожидаются начальные значения NaN, поскольку для первого индекса силы требуется предварительное значение.
Нам нужно убедиться, что исходный индекс силы в момент времени t равен NaN. Это обеспечивает надежность за счет явной обработки отсутствующих данных, что предотвращает некорректные расчеты EMA. В реальных данных часто встречаются пробелы. Это надо учитывать. Хорошей практикой была бы разметка значений NaN при отладке.
Затем мы обновляем буфер индекса силы в каждый момент времени t, используя формулу EMA. Это позволяет объединить текущий исходный индекс силы с предыдущим сглаженным значением. Это важно, поскольку является ключевым этапом сглаживания, который уменьшает шум в исходном индексе силы и выделяет устойчивые тренды или развороты. На этом этапе важно убедиться, что force_index[t-1] инициализирован должным образом, поскольку для ранних индексов часто используется значение NaN. Поэтому необходимо надлежащим образом обрабатывать граничные случаи, возникающие в этом буфере.
После этого мы возвращаем сглаженные значения индекса силы в виде фрейма данных pandas с одним столбцом, названным main. Как уже говорилось выше в отношении FrAMA, стандартизированный формат вывода позволяет лучше интегрировать и визуализировать данные во вторичные форматы или инструменты в зависимости от требований пользователя. Тег main обычно соответствует "общепринятым" принципам технического анализа. Для обеспечения бесшовного слияния важно убедиться, что индекс фрейма данных соответствует входным данным.
Мы определили функции индикатора на Python. Теперь пришло время рассмотреть два сигнальных паттерна, которые прошли форвард-тест, как описано в предыдущей статье. Это паттерны 6 и 9. Мы реализуем их в виде простого 2-битного вектора, который служит входными данными для нашей нейронной сети. Эти "биты" обозначают бычий и медвежий тренд, при этом значения каждого индекса равны либо 0, либо 1. Подведем итог: входной вектор имеет размер 2, и по первому индексу мы записываем 0, если ВСЕ бычьи условия не выполнены, или 1, если все они выполнены. Аналогично, на втором индексе мы регистрируем 0, если ВСЕ медвежьи условия не выполнены, или 1, если ВСЕ они выполнены.
Ранее мы рассматривали сценарии, в которых мы разбивали каждую из составляющих частей бычьего и медвежьего сигнала, чтобы получить более сложные входные векторы для наших нейронных сетей. Дальнейшие шаги, вытекающие из этого, оказались не столь многообещающими, и это больше походило на подход, который мы пробовали ранее, объединяя несколько паттернов в один советник, как мы уже писали в предыдущей статье.
Однако этот анализ может быть неточным, поэтому читатели могут использовать и модифицировать прилагаемый исходный код и провести независимое тестирование, чтобы прийти к собственным выводам. Приложенный код предназначен для использования в Мастере MQL5. Руководство можно найти здесь.
Функция признака 6
Наша реализация функции паттерна 6 на Python генерирует двумерный массив двоичных сигналов 0 или 1, как описано выше. Начнем с инициализации массива выходных данных с нулями. В результате получается двумерный массив NumPy, эквивалентный двухстолбцовой матрице. Количество строк этой "матрицы" устанавливается равным размеру или длине одного из входных фреймов данных one_df. Эта инициализация настраивает структуру выходных данных для раздельного хранения бычьих и медвежьих сигналов. Нулевая инициализация означает, что сигналы не генерируются, если не выполнены условия паттерна. Мы получаем своего рода "чистый лист".
При реализации важно убедиться, что длина первого входного фрейма данных pandas совпадает с длиной второго фрейма two_df, а также ценового фрейма данных price_df, чтобы избежать несовпадения индексов. Двухмерная структура играет ключевую роль в различении медвежьих и бычьих сигналов, поэтому важно убедиться, что ее форма соответствует ожиданиям. Вот как мы ее реализуем:
def feature_6(one_df, two_df, price_df): """ Generate binary signals based on sustained price-FrAMA alignment and Force Index momentum. Args: one_df: DataFrame with FrAMA values ('main' column). two_df: DataFrame with Force Index values ('main' column). price_df: DataFrame with price data ('close' column). Returns: 2D NumPy array with bullish (column 0) and bearish (column 1) signals. """ feature = np.zeros((len(one_df), 2)) feature[:, 0] = ((price_df['close'] > one_df['main']) & (price_df['close'].shift(1) > one_df['main'].shift(1)) & (price_df['close'].shift(2) > one_df['main'].shift(2)) & (two_df['main'] > 0.0) & (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int) feature[:, 1] = ((price_df['close'] < one_df['main']) & (price_df['close'].shift(1) < one_df['main'].shift(1)) & (price_df['close'].shift(2) < one_df['main'].shift(2)) & (two_df['main'] < 0.0) & (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Затем мы определяем наши бычьи ожидания или первое значение индекса в каждой строке. Подводя итог предыдущей статье, если текущая цена находится выше FrAMA, и предыдущая цена также выше предыдущей FrAMA, два периода назад наблюдалась аналогичная ситуация, текущий индекс силы положителен, плюс один и два периода назад индекс силы был ниже текущего уровня, перед нами бычий сценарий.
Первая строка в столбце отражает бычий сценарий, гарантируя, что сигнал срабатывает только тогда, когда ценовой тренд постоянно находится выше адаптивной скользящей средней FrAMA, что указывает на восходящий тренд. Индекс силы подтверждает наличие сильного покупательского давления. Трехпериодная проверка повышает надежность, а проверка индекса силы исключает слабые движения. При реализации важно проверить, что все входные фреймы данных выровнены по индексам и содержат корректные данные. Операции shift() создают значения NaN для первых строк, которые необходимо обработать, прежде чем можно будет вернуть значения индикаторов.
Во втором столбце определяется медвежий сигнал при аналогичных условиях: текущая цена ниже FrAMA; предыдущая цена также ниже FrAMA; аналогичное условие наблюдалось два периода назад; текущий индекс силы отрицательный; и индекс силы один период назад находится выше текущего индекса силы и индекса силы два периода назад. n-образная форма индекса силы указывает на увеличение негативного объема или настроения.
Эта модель отражает бычий тренд, указывающий на возможности продаж. Он выявляет устойчивые нисходящие тренды, сопровождающиеся сильным давлением со стороны продавцов. Симметрия обеспечивает согласованную логику в обоих вариантах. Как и в случае с бычьим паттерном, крайне важно обеспечить согласованность данных. Также необходимо провести тесты для определения достаточной частоты встречаемости этого паттерна. Проверка импульса индекса силы (`shift(1) < shift(2)`) может вести себя по-разному на медвежьих и бычьих рынках. Поэтому важно проанализировать исторические показатели, чтобы проверить их достоверность.
Далее мы устанавливаем значения первых двух строк массива признаков равными нулю, поскольку операции .shift(1) и .shift(2) выдают NaN для этих строк, что делает эти условия неопределенными. Это предотвращает появление некорректных сигналов в начальных строках, где исторических данных недостаточно, и обеспечивает чистый и пригодный для использования результат без ручной постобработки. Эта проверка имеет решающее значение для обеспечения надежности, и поскольку этот паттерн может быть реализован с использованием более чем двух сдвигов (например, когда сравниваемые периоды превышают 2), важно убедиться, что установка значений только для первых двух строк достаточна, исходя из используемого максимального количества сдвигов. Если будут добавлены дополнительные сдвиги, это следует соответствующим образом скорректировать.
Подводя итог по нашему признаку 6, можно сказать, что бычий паттерн предполагает устойчивое пересечение бычьей FrAMA не менее 3 периодов с положительным и недавно сформировавшимся U-образным индексом силы. Это подтверждает устойчивость восходящего тренда. Медвежий паттерн является зеркальным отражением, поскольку он основан на устойчивом пересечении медвежьей FrAMA не менее 3 периодов, а также отрицательном индексе силы с недавним паттерном в форме n. Это подтверждает устойчивость нисходящего тренда.
Ключевое различие между ними, помимо симметрии и противоположности, заключается в том, что проверка настроения с помощью индекса силы уникальным образом требует недавнего увеличения его величины, которое может вести себя по-разному на бычьих и медвежьих рынках из-за асимметрии. В частности, рынки, как правило, падают быстрее, чем растут.
Функция признака 9
Эта реализация сигнала 9 на Python фокусируется на выравнивании настроений за один период по цене, FrAMA и индексу силы, что делает его более чувствительным к краткосрочным изменениям, чем признак 6. Мы начинаем так же, как и с функцией 9, инициализируя целевой выход нулями. Это аналогичная схема, как в признаке 6, где начальный размер выходного двухмерного массива NumPy устанавливается равным длине одного из входных фреймов данных, one-df. Мы реализуем это на Python следующим образом:
def feature_9(one_df, two_df, price_df): """ Generate binary signals based on single-period momentum alignment of price, FrAMA, and Force Index. Args: one_df: DataFrame with FrAMA values ('main' column). two_df: DataFrame with Force Index values ('main' column). price_df: DataFrame with price data ('close' column). Returns: 2D NumPy array with bullish (column 0) and bearish (column 1) signals. """ feature = np.zeros((len(one_df), 2)) feature[:, 0] = ((price_df['close'] > price_df['close'].shift(1)) & (one_df['main'] > one_df['main'].shift(1)) & (two_df['main'] > two_df['main'].shift(1))).astype(int) feature[:, 1] = ((price_df['close'] < price_df['close'].shift(1)) & (one_df['main'] < one_df['main'].shift(1)) & (two_df['main'] < two_df['main'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Для обеих функций признаков one-df означает фрейм данных FrAMA, а two-df — фрейм данных индекса силы. Таким образом, как мы уже упоминали в признаке 6, нам необходимо убедиться, что фрейм данных с одним элементом, фрейм данных с двумя элементами и фрейм данных с ценой имеют одинаковую длину, чтобы избежать несоответствий в форме. Эта матрица/двухмерный массив разделяет бычьи и медвежьи паттерны, поэтому важно проверить размер ее строк.
Далее мы сначала проверяем наличие бычьих условий в рамках этого паттерна. Условия следующие: текущая цена выше цены закрытия предыдущего дня; текущая скользящая средняя FrAMA выше предыдущей скользящей средней FrAMA; и текущий индекс силы выше последнего значения. Это отражает краткосрочный бычий импульс, при котором цена, FrAMA и индекс силы растут, что свидетельствует о согласованном давлении покупателей. Он также немного более чувствителен, чем признак 6, поскольку проверяет только один период. Это может сделать его более подходящим для скальпинга или краткосрочной торговли.
При использовании важно обеспечить согласованность данных по всем входным параметрам, чтобы избежать несовпадающих сравнений. Поскольку паттерн чувствителен к изменениям в течение одного периода, нам необходимо проверить частоту его сигнала, чтобы избежать чрезмерной торговли в случае, если рынки столкнутся с зашумленными данными. Также может помочь визуализация сигналов на ценовом графике для обеспечения их соответствия ожидаемым настроениям объема.
Мы установили медвежий паттерн для признака 9, удовлетворяющий следующим условиям: текущая цена ниже предыдущей цены; текущая FrAMA ниже предыдущей FrAMA; и текущий индекс силы ниже предыдущего индекса силы. Это отражает краткосрочные медвежьи настроения, когда все три показателя снижаются, что указывает на согласованное давление со стороны продавцов. Ориентация на один конкретный период приводит к тому, что паттерн реагирует на непосредственные спады.
Как и в случае с бычьим паттерном, проверка целостности данных и тестирование частоты сигнала имеют решающее значение. На волатильных рынках медвежьи сигналы могут срабатывать чаще, поэтому анализ исторических показателей важен при корректировке этой стратегии.
Затем мы обнуляем первые строки выходного массива из-за использования оператора сдвига, как уже упоминалось выше, и возвращаем массив в качестве результата работы функции.
CNN с ядром скалярного произведения
Для расширения моделей FrAMA и индекса силы мы выбрали алгоритм машинного обучения — сверточную нейронную сеть, использующую ядро скалярного произведения. Мы достигаем этого с помощью класса DotProductConv1D. Этот класс представляет собой модуль нейронной сети PyTorch, который сочетает в себе одномерные свертки с механизмом внимания на основе скалярного произведения, вдохновленный архитектурой Transformer.
Он обрабатывает входные данные в виде [batch, channels, width] и выдает одно значение для каждого образца в диапазоне [0, 1]. Смещение значения к 0 будет означать медвежий прогноз, а смещение к 1 — бычий. Механизм внимания, основанный на скалярном произведении, позволяет сети фокусироваться на релевантных частях входной последовательности, в то время как сверточные слои проецируют данные в подходящее пространство для вычисления механизма внимания.
Использование этого ядра в сверточной нейронной сети выгодно по ряду причин. Во-первых, оно позволяет избирательно фокусироваться на необходимых признаках. Механизм внимания, основанный на скалярном произведении, вычисляет показатели сходства между вектором запроса и ключевым вектором, что позволяет сети придавать большее значение важным ключевым шагам или характеристикам. Это может быть полезно для некоторых временных рядов, например, когда определенные периоды более информативны, в частности, периоды с высокой волатильностью по сравнению с периодами без волатильности. Во-вторых, это способствует формированию глобального понимания контекста.
В отличие от традиционных сверточных нейронных сетей, которые используют ядра фиксированного размера и локальные рецептивные поля, механизм внимания на основе скалярного произведения позволяет каждому временному шагу отслеживать все остальные временные шаги, тем самым улавливая зависимости на больших расстояниях без увеличения размера ядра. В-третьих, динамическое взвешивание, при котором оценки внимания вычисляются динамически на основе входных данных, делает модель адаптивной к изменяющимся закономерностям в данных, таким как различные рыночные условия в рамках временного ряда.
Наконец, сочетание с CNN-сетями дает дополнительные преимущества: они хорошо подходят для извлечения локальных признаков, а ядро скалярного произведения — для выявления глобальных взаимосвязей. Механизм внимания на основе скалярного произведения также является вычислительно эффективным при умеренной длине последовательностей, в то время как одномерная сверточная нейронная сеть уменьшает размерность входных данных, делая всю модель легковесной по сравнению с полными трансформерными моделями. Реализация класса сети:
class DotProductConv1D(nn.Module): def __init__(self, in_channels=1, out_channels=1, kernel_size=3): super().__init__() self.kernel_size = kernel_size self.padding = kernel_size // 2 # Projections for dot product attention (1D convolution) self.query = nn.Conv1d(in_channels, out_channels, kernel_size=1) self.key = nn.Conv1d(in_channels, out_channels, kernel_size=1) self.value = nn.Conv1d(in_channels, out_channels, kernel_size=1) # Output projection to produce a single value per sample self.proj = nn.Sequential( nn.Conv1d(out_channels, 1, kernel_size=1), nn.AdaptiveAvgPool1d(1), # Reduce width to 1 (global average pooling) nn.Sigmoid() # Ensures output in [0, 1] ) def forward(self, x): B, C, W = x.shape # [Batch, Channels, Width] # Compute Q/K/V (all [B, out_channels, W]) q = self.query(x) k = self.key(x) v = self.value(x) # Dot product attention attn = torch.bmm(q.transpose(1, 2), k) # [B, W, W] attn = F.softmax(attn / (W ** 0.5), dim=-1) # Scaled softmax # Apply attention to values out = torch.bmm(v, attn.transpose(1, 2)) # [B, out_channels, W] # Project to [B, 1, 1] and squeeze to [B, 1] out = self.proj(out) # [B, 1, 1] return out.squeeze(-1) # [B, 1]
Первым делом, как показано выше, мы определяем этот класс как подкласс класса 'nn.module' из PyTorch. Это позволяет использовать его в качестве модуля нейронной сети с автоматическим управлением параметрами и поддержкой графического процессора. Затем мы инициализируем модуль параметрами для входных каналов, выходных каналов для проекций внимания и размером ядра для вычислений заполнения. Эти параметры определяют пропускную способность сети и ее совместимость с входными данными. Затем мы вычисляем размер заполнения, чтобы сохранить длину входной последовательности после свертки, используя целочисленное деление для обеспечения симметричного заполнения.
Далее определяем три одномерных сверточных слоя, которые будем использовать для проецирования входных данных в тензоры запроса, ключа и значения для механизма внимания. Каждый из них имеет ядро размером 1 и действует как линейное преобразование на каждом временном шаге. Затем мы определяем конвейер проекции выходных данных. Это уменьшает количество каналов выходного сигнала внимания до 1. В нем применяется метод глобального усреднения для сведения временного измерения к одному значению. Наконец, он преобразует выходные данные в один скаляр в диапазоне [0, 1].
Определив этот "заголовок класса", мы переходим к описанию функции forward. Первым делом в рамках прямого прохода мы применяем сверточные слои запроса, ключа и значения к входному тензору 'x', имеющему форму '[B, C, W]', в результате чего получаем три тензора формы [B, out_channels, W]. После этого мы вычисляем скалярное произведение оценок внимания, выполняя пакетное умножение матриц ('bmm') между транспонированным тензором запроса (`[B, W, out_channels]`) и тензором ключа (`[B, out_channels, W]`), в результате чего получаем матрицу внимания вида [B, W, W].
Затем мы применяем масштабированный soft max к показателям внимания, деля их на квадратный корень из длины последовательности, чтобы стабилизировать градиенты. Наконец, мы проводим нормализацию по последнему измерению, чтобы получить весовые коэффициенты внимания, сумма которых равна 1. Выход функции forward определяется следующим образом: мы выполняем умножение матриц пакета между 'v' ('[B, out_channels, W]') и транспонированной матрицей внимания ('[B, W, W]'), чтобы получить выходную форму '[B, out_channels, W]'.
Затем мы пропускаем выходные данные механизма внимания через конвейер проекции ('self.proj'), чтобы получить тензор вида '[B, 1, 1]'. После этого мы просто удаляем последнее измерение, так что возвращаемый результат имеет форму '[B, 1]'.
Тестовые запуски
Результаты тестовых запусков для двух паттернов признаков 6 и 9 представлены ниже. Оба варианта, по-видимому, движутся вперед, но, как всегда, наше обучение основывалось на очень ограниченном наборе данных, поэтому читателям всегда рекомендуется проводить более тщательное и обширное тестирование, прежде чем делать какие-либо долгосрочные выводы.
Для паттерна 6.
Для паттерна 9.
Заключение
Мы продемонстрировали, как машинное обучение с использованием ядра произведения может быть применено для расширения и, возможно, извлечения выгоды из предварительного потенциала на основе сигналов индикатора FrAMA и осциллятора индекса силы. Мы протестировали только два паттерна — 6 и 9, что весьма ограничивает выборку. Однако, помимо остальных восьми, которые мы не рассматривали, существует несколько других вариантов сочетания только этих двух индикаторов, которые можно изучить.
| Файл | Описание |
|---|---|
| wz_66.mq5 | Собранный в Мастере советник, заголовок которого отображает включаемые файлы |
| SignalWZ_66.mqh | Файл класса пользовательских сигналов |
| 66_6.0nnx | Файл ONNX с паттерном 6 |
| 66_9.onnx | Файл ONNX с паттерном 9 |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18188
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Адаптивная факторная токенизация (Основные компоненты)
Торговые инструменты MQL5 (Часть 4): Улучшаем панель мультитаймфреймового сканера — динамическое позиционирование и сворачивание/разворачивание
Торговые инструменты на языке MQL5 (Часть 5): Создание бегущей тикерной строки для мониторинга символов в реальном времени
Алгоритм извлечения торговых правил из паттернов в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования



