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

Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем

MetaTrader 5Торговые системы |
355 0
Stephen Njuki
Stephen Njuki

Введение

В нашей предыдущей статье, где мы представили пару индикаторов MACD и OBV, мы рассмотрели 10 возможных сигналов, которые могут быть сгенерированы при взаимодополняющем сочетании этих индикаторов. Как обычно, мы тестируем 10 сигнальных паттернов, полученных из пары из двух индикаторов, выполняя форвард-тест в течение года, предварительно проведя обучение или оптимизацию в течение предыдущего года. В предыдущей статье только один сигнальный паттерн (паттерн 7) смог принести прибыль при форвард-тесте. Мы рассмотрели некоторые причины, по которым эта пара индикаторов показала неудовлетворительные результаты по сравнению с другими индикаторами, однако это также предоставило нам возможность изучить возможности машинного обучения для улучшения некоторых из этих сигнальных паттернов.

В этой статье мы рассмотрим, как сверточная нейронная сеть (CNN), использующая рациональное квадратичное ядро, может помочь и, возможно, улучшить интерпретацию и применение сигналов индикаторов. Рационально-квадратичное (Rational Quadratic, RQ) ядро определяется следующей формулой:

f1

где:

  • k(x,x′) - значение сходства ядра между двумя входными данными x и x′. Значение варьируется от 0 до 1, при этом значения, близкие к 1, указывают на более высокую степень сходства.

  • x и x′ — два входных вектора (например, срезы одномерного сигнала, векторы признаков или координаты во времени). Это могут быть скалярные значения (размерность = 1) или многомерные векторы.

  • ∥x−x′∥2 - квадрат евклидова расстояния между двумя входными векторами. Он измеряет расстояние между x и x′ в пространстве признаков. Расстояние можно получить по следующей формуле:
  • f2

  • l — это масштаб длины (или характерный масштаб) ядра. Он определяет, как быстро уменьшается сходство с расстоянием:
    • Маленькая l - резкое затухание (локальная чувствительность).
    • Большая l - медленное затухание (широкое сходство).

  • α или alpha - параметр смешения масштабов (иногда называемый параметром формы). Он контролирует соотношение больших и малых масштабов изменчивости:

    • При α → бесконечность ядро становится эквивалентным квадратично-экспоненциальное ядру (RBF).
    • Маленькая α означает, что ядро имеет более тяжелые хвосты, что позволяет осуществлять взаимодействия на больших расстояниях.

В нашей реализации рационального квадратичного ядра (Rational Quadratic Kernel, RQK) в сверточной нейронной сети (CNN) мы используем его для задания размеров ядер и каналов CNN. Однако существуют и альтернативные способы использования этого ядра в CNN, которые читатель может рассмотреть.


Альтернативные варианты применения и преимущества RQ-ядра

Во-первых, следует упомянуть об адаптивном маскировании внимания (adaptive attention masking). В этом варианте использования ядро RQ формирует пространственные или временные маски внимания внутри CNN. Это помогает в сегментации или прогнозировании временных рядов. Этот метод работает за счет выделения областей, обладающих высоким сходством с эталонной областью, например, с центроидным окном. Ещё одним возможным применением RQ-ядра может быть пулинг с учётом расстояния (distance aware pooling). В этом сценарии объединяются только активации сети в пределах заданного порога сходства в рамках RQ-ядра вместо усреднения всех значений. Это позволяет осуществлять адаптивный пулинг с учетом расстояния, при этом принимается во внимание близость объектов.

Ещё одним примером применения может быть масштабирование важности признаков (feature importance scaling). В данном случае к каналам CNN будет применяться взвешивание плавным обученным образом. Наконец, ядро RQ можно использовать для уменьшения или "обрезки" весов CNN, влияние которых, измеряемое ядром, минимально в пределах входного пространства сети. Причины, по которым это ядро может и должно применяться в CNN, также многогранны. Ниже представлена таблица, которая пытается это обобщить.

Устойчивость к выбросам Длинные "хвосты" RQ-ядер делают архитектуру менее восприимчивой к пространственным или временным аномалиям.
Плавные переходы Плавное затухание ядра обеспечивает менее резкие сдвиги в представлении данных между слоями. Это полезно для градиентного потока.
Динамическая архитектура В ядре используется выбор размеров ядра CNN или расширение каналов с учетом особенностей данных на основе мер сходства.

Данная конструкция CNN (conv-1d), использующая RQ-ядро, идеально подходит для одномерных входных данных, где важна локализованная схожесть, например, для финансовых временных рядов, которые мы рассматриваем в этой статье. В качестве других подходящих типов данных можно рассмотреть аудиосигналы и обнаружение бинарных паттернов. При использовании этой CNN важно нормализовать или стандартизировать входные векторы до диапазона 0–1, как мы это делали с сетями в предыдущих статьях. Форма входных данных обычно имеет вид (batch_size, input_length), при этом входные векторы заполнены нулями или единицами. 

RQ-ядро имеет ряд важных гиперпараметров, как указано в приведенной выше формуле. Первым среди них является alpha (альфа), который, как указано в формуле, регулирует затухание хвоста. При более высоких значениях ядро ведет себя скорее как обычное RBF-ядро. Однако, по мере уменьшения этого значения, RQ-ядро действует как комбинация нескольких радиальных базисных функций (RBF) в разных масштабах, управляемых параметром alpha. Еще одним гиперпараметром является length-scale (масштаб длины), который определяет чувствительность сходства к расстоянию.

Если наша нейронная сеть представляет собой классификатор с выходными данными в виде распределения вероятностей, то для обучения можно использовать функцию потерь BCE (BCE loss-function). Ввиду нашей нетрадиционной архитектуры, правильная инициализация и планирование скорости обучения также могут иметь здесь важное значение. Дальнейшее расширение функционала может быть достигнуто за счет добавления остаточных связей между слоями для улучшения распространения градиента. Кроме того, интеграция RQ-ядра может быть выполнена в качестве механизма управления. Между слоями также можно использовать алгоритмы отсеивания на основе сходства.

Исследования указывают на то, что RQ-ядра определяют веса Conv1D и таким образом улавливают определенные закономерности в данных. Применение этого ядра явно снижает общее количество необходимых параметров и повышает интерпретируемость, однако может ограничивать адаптивность по сравнению с ядрами, полученными в результате обучения. Доказательства лучших результатов также указывают на целесообразность использования этого ядра в слоях гауссовского процесса и оптимизации гиперпараметров в более широком проектировании CNN, как, например, описано в этой статье о глубоких гауссовских процессах со сверточными ядрами. 

Использование ядер определенного размера действительно уменьшает количество обучаемых параметров и снижает риск переобучения, особенно в случаях, когда данные ограничены. Это было проиллюстрировано в классификации болезней маниоки здесь. Однако, несмотря на то, что интерпретируемость, обеспечиваемая использованием предварительно заданных ядер, помогает лучше понять извлечение признаков, что резко контрастирует с ядрами, обучаемыми по принципу "черного ящика", существуют некоторые проблемы с их адаптацией. Ядра с фиксированным значением, как правило, хуже адаптируются к данным, чем ядра, обученные на основе данных, и это потенциально может привести к снижению производительности при решении сложных задач. Это подтверждается результатами исследования маниоки, ссылка на которое приведена выше, где гибридные ядра показали лучшие результаты, чем отдельные RQ-ядра. Часть результатов исследования представлена ниже:

Тип ядра Точность
RQ-ядро 88,5%
Квадратично-экспоненциальное ядро 88,0%
Гибридное ядро 90,1%

Приведенная выше таблица, основанная на исследовании болезней маниоки, показывает небольшие различия в производительности между обученными и фиксированными ядрами. Рекомендуется гибридный подход. Ранее RQ-ядро выполняло неоднозначную роль. Однако, как указано в некоторых ссылках выше, такие исследования, как это, это и это, помогли выявить возможности его применения в глубоких гауссовых процессах (Deep-Gaussian-Process), особенно для обработки изображений и адаптации к Conv1D. Исследование болезней маниоки сосредоточилось на заранее определенных весах как средстве баланса между фиксированными и усвоенными подходами.

При проектировании используемых сетей размер ядра определялся относительно гиперпараметра length-scale l. Благодаря использованию нескольких каналов для захвата данных в разных масштабах, что соответствует практике CNN с различными рецептивными полями, в окончательной реализации для обеспечения гибкости был применен глобальный усредняющий пулинг. Это обеспечило независимость выходных данных от длины последовательности и хорошо подошло для скалярного диапазона выходных данных от 0 до 1.


Сеть

Наша нейронная сеть реализована на Python, потому что, как мы уже утверждали в предыдущих статьях, кодирование и обучение на Python более эффективно, чем выполнение тех же задач на MQL5. Мы реализуем CNN на Python. Сеть масштабирует свои ядра и каналы с помощью RQ-ядра следующим образом:

import torch
import torch.nn as nn
import torch.nn.functional as F

import pandas as pd
import numpy as np

class RationalQuadraticConv1D(nn.Module):
    def __init__(self, alpha=1.0, length_scale=1.0, input_length=100):
        super(RationalQuadraticConv1D, self).__init__()
        self.alpha = alpha
        self.length_scale = length_scale
        self.input_length = input_length

        # Deeper and wider design
        self.kernel_sizes, self.channels = self._design_architecture()

        self.conv_layers = nn.ModuleList()
        in_channels = 1

        for i, (out_channels, kernel_size) in enumerate(zip(self.channels, self.kernel_sizes)):
            layer = nn.Sequential(
                nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size, padding=kernel_size // 2),
                nn.BatchNorm1d(out_channels),
                nn.ReLU(),
                nn.Dropout(0.2)
            )
            self.conv_layers.append(layer)
            in_channels = out_channels

        # Fully connected head to increase parameter count
        self.head = nn.Sequential(
            nn.AdaptiveAvgPool1d(1),
            nn.Flatten(),
            nn.Linear(in_channels, 128),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid()
        )

    def _rq_kernel(self, x, x_prime):
        dist_sq = (x - x_prime) ** 2
        return (1 + dist_sq / (2 * self.alpha * self.length_scale ** 2)) ** -self.alpha

    def _design_architecture(self):
        x = torch.linspace(0, 1, self.input_length)
        center = 0.5
        responses = self._rq_kernel(x, center)
        thresholded = responses[responses > 0.01]  # allow more spread

        num_layers = 10
        kernel_sizes = [3 + (i % 4) * 2 for i in range(num_layers)]  # cyclic 3, 5, 7, 9
        channels = [32 * (i + 1) for i in range(num_layers)]  # 32, 64, ..., 320

        return kernel_sizes, channels

    def forward(self, x):
        x = x.unsqueeze(1)  # (B, 1, L)
        for layer in self.conv_layers:
            x = layer(x)
        return self.head(x)

Для начала определим пользовательский модуль PyTorch, который будет принимать на вход некоторые из ключевых параметров, выделенных выше в формуле RQ-ядра. Наш класс, который мы назвали RationalQuadraticConv1D, наследует от nn.Module, который, как мы видели в наших предыдущих статьях, является базовым классом для всех модулей нейронных сетей PyTorch. Конструктор инициализирует три важных параметра RQ-ядра: alpha, length-scale и input-length. Как уже упоминалось выше, параметр Alpha контролирует форму ядра, параметр length-scale определяет расстояние, на котором ядро затухает, а параметр input-length задает ожидаемый размер/длину входного вектора.

Эти первые шаги важны, поскольку они закладывают основу модели и позволяют настраивать поведение ядра. Это также гарантирует соответствие архитектуры размерам входного вектора. Настройка alpha и length-scale должна проводиться для регулирования чувствительности ядра к различиям во входных данных. Например, меньшее значение length-scale позволит сосредоточиться на локальных закономерностях, в то время как больший масштаб позволит выявить более общие сходства. Как всегда, input-length следует проверить на соответствие размеру входных данных. 

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

После этого в последующих строках кода мы создаем гибкий стек сверточных слоев с динамическими свойствами. Функция nn.ModuleList() создает контейнер для переменного числа сверточных слоев. Цикл перебирает пары значений out-channels, то есть количество выходных каналов, с указанием kernel-size, которое в данном случае относится к размеру сверточного ядра; эти значения считываются из self.channels и self.kernel-size соответственно. Каждый слой nn.Conv1D принимает in-channels, начиная с 1 для первого слоя. Он выводит данные в out-channels и использует kernel-size с заполнением для определения следующего размера ядра, чтобы сохранить длину входных данных. Затем in-channels преобразуются в out-channels для следующего слоя, образуя таким образом цепочку.

Это важно, поскольку динамическая конструкция позволяет модели иметь заданное количество слоев и параметров. Одновременно с этим, заполнение обеспечит соответствие длины выходных данных длине входных, что является критически важным фактором при работе с последовательными данными. Большие значения kernel-size позволяют, как правило, улавливать более общие закономерности входных данных, в то время как большее количество out-channels увеличивает разнообразие признаков, извлекаемых из данных. Если сохранение полной длины не имеет значения, можно внести корректировки в заполнение, например, установить значение 0 при понижении разрешения.

Затем мы переходим к определению "финальной" сети, которая обрабатывает выходные данные свертки, преобразуя их в единое значение, подобное вероятности. Оно определяет последовательность операций, включающую nn.AdaptiveAvgPool1D, nn.Flatten, nn.Linear и nn.Sigmoid. Эти функции отвечают за: уменьшение пространственной размерности до 1 путем усреднения по последовательности; преобразование тензора в одномерный вектор; отображение сглаженных признаков на единый выходной сигнал; и вывод значения от 0 до 1, подходящего для бинарной классификации или регрессии соответственно. 

На заключительном этапе происходит объединение функций и создается результат, специфичный для конкретной задачи. Это делает модель пригодной для таких целей, как бинарная классификация или оценка вероятности. В случае задач с несколькими классами замену линейного и сигмоидного слоев можно выполнить с помощью nn.Linear(in_channels, num_classes) и nn.Softmax() соответственно. Для регрессионного анализа потребуется исключить сигмоидную функцию, поскольку она лучше подходит для классификации.

В нашем классе есть функция, которая вычисляет сходство между точками данных или векторами. Это функция _rq_kernel. Это сходство, как и следовало ожидать, рассчитывается с помощью RQ-ядра. В результате вычислений определяется евклидово расстояние между x и x-prime. Используется приведенная выше формула RQ-ядра, которая уменьшается с увеличением расстояния при умеренном значении гиперпараметров alpha и length-scale. RQ-ядро количественно оценивает сходство между входными данными, что служит основой для проектирования архитектуры сверточной нейронной сети (CNN).

Его гибкость заключается в том, что он способен улавливать как локальные, так и глобальные закономерности. Большее значение alpha приближает ядро к гауссовому распределению, тогда как меньшее обеспечивает большую гибкость. Также важно регулировать length-scale для контроля затухания, при этом меньшие значения подчеркивают локальные сходства.

Это приводит нас к упомянутой выше функции _design_architecture. Она динамически проектирует или создает архитектуру CNN, используя профиль затухания RQ-ядра. Первым шагом в этой функции является создание нормализованного диапазона позиций входных данных. Это x. Затем мы устанавливаем опорную точку для вычислений сходства, которую определяем как center (центр). После этого мы вычисляем "ответы" (responses), то есть отклик RQ-ядра для каждой позиции относительно центра. Далее мы сортируем позиции для откликов ядра, превышающих 0,2, одновременно фокусируясь на значимых областях.

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

Эта функция адаптирует CNN к структуре сходства данных. Использование ядер большего размера хорошо подходит для выявления более широких закономерностей, а большее количество каналов позволяет извлекать более сложные признаки. Пороговое значение 0,2 и коэффициенты масштабирования 4 для ядер и 60 для каналов являются настраиваемыми. Для задач, требующих более глубокого извлечения признаков, количество слоев может быть любым, однако необходимо найти баланс с вычислительными затратами.

Наконец класс RationalQuadraticConv1D завершается методом прямого прохода. Он выполняет вычисления модели от входных данных до выходных. Метод начинает с добавления размерности канала к входным данным, чтобы обеспечить совместимость с nn.Conv1D. Затем мы применяем к каждому сверточному слою функцию активации ReLU для внесения нелинейности. Затем мы пропускаем результат через заключительные слои, чтобы получить окончательный результат. Наша функция прямого прохода определяет поток данных модели, используя динамически разработанную архитектуру для получения признаков, используемых в прогнозировании.

Крайне важно убедиться, что форма входных данных соответствует формату (batch_size, input_length) и что используемый тип активации подходит для решения поставленной задачи. В сети часто встречаются несоответствия в форме входных данных, поэтому крайне важно проводить отладку, выводя форму входных данных на каждом шаге. Теперь мы можем рассмотреть, как задавать входные параметры сети, изучив реализации индикаторов MACD и OBV на Python.


MACD

Наши индикаторы написаны на Python с нуля, хотя существуют библиотеки и модули, которые могут решить некоторые из этих задач. Они предназначены для более плавной и менее ресурсоемкой компиляции и выполнения. Индикатор MACD вычисляет линию MACD, сигнальную линию и гистограмму; все эти элементы помогают анализировать цену актива. Реализация в Python:

def MACD(df, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> pd.DataFrame:
    """
    Calculate Moving Average Convergence Divergence (MACD) and append it to the input DataFrame.

    The MACD line is the difference between a fast and slow EMA.
    The Signal line is an EMA of the MACD line.
    The Histogram is the difference between the MACD and Signal lines.
    
    Args:
        df (pd.DataFrame): DataFrame with a 'close' column.
        fast_period (int): Lookback period for the fast EMA. Default is 12.
        slow_period (int): Lookback period for the slow EMA. Default is 26.
        signal_period (int): Lookback period for the Signal line EMA. Default is 9.
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'MACD', 'Signal_Line', and 'MACD_Histogram' columns.
    """
    # Input validation
    if 'close' not in df.columns:
        raise ValueError("DataFrame must contain a 'close' column")
    if not all(p > 0 for p in [fast_period, slow_period, signal_period]):
        raise ValueError("All period values must be positive integers")
    if fast_period >= slow_period:
        raise ValueError("fast_period must be less than slow_period")

    # Create a copy to avoid modifying the original DataFrame
    result_df = df.copy()
    
    # Calculate Fast and Slow Exponential Moving Averages (EMA)
    ema_fast = result_df['close'].ewm(span=fast_period, adjust=False).mean()
    ema_slow = result_df['close'].ewm(span=slow_period, adjust=False).mean()
    
    # Calculate MACD line
    result_df['MACD'] = ema_fast - ema_slow
    
    # Calculate Signal line (EMA of MACD)
    result_df['Signal_Line'] = result_df['MACD'].ewm(span=signal_period, adjust=False).mean()
    
    # Calculate MACD Histogram
    result_df['MACD_Histogram'] = result_df['MACD'] - result_df['Signal_Line']
    
    return result_df

Формат нашей функции соответствует тому, что мы использовали ранее: она принимает фрейм данных pandas со столбцом цены закрытия и тремя необязательными целочисленными параметрами: fast-period (быстрый период, по умолчанию 12), slow-period (медленный период, по умолчанию 26) и signal period (период сигнала, по умолчанию 9). Использование фрейма данных во входных данных, как мы это делали до сих пор, следует за импортом ценовых данных с помощью модуля MT5 Python. Наша функция возвращает копию входного фрейма данных с добавленными столбцами для значений MACD, линии MACD и линии сигнала.

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

Затем мы создаем копию входного фрейма данных, которая служит для хранения наших выходных данных, сохраняя при этом исходные данные. Этот метод позволяет сохранить целостность входного фрейма, который может использоваться в нескольких функциях, и при этом не требуется искажение результатов анализа. В Python он обеспечивает модульность и возможность повторного использования кода. После создания копии входного фрейма мы используем функцию экспоненциально взвешенного скользящего среднего из библиотеки pandas для вычисления EMA цен закрытия за быстрый и медленный периоды. При этом мы присваиваем параметру adjust значение False, что гарантирует использование стандартной формулы EMA, а параметр span задает размер окна. Выбор параметра span напрямую влияет на чувствительность: меньшие значения делают EMA более отзывчивым, а большие значения сглаживают шум.

После завершения вычисления быстрой и медленной EMA мы присваиваем первую новую серию pandas выходному фрейму данных, который мы обозначаем как MACD. Это позволяет вычислить линию MACD как разницу между быстрой и медленной EMA. Линия MACD — это ключевой индикатор, показывающий сближение или расхождение долгосрочных и краткосрочных движений цен. Подводя итог, можно сказать, что положительное значение указывает на бычий тренд, а отрицательное — на медвежий. Мониторинг направления и величины линии MACD может служить индикатором силы тренда. Пересечения могут сигнализировать об изменении тренда.

Далее вычисляем серию сигналов pandas для нашего выходного фрейма данных. Это расчет сигнальной линии в виде EMA линии MACD за signal-period. Он сглаживает линии MACD и сигнальные линии, предоставляя ориентир для торговых сигналов. Пересечения линий MACD и сигнальных линий также являются важными точками принятия решений. При значении периода по умолчанию, равном 9, этот буфер обеспечивает баланс между быстродействием и сглаживанием, но его можно дополнительно настроить в зависимости от условий торгуемого актива.

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


OBV

Эта функция вычисляет балансовый объем (On-Balance-Volume) - индикатор объема, который проводит аналогию между изменением цены и объемом. Реализация в Python:

def OBV(df) -> pd.DataFrame:
    """
    Calculate On-Balance Volume (OBV) and append it as an 'OBV' column 
    to the input DataFrame.

    OBV is a momentum indicator that uses volume flow to predict changes in stock price.
    
    Args:
        df (pd.DataFrame): DataFrame with 'close' and 'volume' columns.
        
    Returns:
        pd.DataFrame: Input DataFrame with a new 'OBV' column.
    """
    # Input validation
    if not all(col in df.columns for col in ['close', 'tick_volume']):
        raise ValueError("DataFrame must contain 'close' and 'volume' columns")

    # Create a copy to avoid modifying the original DataFrame
    result_df = df.copy()
    # print(result_df.columns)
    
    # Calculate the direction of price change
    # np.sign returns 1 for positive, -1 for negative, and 0 for zero change
    direction = np.sign(result_df['close'].diff())
    
    # Calculate the OBV
    # Multiply the volume by the direction of price movement
    # Then calculate the cumulative sum
    obv = (result_df['tick_volume'] * direction).cumsum()
    
    # The first value of diff() is NaN, so the first OBV will be NaN.
    # We can fill it with the first day's volume or 0. A common practice is to start at 0.
    result_df['OBV'] = obv.fillna(0)
    
    return result_df

Приведенный выше исходный код начинается с проверки фрейма данных, чтобы убедиться, что он содержит необходимые столбцы: цену закрытия и объем торгов (tick-volume). Мы полагаемся на тиковый объем, поскольку он, как правило, более доступен для большинства классов активов, в отличие от реального объема, который трудно получить для валютных пар из-за их децентрализованной природы. После завершения этой проверки мы создаем копию входного фрейма данных, поскольку мы намерены добавлять входные данные, и, как уже говорилось выше в случае с функцией MACD, может быть целесообразно сохранить целостность входных данных. 

Исходя из этого, мы переходим к расчету direction (направление) изменения цены закрытия. Эти расчеты выполняются за последовательные периоды. Функция np.sign используется для обозначения значений -1, +1 или 0; соответственно, это означает снижение цены, повышение цены или отсутствие изменения цены. Этот шаг важен для определения направления движения цены, которое, в свою очередь, определяет, как накапливается объем на OBV. Этот шаг также упрощает анализ ценовых трендов, преобразуя их в знак направления движения, что облегчает интеграцию с данными об объеме торгов. 

Определив вектор направления, мы переходим к вычислению OBV, то есть произведения объема и этого направления на кумулятивную сумму, чтобы отслеживать OBV во времени. OBV учитывает изменения цены в зависимости от объема, суммируя объемы в периоды роста и вычитая их в периоды падения. Таким образом, значение OBV может отражать давление покупателей или продавцов. Тренд OBV важнее его абсолютного значения. Сравнение знака и направления цены может помочь подтвердить тренд.

Следующая строка кода заполняет значение NaN в первой записи OBV, полученное в результате вычислений функции diff(), поскольку это предотвратило бы сравнение. Эта строка также добавляет значение OBV в новую серию OBV pandas в нашем выходном фрейме данных pandas. Выбор OBV в конкретном случае этого индикатора является практичным, поскольку 0 — это нейтральное значение, означающее отсутствие колебаний объема в любом направлении. Это не то же самое, что присвоить 0 буферу цен скользящей средней.


Предыдущие замечания

Многие из сигнальных паттернов, описанных в предыдущей статье, где рассматривалась пара индикаторов MACD и OBV, не принесли прибыли при форвард-тесте. Только паттерн 7 прошел форвард-тест, в то время как в остальных парах обычно из 10 тестируемых паттернов как минимум 6 показывают прибыльные результаты. Отберем несколько примеров с очень низкими результатами, описанных в предыдущей статье, и посмотрим, сможем ли мы добиться лучших результатов, применив обучение с учителем. Для данной статьи мы выберем 3 паттерна из 9 предыдущих отстающих. Это паттерны 2, 3 и 5.

Паттерн 2

Реализуем паттерн 2 в Python следующим образом;

def feature_2(macd_df, obv_df, price_df):
    """

    """
    feature = np.zeros((len(macd_df), 2))
    
    feature[:, 0] = ((price_df['low'] < price_df['low'].shift(1)) &
                     (macd_df['MACD_Histogram'] > macd_df['MACD_Histogram'].shift(1)) &
                     (obv_df['OBV'] > obv_df['OBV'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'] > price_df['high'].shift(1)) &
                     (macd_df['MACD_Histogram'] < macd_df['MACD_Histogram'].shift(1)) &
                     (obv_df['OBV'] < obv_df['OBV'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Подводя итог, можно сказать, что этот метод основан на дивергенции цены и тренда, где более низкий минимум цены и растущий MACD указывают на скрытую бычью дивергенцию, а более высокий максимум цены в сочетании с падающим MACD также свидетельствует о скрытой медвежьей дивергенции. Это сочетается с подтверждением объема, где растущий OBV подтверждает накопление, что является бычьим сигналом, тогда как падающий OBV указывает на распределение, что является медвежьим сигналом. Вкратце, эта модель призвана подтвердить дивергенцию, выявляемую на основе ослабления импульса, несмотря на ценовые экстремумы, с помощью сигналов, связанных с объемом движения.

Таким образом, этот паттерн позволяет выявлять закономерности продолжения трендов. Паттерн наиболее надежен в условиях устоявшихся трендов, таких как бычьи восходящие тренды и медвежьи нисходящие тренды. Кроме того, перед началом его применения часто требуется подтверждение на протяжении двух баров. Мы проводим тестирование на CNN, в архитектуре которой используется RQ-ядро, принимая на вход сигналы из этого паттерна за период с 01.01.2023 по 01.01.2025, после обучения и оптимизации, проведенных с 01.01.2023 по 01.01.2024 на валютной паре GBPJPY H4. Отчет о тестировании:

r2

c2

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

p2

Паттерн 3

В Python мы реализуем этот паттерн так:

def feature_3(macd_df, obv_df, price_df):
    """
    """
    feature = np.zeros((len(macd_df), 2))
    
    feature[:, 0] = ((macd_df['MACD_Histogram'] > macd_df['MACD_Histogram'].shift(1)) &
                     (macd_df['MACD_Histogram'].shift(1) < macd_df['MACD_Histogram'].shift(2)) &
                     (obv_df['OBV'] > obv_df['OBV'].shift(1))).astype(int)
    
    feature[:, 1] = ((macd_df['MACD_Histogram'] < macd_df['MACD_Histogram'].shift(1)) &
                     (macd_df['MACD_Histogram'].shift(1) > macd_df['MACD_Histogram'].shift(2)) &
                     (obv_df['OBV'] < obv_df['OBV'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Этот паттерн отслеживает развороты на гистограмме MACD, при этом бычий сигнал подтверждается минимумом, который определяется подъемом гистограммы после снижения. С другой стороны, медвежий сигнал представляет собой подтверждение пика в виде нисходящей гистограммы, которая на последних барах демонстрировала восходящую динамику. Гистограмма предназначена для выявления точек перегиба импульса. Для правильного определения паттерна пика/впадины и уменьшения количества ложных сигналов также требуется 3 бара.

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

r3

c3

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

p3

Паттерн 5

Реализация паттерна в Python:

def feature_5(macd_df, obv_df, price_df):
    """

    """
    feature = np.zeros((len(macd_df), 2))
    
    feature[:, 0] = ((macd_df['MACD_Histogram'] > macd_df['MACD_Histogram'].shift(1)) &
                     (macd_df['MACD_Histogram'] < 0.0) &
                     (obv_df['OBV'] > obv_df['OBV'].shift(1))).astype(int)
    
    feature[:, 1] = ((macd_df['MACD_Histogram'] < macd_df['MACD_Histogram'].shift(1)) &
                     (macd_df['MACD_Histogram'] > 0.0) &
                     (obv_df['OBV'] < obv_df['OBV'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Паттерн 5 основан на контексте нулевой линии MACD. Цель стратегии — ранние точки входа, при этом бычий сигнал срабатывает, когда MACD находится ниже нуля на медвежьей территории, а медвежий — когда MACD находится выше нуля. Использование стороннего индикатора для сигнализации изменения импульса в момент срабатывания сигнала паттерна может помочь избежать ложных сигналов. Фильтр направления объема также должен совпадать с изменением динамики объема, что подтверждает заинтересованность институциональных игроков.

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

r5

c5

Похоже, из трех протестированных паттернов этот по-прежнему испытывает трудности с форвард-тестом. Это может быть связано с ложными показаниями MACD из-за раннего определения момента времени без подтверждения независимых импульсных разворотов. Вид паттерна на графике:

p5



Заключение

Мы вернулись к комбинации индикаторов MACD и OBV, которую представили в предыдущей статье, с целью выяснить, сможем ли мы исправить неудовлетворительные результаты некоторых из этих сигнальных паттернов. Нам удалось это сделать для 2 из 3 повторно рассмотренных паттернов благодаря использованию CNN с обучением с учителем, использующей рациональное квадратичное ядро для задания размеров ядра и каналов. Неудовлетворительные результаты паттерна 5 отнесем к слабым сигналам входа, требующим дополнительного подтверждения.

Имя Описание
WZ-72.mq5 Созданный Мастером советник, в заголовке которого описаны использованные файлы
SignalWZ_72.mqh Пользовательский файл класса сигнала для сборки в Мастере
72_2.onnx Экспортируемая сеть Python для сигнального паттерна 2
72_3.onnx
Экспортируемая сеть Python для сигнального паттерна 3
72_5.onnx
Экспортируемая сеть Python для сигнального паттерна 5
Приложенный файл пользовательского сигнала предназначен для сборки советника с помощью Мастера MQL5. Новички могут найти подробности здесь.

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

Прикрепленные файлы |
WZ-72.mq5 (6.91 KB)
SignalWZ_72.mqh (13.67 KB)
72_2.onnx (7541.91 KB)
72_3.onnx (7541.91 KB)
72_5.onnx (7541.91 KB)
Машинное обучение и Data Science (Часть 44): Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR) Машинное обучение и Data Science (Часть 44): Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR)
В этом материале мы познакомимся с тем, как модели векторной авторегрессии (VAR) могут прогнозировать временные ряды значений OHLC (цены открытия, максимум, минимум и цена закрытия) на форексе Поговорим о том, как реализовать VAR-модели, обучать их и строить прогнозы в MetaTrader 5 в реальном времени, чтобы анализировать взаимозависимые движения валютных курсов для получения лучших результатов в трейдинге.
Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA) Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)
Статья посвящена реализации алгоритма искусственного поискового роя (ASSA) на MQL5 в составе унифицированного тестового стенда. Разобраны три поведенческих правила движения, механизм сигнала и глобального табло, нормализация пространства, а также параметры stepRatio и Pc. Читатель получит готовую основу для интеграции ASSA, а также ответ на вопрос — насколько тактическая метафора оказалась удачным фундаментом для конкурентоспособности оптимизационного алгоритма.
Внедрение в MQL5 практических модулей из других языков (Часть 02): Создание библиотеки REQUESTS, как в Python Внедрение в MQL5 практических модулей из других языков (Часть 02): Создание библиотеки REQUESTS, как в Python
В этой статье опишем реализацию модуля, аналогичного модулю requests в Python, чтобы упростить отправку и получение веб-запросов в MetaTrader 5 с использованием MQL5.
Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности
В данной статье мы разрабатываем систему отслеживания стратегий на языке MQL5, которая обнаруживает сигналы пересечения скользящих средних, отфильтрованные долгосрочной скользящей средней, моделирует или исполняет сделки с настраиваемыми уровнями TP и SL в пунктах, а также отслеживает результаты, такие как попадание в TP/SL, для анализа эффективности.