English Deutsch 日本語
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 74): Использование паттернов Ишимоку и ADX-Wilder с обучением с учителем

Возможности Мастера MQL5, которые вам нужно знать (Часть 74): Использование паттернов Ишимоку и ADX-Wilder с обучением с учителем

MetaTrader 5Трейдинг |
338 0
Stephen Njuki
Stephen Njuki

Введение

В предыдущей статье мы рассмотрели сочетание индикаторов Ишимоку и ADX-Wilder как взаимодополняющую пару инструментов для определения уровней поддержки/сопротивления и тренда. Как обычно, мы проводили тестирование в созданном с помощью Мастера советнике и рассмотрели 10 различных сигнальных паттернов. В большинстве случаев при таком сочетании индикаторов удалось успешно пройти форвард-тест на годовом отрезке, проведя тестирование и оптимизацию в предыдущем году. Однако 3 паттерна не справились с этой задачей удовлетворительно. Это паттерны 0, 1 и 5. Мы продолжим исследование, начатое в той статье, и изучим, может ли обучение с учителем повлиять на их результаты. Наш подход заключается в том, чтобы реконструировать сигналы каждого из этих паттернов в виде простого входного вектора для нейронной сети, по сути, превращая нейронную сеть в дополнительный фильтр для сигнала.


Сеть

В качестве модели обучения c учителем мы выбрали сеть, которая опирается на ядро спектрального смешения (Spectral Mixture Kernel). Это определяется следующей формулой:

форма

где:

  • τ \tau - разница во времени между двумя точками.
  • Q - количество спектральных компонентов.
  • wi - вес i-й компоненты (амплитуды).
  • li - масштаб длины i-го компонента (контролирует затухание).
  • fi - частота i-го компонента (частота колебаний).

Мы используем ядро спектрального смешения для определения входного слоя простой нейронной сети, за которым следуют последующие слои PyTorch-Linear. Создадим и используем слой спектрального смешения. Реализуем этот слой и следующие за ним сети регрессоров на языке Python, как показано ниже:

class DeepSpectralMixtureRegressor(nn.Module):
    def __init__(self, input_dim=2, num_components=96):
        super().__init__()
        self.smk = DeepSpectralMixtureLayer(input_dim, num_components)
        feature_dim = input_dim * num_components  # 2 * 96 = 192

        self.head = nn.Sequential(
            nn.Linear(feature_dim, 1024),
            nn.ReLU(),
            nn.Linear(1024, 512),
            nn.ReLU(),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Linear(128, 1)  # <--- Removed sigmoid!
        )

    def forward(self, x):
        features = self.smk(x)
        output = self.head(features)
        return output

Некоторые исследования показывают, что использование нашего специального ядра смешения на входе позволяет извлекать признаки из входных данных, а регрессионная часть последующих слоев на выходе подходит в случаях, когда модель является регрессором, как показано на примере использования сигмоидной функции. В выбранном нами алгоритме спектрального ядра смешения первый слой выполняет периодическое моделирование данных, в то время как слои регрессора/блока обучаются частотам, дисперсиям и весам сети. Наш подход к реализации лучше всего подходит для задач регрессии с осциллирующими паттернами во входных данных. Она также объединяет гибкость нейронных сетей с концепциями гауссовских процессов.

Наша нейронная сеть называется DeepSpectralMixtureRegressor. Также мы используем модульную архитектуру, состоящую из двух частей. Извлечение признаков и регрессия. Первый этап извлечения признаков обрабатывается функцией DeepSpectralMixtureLayer, код которой приведен ниже:

class DeepSpectralMixtureLayer(nn.Module):
    def __init__(self, input_dim, num_components):
        super().__init__()
        self.num_components = num_components
        hidden = 512  # Wider hidden layers

        # Go deeper with 4 layers
        def make_subnet():
            return nn.Sequential(
                nn.Linear(input_dim, hidden),
                nn.ReLU(),
                nn.Linear(hidden, hidden),
                nn.ReLU(),
                nn.Linear(hidden, hidden),
                nn.ReLU(),
                nn.Linear(hidden, num_components),
            )

        self.freq_net = make_subnet()
        self.var_net = nn.Sequential(*make_subnet(), nn.Softplus())
        self.weight_net = nn.Sequential(*make_subnet(), nn.Softplus())

    def forward(self, x):
        mu = self.freq_net(x)
        v = self.var_net(x)
        w = self.weight_net(x)

        expanded = x.unsqueeze(2)
        mu = mu.unsqueeze(1)
        v = v.unsqueeze(1)
        w = w.unsqueeze(1)

        x_cos = torch.cos(2 * np.pi * expanded * mu)
        x_exp = torch.exp(-v * expanded**2)
        features = w * x_exp * x_cos

        return features.flatten(start_dim=1)

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

Уилсон и др. (2013) отмечают, что ядра спектральных смешений могут быть реализованы в таких библиотеках, как GPyTorch, в качестве ковариационных функций для использования в гауссовском процессе моделирования периодических паттернов, если они представляют спектральную плотность как смешение компонентов. Таким образом, наш подход адаптирует эту концепцию в нейронную сеть, параметризуя компоненты ядра - частоту, дисперсию и веса - посредством нейронных сетей. Это позволяет осуществлять обучение на основе данных и извлечение признаков, что может быть особенно актуально для таких задач, как прогнозирование временных рядов, где в данных присутствуют периодические/колебательные закономерности. На это обратил внимание Себастьян Калль (Sebastian Callh) в своем блоге о ядрах спектральных смешений. Идеи по использованию ядра мы частично черпали из документации по GPyTorch

Говоря кратко, вместо фиксированного набора параметров наша модель использует нейронные сети для обучения частотам, обозначаемым как мю, дисперсиям, обозначаемым как v, и весам, обозначаемым как w. Такой подход позволяет адаптироваться к различным шаблонам данных. Первый слой извлечения признаков вычисляет признаки, используя формулу 'w * exp(-v * x^2) * cos(2πμx)', которая улавливает периодичность посредством косинусной модуляции с плавностью экспоненциального затухания. Модульная конструкция, в которой извлечение признаков осуществляется с помощью слоя DeepSpectralMixtureLayer, а регрессионный модуль является отдельным компонентом, повышает гибкость. В свою очередь, регрессионная часть представляет собой глубокую, полносвязную нейронную сеть, которая с помощью сигмоидной функции активации сопоставляет признаки с одним выходным сигналом. Как уже упоминалось, это делает его подходящим для ограниченной регрессии или бинарной классификации.

Давайте внимательно рассмотрим приведенный выше код, чтобы увидеть, как реализуется все описанное выше. Наш код начинается со стандартных импортов для PyTorch и связанных библиотек. Эти импорты предоставляют инструменты для тензорных операций, построения нейронных сетей, оптимизации, обработки данных и экспорта обученной модели в ONNX для вывода результатов на сторонних платформах. Это имеет решающее значение для создания, обучения и развертывания модели. Это, как и следовало ожидать, имеет основополагающее значение не только для разработки на основе PyTorch, но и для машинного обучения в целом. В качестве рекомендации важно обеспечить совместимость версий PyTorch и ONNX для эффективной загрузки данных во время обучения.

Затем приступим к инициализации DeepSpectralMixtureLayer. Это определяет три нейронные сети, а именно частотную сеть, дисперсионную сеть и сеть весов. Каждая из сетей сопоставляет входные данные с количеством выходных компонентов, используя активацию Soft Plus, которая гарантирует, что дисперсии и веса остаются положительными, как того требует определение ядра. Это важно, потому что эти сети отдельно параметризуют ядро спектрального смешения, что позволяет извлекать признаки на основе данных с помощью гауссовских процессов. Параметр num_components управляет насыщенностью признаков. Его можно настроить в зависимости от сложности задачи. Использование нами активации soft plus позволяет избежать отрицательных значений, которые могут привести к численной нестабильности.

Затем настроим функцию прямого прохода для сети. Эта функция вычисляет характеристики спектрального смешения с помощью формулы, приведенной выше. Она преобразует входные данные и параметры таким образом, чтобы их можно было передать на выходной блок. Применение косинусной и экспоненциальной функций сглаживает выходные данные для слоев регрессии. Это основное преобразование имитирует ядро спектрального смешения. Форма входных данных x должна соответствовать формату (batch_size, input_dim). Кроме того, для проверки численной устойчивости в exp может потребоваться ограничение дисперсии v. Параметр num_components можно настроить в соответствии с желаемой размерностью признаков.

После рассмотрения слоя извлечения признаков, описанного выше, мы можем перейти к регрессору — классу, который называется так при определении модели. Главная цель состоит в том, чтобы объединить описанный выше слой спектрального смешения с полностью подключенным модулем. Модуль сопоставляет характеристики ядра с одним выходным сигналом в диапазоне 0-1 с помощью сигмоидной функции. Модуль архитектуры можно настраивать в зависимости от сложности задачи, увеличивая размеры/количество слоев. В задачах неограниченной регрессии сигмоидную функцию можно исключить. Наконец, у нас появилась и форвард-функция, как и в функции спектрального слоя, описанной выше. Входные данные проходят через слой спектрального смешения для извлечения признаков, а затем через модуль для получения окончательных выходных данных. Форвард-функция определяет весь прямой проход, обеспечивая сквозное обучение и вывод результатов.

Размер входного параметра x должен соответствовать input-dim. Нам вполне подойдет такая функция потерь, как BCE, учитывая использование сигмоидной функции активации. Параметр num-components очень чувствителен к производительности сети, и его значение определяет общее количество спектральных компонентов. Более высокие значения повышают выразительность, однако существует риск переобучения. Разумным подходом может быть начать со значения 64 и затем корректировать его в зависимости от сложности данных. Мониторинг переобучения в процессе тренировки может иметь решающее значение, учитывая чрезмерно выраженную природу слоя спектрального смешения. Регуляризация и досрочная остановка могут помочь смягчить эту проблему.

Анализ изученных параметров мю, v и w может помочь понять периодические паттерны, наблюдаемые в приведенных примерах GPyTorch. В отличие от типичных моделей гауссовых процессов, наш подход не содержит оценок неопределенности. Можно было бы дополнить эти показатели количественными мерами. Наконец, прямой проход может быть вычислительно затратным, особенно когда параметр num-components очень велик или используются входные данные высокой размерности. Это дополнительный фактор, который следует учитывать при тонкой настройке и оптимизации данной модели.


Реализация индикаторов

Судя по протестированным нами в предыдущей статье сигнальным паттернам, большинство из них достаточно хорошо показали себя в форвард-тестировании за исключением паттернов 0, 1 и 5. Вкратце, мы протестировали/оптимизировали 10 сигнальных паттернов для символа GBPUSD на 2023 год. Мы использовали 30-минутный временной интервал, в отличие от нашего обычного 4-часового, потому что сигналы Ишимоку обычно не так часты в течение длительных промежутков времени. Как обычно, наше форвард-тестирование охватывало 2024 год. Хотя 7 из 10 паттернов прошли форвард-тест за 2025 год, наше тестирование проводилось только в течение 2 лет, поэтому читателю всегда рекомендуется проводить дальнейшую тщательную проверку и тестирование на более длительных периодах исторических цен, прежде чем применять представленные здесь паттерны в реальной торговле.

С учетом вышеизложенного, давайте рассмотрим, как функции индикаторов Ишимоку и ADX Wilder реализованы на Python, а также их функции извлечения признаков для паттернов 0, 1 и 5.

Ишимоку в Python

Функция Ишимоку в Python реализована следующим образом:

def Ichimoku(df, tenkan_period: int = 9, kijun_period: int = 26, senkou_span_b_period: int = 52, displacement: int = 26) -> pd.DataFrame:
    """
    Calculate Ichimoku Kinko Hyo components and append them to the input DataFrame.

    The components are:
        - Tenkan-sen (Conversion Line)
        - Kijun-sen (Base Line)
        - Senkou Span A (Leading Span A)
        - Senkou Span B (Leading Span B)
        - Chikou Span (Lagging Span)

    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', and 'close' columns.
        tenkan_period (int): Lookback period for Tenkan-sen (default 9).
        kijun_period (int): Lookback period for Kijun-sen (default 26).
        senkou_span_b_period (int): Lookback period for Senkou Span B (default 52).
        displacement (int): Forward displacement of Senkou spans and backward shift of Chikou span (default 26).
        
    Returns:
        pd.DataFrame: Input DataFrame with added Ichimoku columns.
    """
    # Input validation
    required_cols = {'high', 'low', 'close'}
    if not required_cols.issubset(df.columns):
        raise ValueError("DataFrame must contain 'high', 'low', and 'close' columns")
    if not all(p > 0 for p in [tenkan_period, kijun_period, senkou_span_b_period, displacement]):
        raise ValueError("All period values must be positive integers")

    result_df = df.copy()

    # Tenkan-sen (Conversion Line)
    high_tenkan = result_df['high'].rolling(window=tenkan_period).max()
    low_tenkan = result_df['low'].rolling(window=tenkan_period).min()
    result_df['Tenkan_sen'] = (high_tenkan + low_tenkan) / 2

    # Kijun-sen (Base Line)
    high_kijun = result_df['high'].rolling(window=kijun_period).max()
    low_kijun = result_df['low'].rolling(window=kijun_period).min()
    result_df['Kijun_sen'] = (high_kijun + low_kijun) / 2

    # Senkou Span A (Leading Span A)
    result_df['Senkou_Span_A'] = ((result_df['Tenkan_sen'] + result_df['Kijun_sen']) / 2).shift(displacement)

    # Senkou Span B (Leading Span B)
    high_span_b = result_df['high'].rolling(window=senkou_span_b_period).max()
    low_span_b = result_df['low'].rolling(window=senkou_span_b_period).min()
    result_df['Senkou_Span_B'] = ((high_span_b + low_span_b) / 2).shift(displacement)

    # Chikou Span (Lagging Span)
    result_df['Chikou_Span'] = result_df['close'].shift(-displacement)

    return result_df

Приведенная выше функция вычисляет компоненты индикатора и добавляет их к входному фрейму данных с периодами по умолчанию 9 для Тенкан-Сен, 26 для Киджун-Сен, 26 для смещения и 52 для Сенку-Спан B. Первая строка нашего кода предназначена для проверки входных данных. Это гарантирует, что во фрейме данных есть необходимые столбцы с ценами, а параметры периода имеют положительное значение во избежание ошибок вычислений. Этот шаг имеет решающее значение для обеспечения целостности данных и предотвращения ошибок выполнения. Наша функция индикатора должна работать на корректных входных данных. Всегда рекомендуется проверять входные данные перед обработкой, чтобы корректно обрабатывать пропущенные данные или недопустимые параметры.

После завершения проверки переходим к вычислению Тенкан-Сен. Наш код вычисляет среднее значение наивысшего максимума и наинизшего минимума за прошедший tenkan-period. Как правило, мы используем период 9, который служит индикатором краткосрочного тренда. Этот буфер помогает сигнализировать о потенциальных разворотах при пересечении линии Киджун-Сен. Это может быть полезно при принятии краткосрочных торговых решений, поскольку отражает недавнюю динамику цен. Для достижения желаемой чувствительности к краткосрочным колебаниям цен можно внести корректировки в Tenkan-period. Более короткие периоды времени будут более чувствительными, а более длинные — менее чувствительными.

Затем мы вычисляем буфер Киджун-Сен. Подобно Тенкан-Сен, он вычисляется за более длительный период, обычно 26. Это позволяет получить среднесрочный индикатор тренда. Киджун-Сен важен, поскольку он подтверждает направление тренда и выступает в качестве уровня поддержки/сопротивления, что, как правило, обеспечивает несколько более стабильное представление о рынках по сравнению с Тенкан-Сен. Это полезно для среднесрочного анализа, и, по сути, пересечения с линией Тенкан-Сен могут служить торговыми сигналами.

Затем мы вычисляем Сенку-Спан А. Это просто среднее значение Тенкан-Сен и Киджун-Сен, сдвинутое вперед на период смещения. Обычно это смещение составляет 26. Этот буфер образует одну из границ облака Кумо. Это имеет решающее значение, поскольку показывает будущую поддержку/сопротивление, а это, в свою очередь, дает перспективный взгляд на анализ тренда, как отмечалось здесь. Важно отслеживать относительное положение облака по отношению к цене: цена выше него указывает на бычий тренд, а ниже — на медвежий.

Далее следует расчет Сенку-Спан B, где мы просто усредняем самый высокий максимум и самый низкий минимум за период, обычно составляющий 52, и смещаем его вперед, образуя вторую границу облака Кумо. Сенку B обеспечивает более долгосрочный взгляд на уровни поддержки/сопротивления и улучшает определение тренда, особенно на более длительных таймфреймах. В качестве ориентира можно использовать толщину облака, то есть расстояние между точками Сенку А и В, которое служит показателем волатильности. Таким образом, более толстые облака могут указывать на более сильную поддержку/сопротивление.

Наконец, мы вычисляем последний буфер, Чику-Спан. Этот буфер представляет собой просто цену закрытия, сдвинутую назад на период смещения, обычно 26, для сравнения текущих цен с прошлыми ценами. Это служит подтверждением тренда, показывая, находятся ли текущие цены выше или ниже прошлых, что полезно для подтверждения или проверки тренда. Таким образом, основная цель Чику-Спан — подтверждение данных, а для этого часто необходимы достаточные исторические сведения, учитывая использование смещений и длительные периоды усреднения.


ADX-Wilder на Python

Реализовав Ишимоку на Python, мы переходим к ADX Wilder. Эта функция вычисляет ADX, используя метод Уайлдера. Как и в случае с индикатором Ишимоку, мы добавляем к исходным данным о ценах дополнительные буферы ADX, +DI и -DI. Этот индикатор использует период по умолчанию, равный 14, который можно скорректировать для согласования с Ишимоку или для достижения большей ценовой чувствительности. Тем не менее, мы используем этот период по умолчанию, равный 14. Реализация в Python:

def ADX_Wilder(df, period: int = 14) -> pd.DataFrame:
    """
    Calculate the Average Directional Index (ADX) using Wilder's method and 
    append the ADX, +DI, and -DI columns to the input DataFrame.

    ADX measures trend strength, while +DI and -DI measure trend direction.
    
    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', and 'close' columns.
        period (int): The lookback period to use. Default is 14.
        
    Returns:
        pd.DataFrame: Input DataFrame with 'ADX', '+DI', and '-DI' columns.
    """
    if not all(col in df.columns for col in ['high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'high', 'low', and 'close' columns")
    if period <= 0:
        raise ValueError("Period must be a positive integer")

    result_df = df.copy()

    # Calculate directional movements
    up_move = result_df['high'].diff()
    down_move = result_df['low'].diff()

    plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0)
    minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0)

    # Calculate True Range (TR)
    high_low = result_df['high'] - result_df['low']
    high_close = np.abs(result_df['high'] - result_df['close'].shift(1))
    low_close = np.abs(result_df['low'] - result_df['close'].shift(1))
    tr = np.maximum(high_low, np.maximum(high_close, low_close))

    # Apply Wilder's smoothing
    atr = pd.Series(tr).rolling(window=period).mean()
    plus_di = 100 * pd.Series(plus_dm).rolling(window=period).sum() / atr
    minus_di = 100 * pd.Series(minus_dm).rolling(window=period).sum() / atr
    dx = 100 * np.abs(plus_di - minus_di) / (plus_di + minus_di)
    adx = dx.rolling(window=period).mean()

    result_df['+DI'] = plus_di
    result_df['-DI'] = minus_di
    result_df['ADX'] = adx

    return result_df

Первым делом мы проверяем входные данные функции. Это, как всегда, гарантирует наличие необходимых столбцов с ценами и корректность периода индикатора во избежание ошибок в расчетах. Все это важно для целостности данных и работы с достоверными данными. Кроме того, следует правильно обрабатывать случаи отсутствия данных. После проверки мы переходим к расчетам направления движения. Именно эти различия в максимумах и минимумах определяют положительное и отрицательное движение DM. Они показывают силу восходящих и нисходящих трендов, что является ключевой информацией для оценки и анализа направления тренда. Для точного расчета направленных различий важно обеспечить наличие достаточного количества исторических данных. Также необходимо отслеживать данные на наличие пробелов.

Затем мы переходим к вычислению истинных диапазонов. Эти значения рассчитываются как наибольшая из величин текущего максимума минус минимум, или максимума минус предыдущее закрытие, или минимума минус предыдущее закрытие. Для обеспечения согласованности используются абсолютные значения этих разностей. Этот диапазон нормализует направленные движения, делая их сопоставимыми для разных активов в соответствии с требованиями расчета ADX. Крайне важно убедиться в отсутствии пропущенных данных во входном фрейме данных о ценах, чтобы избежать ошибок, вызванных значением NaN в расчетах.

Затем мы переходим к вычислению более сглаженного истинного диапазона с использованием скользящего среднего. Мы также сглаживаем буферы плюсовой и минусовой DM, нормализуя положительные и отрицательные индикаторы направления таким образом, чтобы они были масштабированы до 100 для представления в процентах. Таким образом, направленный индекс DX, показатель силы тренда, будет представлять собой абсолютную разницу между положительным и отрицательным значением DM, нормированную по их сумме. Наконец, основной буфер индикатора, ADX, будет представлять собой сглаживание DX за период действия индикатора, который в нашем случае составляет 14. Полученные в итоге результаты служат показателем силы преобладающего тренда.

Как уже говорилось в предыдущей статье, ADX отображает значения силы тренда в диапазоне от 0 до 100, при этом значение выше 25 часто считается признаком сильного тренда. С другой стороны, значения ниже 20 рассматриваются как признак слабых или зарождающихся трендов. Положительный и отрицательный индексы направленности количественно оценивают силу бычьего и медвежьего трендов соответственно, а разница между ними потенциально может служить сигналом для входа в сделку. Используемый период в 14 также можно отрегулировать в соответствии с требуемой чувствительностью: меньшие значения приводят к более раннему обнаружению сигнала и фрактальных точек, тогда как более длинные периоды будут менее волатильными.

Применяя ядро спектрального смешения через регрессионную сеть, как мы определили после введения, мы получаем входные данные от этих двух индикаторов в виде простого двухмерного вектора. В некоторых предыдущих статьях мы рассматривали и исследовали возможность увеличения размера входного вектора для наших сетей обучения с учителем путем разложения различных сигналов каждого паттерна на логическое значение, равное 0 или 1. Это позволило добиться лучших результатов в тренировке/оптимизации, чего не удалось достичь при форвард-тесте. Таким образом, в настоящее время сигналы и паттерны каждого индикатора представляют собой то, что мы объединяем в единое логическое значение, а не его отдельные части. В двухмерном случае каждое измерение объединяет сигналы от двух индикаторов. Первый индикатор предназначен исключительно для подтверждения бычьего сигнала, а второй — медвежьего.



Признак 0

Мы применяем 3 отстающих или плохо работающих признака к нашей сети - признаки 0, 1 и 5. Реализуем признак 0 на Python следующим образом:

def feature_0(df):
    """
    """
    feature = np.zeros((len(df), 2))
    
    feature[:, 0] = ((df['close'].shift(1) < df['Senkou_Span_A'].shift(1)) &
                     (df['close'] > df['Senkou_Span_A']) &
                     (df['ADX'] >= 25.0)).astype(int)
    
    feature[:, 1] = ((df['close'].shift(1) > df['Senkou_Span_A'].shift(1)) &
                     (df['close'] < df['Senkou_Span_A']) &
                     (df['ADX'] >= 25.0)).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Функция начинается с создания пустого массива NumPy. Этот массив признаков имеет размер, соответствующий входному фрейму данных в строках, однако ему присвоены 2 столбца. Это обеспечивает чистый лист для генерации сигналов и гарантирует, что никакие остаточные значения не повлияют на результат. Затем присвоим значение первому столбцу этого фрейма данных, что является проверкой на наличие бычьего сигнала. +В соответствии с условиями, описанными в предыдущей статье, каждой строке в этом столбце массива NumPy присваивается значение 1, если предыдущая цена закрытия ниже Сенку-Спан A, текущая цена закрытия выше текущего Сенку-Спан A и ADX не менее 25. Обнаружение бычьего пересечения, при котором цена поднимается выше уровня Сенку-Спан A, часто указывает на восходящий тренд. Значение ADX не менее 25 гарантирует появление сигнала во время сильного тренда, что снижает количество ложных срабатываний на рынках со слабым трендом.

В данном случае, при использовании массивов NumPy, присваивание значений выполняется одновременно для нескольких строк. Это одна из главных причин, почему разработка и обучение моделей на Python так важны. Затем мы присваиваем значение столбцу с медвежьим трендом всем строкам массива. Здесь мы устанавливаем значение столбца в каждой соответствующей строке равным 1, если предыдущая цена закрытия выше предыдущего Сенку-Спан A, текущая цена закрытия ниже текущего Сенку-Спан A, а основной буфер ADX равен как минимум 25. Это указывает на медвежье пересечение, которое является признаком потенциального нисходящего тренда. Фильтр ADX гарантирует, что сила тренда поддерживает сигнал.

После присвоения бычьих и медвежьих значений мы принудительно обнуляем первые две строки нашего массива признаков, поскольку в этих строках из-за сдвигов возникают значения NaN. Мы смогли обучить нашу нейронную сеть, используя только этот паттерн, экспортировали его как 74_0.onnx и импортировали в пользовательский класс сигналов MQL5-советника. В предыдущей статье было показано, что этот конкретный паттерн не смог пройти форвард-тест, однако, когда мы тестируем его сейчас, используя сеть в качестве дополнительного фильтра для входных сигналов индикатора, мы получаем следующий результат:

r0

c0

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

p0



Признак 1

Еще один признак/паттерн, показавший крайне неудовлетворительные результаты в предыдущей статье, — это признак 1. Реализация в Python:

def feature_1(df):
    """

    """
    feature = np.zeros((len(df), 2))
    
    feature[:, 0] = ((df['Tenkan_sen'].shift(1) < df['Kijun_sen'].shift(1)) &
                     (df['Tenkan_sen'] > df['Kijun_sen']) &
                     (df['ADX'] >= 20.0)).astype(int)
    
    feature[:, 1] = ((df['Tenkan_sen'].shift(1) > df['Kijun_sen'].shift(1)) &
                     (df['Tenkan_sen'] < df['Kijun_sen']) &
                     (df['ADX'] >= 20.0)).astype(int)    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Мы снова инициализируем целевой выходной массив, также названный feature, нулями и задаем его размер в соответствии с размером входного фрейма данных. Для каждой строки данных предусмотрено 2 столбца для регистрации бычьих и медвежьих сигналов. В первом столбце мы присваиваем 1 при подтвержденном бычьем сигнале, если предыдущий Тенкан-Сен ниже предыдущего Киджун-Сен, текущий Тенкан-Сен теперь выше текущего Киджун-Сен, и ADX составляет не менее 20. Сигнал отражает бычье пересечение между Тенкан-Сен и Киджун-Сен. Это среднесрочный сигнал, предвосхищающий начало основных трендов, поэтому значение ADX не обязательно должно быть равно 25, а может быть равно 20.

Медвежий сигнал, заданный для второго столбца каждой строки массива NumPy, устанавливается равным 1 при условиях, зеркально отражающих наш бычий сигнал. Если предыдущий уровень Тенкан-Сен был выше уровня Киджун-Сен, а текущий уровень Тенкан-Сен ниже текущего уровня Киджун-Сен, и ADX равен по крайней мере 20 и продолжает расти, то это медвежий сигнал. Наш подход требует, чтобы для присвоения ненулевого значения было достигнуто несколько сигнальных точек. Это строгий подход, который явно контрастирует с альтернативным вариантом использования массивов большего размера, принимающих отдельные сигналы индикаторов в виде битов и поступающих на вход нашей сети, о котором мы упоминали выше. В заключение мы присваиваем нули первым двум строкам нашего массива NumPy, поскольку мы использовали сравнение сдвига, и некоторые точки данных содержат значения NaN вместо индикаторных данных. Ниже приведен отчет о тестировании паттерна 1 на валютной паре GBPUSD M30 за 2 года, включая тренировочный и тестовый периоды:

r1

c1

Наша нейронная сеть, которая использует обработку сигналов, описанную в предыдущей статье, в качестве фильтра, снова, по-видимому, улучшает результаты советника, собранного в Мастере MQL5. Бычий паттерн признака 1 на свечном графике может выглядеть так:

p1



Признак 5

Мы добрались до паттерна 5. Реализация на Python:

def feature_5(df):
    """

    """
    feature = np.zeros((len(df), 2))
    
    feature[:, 0] = ((df['close'].shift(2) > df['close'].shift(1)) &
                     (df['close'].shift(1) < df['close']) &
                     (df['close'].shift(2) > df['Tenkan_sen'].shift(2)) &
                     (df['close'] > df['Tenkan_sen']) &
                     (df['close'].shift(1) <= df['Tenkan_sen'].shift(1)) &
                     (df['+DI'] > df['-DI']) &
                     (df['ADX'] >= 25.0)).astype(int)
    
    feature[:, 1] = ((df['close'].shift(2) < df['close'].shift(1)) &
                     (df['close'].shift(1) > df['close']) &
                     (df['close'].shift(2) < df['Tenkan_sen'].shift(2)) &
                     (df['close'] < df['Tenkan_sen']) &
                     (df['close'].shift(1) >= df['Tenkan_sen'].shift(1)) &
                     (df['+DI'] < df['-DI']) &
                     (df['ADX'] >= 25.0)).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Наш формат аналогичен паттернам 0 и 1, описанным выше. Мы инициализируем и определяем размер выходного массива NumPy. Чтобы присвоить единицу первому столбцу, подтверждающему бычий сигнал, нам необходимо: буфер цены закрытия, способный отклониться в U-образном развороте над уровнем Тенкан-Сен. Кроме того, положительный DM должен быть выше отрицательного DM, а ADX должен составлять 25 или более. Такое движение цены часто сигнализирует о продолжении тренда, однако необходимо учитывать относительный размер буферов направленного движения ADX, а также его значения.

Во второй колонке вместо значения по умолчанию 0 присваивается 1, если цена отклоняется от уровня Тенкан-Сен снизу так, что текущая цена закрытия находится ниже него, а также если ADX не менее 25, и отрицательное направление движения превышает положительное, что является окончательным подтверждением сигнала. В заключение мы обнуляем первые 2 строки нашего массива признаков, чтобы учесть вычисления сдвига, которые приводят к значениям NaN, учитывая конечный характер входных данных. Мы протестировали и этот вариант в условиях, аналогичных двум другим описанным выше, и получили следующий результат.

r5

c5

Наша модель прибыльна, однако динамика эквити очень нестабильна. Таким образом, есть основания для дополнения машинного обучения типичными сигналами индикаторов. Хотя наши результаты не идеальны, а тестирование ограничено, можно утверждать, что дополнение сигналов индикаторов сетью на основе ядра спектрального смешения может использоваться в качестве дополнительного фильтра для уточнения точек входа в сделку и повышения эффективности.



Заключение

Мы исследовали использование нейронной сети на основе ядра спектрального смешения для точной настройки исходных торговых сигналов пары индикаторов Ишимоку и ADX-Wilder. Использованная нами специальная сеть показала определенные перспективы и, безусловно, продемонстрировала разницу в результатах по сравнению с тем, что мы видели в предыдущей статье, где сделки совершались исключительно на основе сигналов индикаторов. Представленные здесь результаты, как всегда, требуют от читателя дополнительной осмотрительности, прежде чем их можно будет принять или включить в существующие торговые системы. В процессе сборки советника в Мастере MQL5 можно выбрать несколько пользовательских сигналов.

файл описание
WZ-74.mq5 Созданный Мастером советник, в заголовок которого включены использованные в сборке файлы
SignalWZ-74.mqh Файл класса пользовательских сигналов
74-5.onnx ONNX-сеть для сигнального паттерна 5
74-1.onnx ONNX-сеть для сигнального паттерна 1
74-0.onnx ONNX-сеть для сигнального паттерна 0

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18776

Прикрепленные файлы |
WZ-74.mq5 (7.17 KB)
SignalWZ_74.mqh (15.37 KB)
74_5.onnx (10222.44 KB)
74_1.onnx (10222.44 KB)
74_0.onnx (10222.44 KB)
Преодоление ограничений машинного обучения (Часть 9): Обучение признаков на основе корреляции в задачах самообучения на финансовых данных Преодоление ограничений машинного обучения (Часть 9): Обучение признаков на основе корреляции в задачах самообучения на финансовых данных
Самоконтролируемое обучение (Self-supervised learning) - это мощная парадигма статистического обучения, которая заключается в поиске обучающих сигналов, генерируемых в результате самих наблюдений. Такой подход превращает сложные задачи обучения без наблюдения в более привычные задачи обучения под наблюдением. Эта технология не нашла применения для достижения нашей цели как сообщества алгоритмических трейдеров. Таким образом, наше обсуждение направлено на то, чтобы предоставить читателю доступный мостик к открытой исследовательской области самоконтролируемого обучения, и предлагает практические виды применения, которые позволяют создавать стабильные и надежные статистические модели финансовых рынков без переобучения небольшими наборами данных.
Как реализовать конкуренцию LLM-агентов в MetaTrader 5 Как реализовать конкуренцию LLM-агентов в MetaTrader 5
Статья описывает конкурентную архитектуру для MetaTrader 5, в которой десять LLM-агентов с разными торговыми правилами управляют собственным капиталом и открывают независимые позиции через уникальные magic numbers. Системный промпт и агрессивность агента адаптируются по результатам PnL и серии сделок. Представлен воспроизводимый каркас с режимами эксплуатации и контролируемыми метриками, пригодный для тестирования и дальнейшей оптимизации.
Нейросети в трейдинге: Адаптивное масштабирование представлений (Основные компоненты) Нейросети в трейдинге: Адаптивное масштабирование представлений (Основные компоненты)
Статья продолжает адаптацию фреймворка ADS под задачи трейдинга. Рассматривается отказ от PSRG и интеграцию его функций в PCRG, где адаптация выполняется в пространстве запросов. Применен порядок вычислений, аналогичный STCA, для линейного масштабирования по длине истории. Представлены OpenCL‑кернелы ConcatVecMatrix/Grad и класс CNeuronPCGR, что упрощает архитектуру и уменьшает вычислительную нагрузку при анализе длинных временных рядов.
Машинное обучение и Data Science (Часть 45): Прогнозирование временных рядов на форексе с моделью PROPHET от Facebook Машинное обучение и Data Science (Часть 45): Прогнозирование временных рядов на форексе с моделью PROPHET от Facebook
Разработанная компанией Faceboook модель Prophet позволяет прогнозировать временные ряды, чтобы выявлять тенденции, сезонность и влияние праздников с минимальной ручной настройкой. Метод широко применяется для прогнозирования спроса и бизнес-планирования. В этой статье мы исследуем эффективность модели Prophet в прогнозировании волатильности валютных инструментов. Проверим, можно ли ее применять вне контекста традиционных бизнес-задач.