Возможности Мастера MQL5, которые вам нужно знать (Часть 68): Использование паттернов TRIX и процентного диапазона Уильямса с сетью косинусного ядра
Введение
Из десяти сигнальных паттернов, рассмотренных в предыдущей статье, только три прошли форвард-тест. Паттерны были получены путем комбинирования сигналов индикатора тренда TRIX и осциллятора уровня поддержки/сопротивления процентный диапазон Уильямса (Williams Percent Range, WPR). Обучение/оптимизация советника были ограничены одним годом (2023), а дальнейший анализ прогнозов проводился в течение следующего года (2024). Мы проводили тестирование на CHFJPY H4.
При расширении наших паттернов, прошедших форвард-тест, средствами машинного обучения мы обычно применяем Python, поскольку он очень эффективно реализует и обучает нейронные сети. Это верно даже без графического процессора. В предыдущих статьях мы начинали с реализаций на Python функций для паттернов, прошедших форвард-тест. В этой статье мы затронем реализацию индикаторов на Python, но в основном сосредоточимся на настройке сети, которая принимает сигналы индикатора в качестве входных данных. Это одномерная сверточная нейронная сеть, использующая в своей архитектуре ядро косинуса.
Индикаторы на Python
Для использования индикаторных сигналов в Python для нашей нейронной сети мы можем использовать некоторые библиотеки Python Code или написать код самостоятельно. Наша функция TRIX написана на Python следующим образом:
def TRIX(df: pd.DataFrame, period: int) -> pd.DataFrame: """ Calculate TRIX indicator and append it as 'TRIX' column to the input DataFrame. Args: df (pd.DataFrame): DataFrame with 'close' column period (int): Lookback period for EMA calculation Returns: pd.DataFrame: Input DataFrame with new 'TRIX' column """ # Input validation if not all(col in df.columns for col in ['close']): raise ValueError("DataFrame must contain 'close' column") if period < 1: raise ValueError("Period must be positive") # Create a copy to avoid modifying the input DataFrame result_df = df.copy() # Calculate triple EMA ema1 = df['close'].ewm(span=period, adjust=False).mean() ema2 = ema1.ewm(span=period, adjust=False).mean() ema3 = ema2.ewm(span=period, adjust=False).mean() # Calculate TRIX: percentage rate of change of triple EMA result_df['main'] = ema3.pct_change() * 100 return result_df
TRIX вычисляет скорость изменения тройного сглаженного EMA. Входными данными для нас является фрейм данных pandas, содержащий столбец close и целочисленный период для расчетов EMA. В результате получается тот же фрейм данных с добавленным столбцом, содержащим значения индикаторов. В основном он используется для определения направления тренда и потенциальных разворотов путем сглаживания данных о ценах и выделения изменений импульса.
В приведенном выше коде функция определяется с указанием типов входных данных, что обеспечивает ясность и безопасность типов. Затем мы вычисляем первую экспоненциальную скользящую среднюю (EMA) для цен закрытия и сглаживаем эти данные о ценах для обеспечения согласованного веса EMA. Затем мы вычисляем вторую EMA на основе первой EMA для дальнейшего сглаживания данных и уменьшения шума. Это присваивает значение переменной ema2. После этого мы вычисляем третью EMA, завершая процесс тройного сглаживания. Это также делает TRIX чувствительным к изменениям импульса. В результате получаем значение ema3.
Таким образом, мы добавляем к нашему входному фрейму данных pandas вычисление TRIX, выраженное в виде процентного изменения тройной EMA (ema3) относительно ее предыдущего значения. В этом процессе умножение на 100 масштабирует результат для большей удобочитаемости и позволяет адаптировать его к диапазону процентов от 0 до 100. Определив это, перейдем к процентному диапазону Уильямса (WPR). Реализуем это следующим образом на Python:
def WPR(df: pd.DataFrame, period: int) -> pd.DataFrame: """ Calculate Williams %R indicator and append it as 'WPR' column to the input DataFrame. Args: df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns period (int): Lookback period for calculation Returns: pd.DataFrame: Input DataFrame with new 'WPR' column """ # Input validation if not all(col in df.columns for col in ['high', 'low', 'close']): raise ValueError("DataFrame must contain 'high', 'low', 'close' columns") if period < 1: raise ValueError("Period must be positive") # Create a copy to avoid modifying the input DataFrame result_df = df.copy() # Calculate highest high and lowest low over the period high_max = df['high'].rolling(window=period).max() low_min = df['low'].rolling(window=period).min() # Calculate Williams %R result_df['main'] = ((high_max - df['close']) / (high_max - low_min)) * -100 return result_df
WPR — это осциллятор поддержки/сопротивления, который помогает определить, находится ли цена в зоне перекупленности/сопротивления или в зоне перепроданности/поддержки. Входными данными для нашей функции при вычислении этого параметра также является фрейм данных pandas, но с используемыми столбцами high, low и close, а также целочисленным периодом для определения окна ретроспективного анализа индикатора. В результате также получается фрейм данных pandas с добавленным столбцом, который мы обозначаем как WPR. Она содержит значения в диапазоне [-100, 0].
В начале нашего кода, как и в TRIX, используются подсказки типов для входных данных. Затем мы вычисляем наивысший максимум за указанный период, формируя таким образом верхнюю границу для расчета WPR. Затем мы аналогичным образом вычисляем самый низкий минимум за тот же период времени. Далее мы определяем дополнительный буфер, который необходимо добавить к нашему входному фрейму данных pandas, и он обозначается как WPR. Здесь используется стандартная формула WPR.
Способность Python без особых усилий преобразовывать результаты вычислений в буферы — это то, к чему программистам, переходящим с языков типа C, таким как я, необходимо привыкнуть. Это невероятно.
Для обеих функций можно ввести дополнительную проверку, поскольку они предполагают, что входной фрейм данных содержит необходимые столбцы и достаточное количество строк данных. Мы используем реализацию данных pandas из модуля Python MetaTrader 5. Однако добавление обработки ошибок не обязательно для отсутствующих столбцов, поскольку MetaTrader 5 всегда имеет свои столбцы, но может быть полезно для небольшого количества строк данных, что может улучшить обе функции. Кроме того, для больших наборов данных ключевым моментом должна стать оптимизация путем предварительного вычисления скользящих окон или использования векторизованных операций (как это реализовано в pandas). При тестировании проверка результатов на соответствие известным значениям индикаторов с таких платформ, как MetaTrader, также может помочь обеспечить точность.
Преимущества использования косинусного ядра для архитектуры Conv1D
Одномерная сверточная нейронная сеть (Conv1D) — это особый тип сверточной нейронной сети, использующий одномерные сверточные операции над данными в последовательности, такими как финансовые временные ряды или обычный текст. В этом процессе применяются фильтры, которые скользят по входным данным и извлекают важные паттерны, тренды или мотивы из этих данных. Результатом этой экстракции является, по сути, уменьшение размерности входной последовательности. Однако каждый фильтр предназначен для вывода определенной/важной характеристики входных данных. Сеть будет включать в себя слои для свертки, активации, субдискретизации и полносвязные слои. Метод Conv1D эффективен для обработки упорядоченных данных, имеющих временные или последовательные различия.
Ядро косинуса, с другой стороны, это мера сходства. Эта программа вычисляет косинус угла между двумя заданными векторами, определяя, насколько схожи их направления. Это достигается путем нормализации скалярного произведения двух векторов по их величинам. Выходные значения варьируются от -1, что означает, что два вектора направлены в противоположные стороны, до +1, что означает, что два вектора выровнены и направлены в одну сторону. Такое сходство может быть очень эффективным при работе с многомерными данными, такими как текст, где величины не так важны, как ориентация.
Использование ядра косинуса при проектировании Conv1D действительно дает ряд преимуществ. Во-первых, это обеспечивает плавное изменение размеров ядра. Функция косинуса создает плавный колебательный паттерн для размеров ядра, что позволяет сверточной нейронной сети улавливать признаки в различных масштабах без резких изменений. Еще одним преимуществом является адаптивное увеличение количества каналов, поскольку масштабирование каналов на основе косинуса обеспечивает постепенное увеличение числа фильтров. Это позволяет сбалансировать сложность модели и возможности извлечения признаков на всех слоях.
Ядро косинуса также позволяет эффективно извлекать признаки. Это объясняется его осциллирующим характером, имитирующим естественные закономерности во временных рядах с возвратом к среднему значению, таких как финансовые временные ряды. Это может помочь сверточной нейронной сети обнаруживать периодические или циклические закономерности. Наконец, ядро косинуса вносит эффект регуляризации благодаря изменяющимся размерам ядра и каналам. Такая "регуляризация" снижает риск переобучения за счет введения контролируемой изменчивости в архитектуру.
Использование косинусного ядра с Conv1D часто требует, чтобы входные данные представляли собой трехмерный тензор, форма которого соответствует параметрам batch_size, input_channels и input_length. Поскольку мы используем одномерные временные ряды, значение входных каналов равно 1. Достаточная длина входных данных позволяет легко обрабатывать размеры ядер без чрезмерного уменьшения.
В данном случае для настройки нашей модели необходимо выбрать пять ключевых гиперпараметров. Первый из них - base_channels. В идеале их должно быть небольшое количество на начальном этапе, около 16 или максимум 32. Количество слоев, второй параметр, обычно должно находиться в диапазоне от 3 до 5. Слишком большое количество слоев приводит к проблеме затухания градиента или к чрезмерным вычислениям. Третий важный параметр — максимальный размер ядра, который можно установить равным 7 или больше для более длинных последовательностей. Значение должно быть нечетным, чтобы сохранить симметрию с помощью отступов.
Следующий параметр — частота, которая управляет колебаниями размеров ядра и каналов. Значение 0,5 означает умеренность, а корректировки в диапазоне от 0,1 до 1,0 позволяют добиться соответственно более быстрых и более медленных колебаний. Последним важным гиперпараметром является коэффициент отсеивания (drop out rate). Этот параметр можно установить в диапазоне от 0,2 до 0,5, и он важен для регуляризации. Хотя более высокие значения отсеивания уменьшают переобучение, они влияют на результат функции потерь, поскольку ей трудно достичь идеальной нулевой границы.
При обучении модели можно использовать стандартные оптимизаторы, такие как Adam, и планировщик скорости обучения для более быстрой и точной сходимости. Повторюсь, достаточная пакетная нормализация и отсеивание (dropout) имеют решающее значение для предотвращения переобучения, особенно при работе с небольшими наборами данных. В идеале, при использовании косинусного сходства для определения размера ядра слоя, выходные данные для классификации должны быть бинарными, то есть представлять собой отдельный нейрон с сигмоидной активацией. Полносвязные слои следует модифицировать для решения других задач, таких как многоклассовая классификация или регрессия. В идеале, примерами использования являются временные ряды, в которых ожидаются периодические или колебательные закономерности.
Ограничениями использования этого ядра при формировании сверточной нейронной сети являются то, что оно может быть неоптимальным для всех наборов данных. Тестирование на стандартных архитектурах Conv1D важно для обеспечения сопоставимой производительности. Кроме того, метод субдискретизации по среднему глобальному показателю предполагает фиксированный размер выходных данных. Это может не подойти для задач, требующих последовательного вывода результатов.
Сеть
Наша сеть, принимающая сигналы от TRIX и WPR в качестве бинарного входного вектора, представляет собой сверточную нейронную сеть, которая использует косинусное сходство для определения размера своих ядер, как было описано выше. Реализуем это следующим образом:
class CosineConv1D(nn.Module): """ A 1D Convolutional Neural Network with kernel sizes and channels based on cosine functions. Outputs a scalar float in [0,1] using sigmoid activation. """ def __init__(self, input_channels: int, base_channels: int, num_layers: int, input_length: int, max_kernel_size: int = 7, frequency: float = 0.5, dropout_rate: float = 0.3): super(CosineConv1D, self).__init__() if input_channels < 1 or base_channels < 1 or num_layers < 1: raise ValueError("Input channels, base channels, and num layers must be positive") if input_length < 1: raise ValueError("Input length must be positive") if max_kernel_size < 1: raise ValueError("Max kernel size must be positive") if not (0 <= dropout_rate < 1): raise ValueError("Dropout rate must be between 0 and 1") self.layers = nn.ModuleList() self.input_length = input_length current_length = input_length for i in range(num_layers): kernel_size = int(3 + (max_kernel_size - 3) * (1 + np.cos(2 * np.pi * frequency * i)) / 2) kernel_size = max(3, min(kernel_size, max_kernel_size)) channels = int(base_channels * (1 + 0.5 * np.cos(np.pi * i / num_layers))) channels = max(base_channels, channels) padding = kernel_size // 2 conv_layer = nn.Sequential( nn.Conv1d( in_channels=input_channels if i == 0 else self.layers[-1][0].out_channels, out_channels=channels, kernel_size=kernel_size, padding=padding ), nn.BatchNorm1d(channels), nn.ReLU(), nn.Dropout(dropout_rate) ) self.layers.append(conv_layer) current_length = (current_length - kernel_size + 2 * padding) // 1 + 1 self.global_pool = nn.AdaptiveAvgPool1d(1) self.fc = nn.Sequential( nn.Linear(channels, 1), nn.Sigmoid() ) def forward(self, x: torch.Tensor) -> torch.Tensor: for layer in self.layers: x = layer(x) x = self.global_pool(x) x = x.squeeze(-1) x = self.fc(x) return x def get_output_length(self) -> int: return self.layers[-1][0].out_channels
Если коротко, одномерная сверточная нейронная сеть использует размеры ядра и каналы, модулированные косинусом, и выдает скалярное значение в диапазоне [0,1] с помощью сигмоидной активации. В ней используется собственный модуль PyTorch, наследующий свойства модуля nn. Такая архитектура, позволяющая модулировать размеры ядер и количество каналов, обеспечивает адаптацию к входным паттернам. Сигмоидная функция активации обеспечивает получение на выходе скалярной величины вероятностной формы в диапазоне 0-1.
Инициализация сети сочетается с проверкой. Это гарантирует корректность входных параметров для количества каналов, слоев, размера ядра и коэффициента отсеивания. Основные проверки здесь заключаются в том, что входные данные положительные и что коэффициент отсечения находится в диапазоне от 0 до 1. Это крайне важно для предотвращения ошибок некорректной конфигурации, которые могут привести к ошибкам выполнения или снижению производительности. Это также обеспечивает гибкость в проектировании сети за счет использования этих настраиваемых параметров.
При реализации, как правило, входные каналы следует настраивать в соответствии с размерами входных данных. Базовые каналы используются для управления "мощностью модели", а количество слоев обеспечивает баланс между глубиной и вычислительными требованиями. Максимальный размер ядра и частота могут быть настроены или скорректированы для оптимизации извлечения признаков на каждом этапе прямого прохода.
После завершения инициализации и проверки мы переходим к определению размера ядра и масштабированию каналов. Как уже упоминалось выше, мы используем косинусные функции для динамической корректировки размеров ядер и количества каналов на каждом слое. Поэтому мы вычисляем размер ядра и количество каналов для каждого слоя, используя косинусные функции. Размер ядра будет варьироваться от 3 до максимального значения параметра размера ядра. Количество каналов увеличивается, начиная с базового значения параметра количества каналов. Они также модулируются функцией косинуса.
Всё это важно, потому что мы вводим динамические изменения в рецептивные поля, а также в емкость признаков, что позволяет сети улавливать разнообразные паттерны. Косинусоидальная модуляция обеспечивает плавные переходы между ядрами, избегая резких изменений. При реализации мы настраиваем частоту, обычно в диапазоне от 0,1 до 1,0, чтобы контролировать колебания размера ядра. Использование каналов с более высоким базовым уровнем часто подходит для более сложных/многомерных наборов данных. Всегда важно следить за тем, чтобы параметр максимального размера ядра совпадал с длиной входных данных, чтобы избежать чрезмерного заполнения. Таким образом, мы переходим к построению сверточного слоя. Это достигается с помощью пакетной нормализации, функции активации ReLU и отсеивания для повышения устойчивости.
Каждый слой строится как последовательность одномерной свертки, пакетной нормализации, активации ReLU и отсеивания. Первый слой использует входные каналы. Последующие слои используют выходные каналы предыдущего слоя. Заполнение помогает сохранить длину входных данных. Такой подход важен, поскольку каждый из компонентов слоя играет решающую роль. Свертка используется для извлечения признаков. Для обеспечения стабильности обучения добавлена пакетная нормализация. Активация ReLU помогает обеспечить нелинейность. И наконец, механизм отсеивания помогает в регуляризации, предотвращая переобучение. При реализации модели целесообразно использовать коэффициент отсеивания в диапазоне от 0,2 до 0,5 для управления риском переобучения. Важно убедиться, что входные каналы соответствуют данным, а модуль 'nn.ModuleList' можно адаптировать для динамического управления слоями. После этого мы занимаемся глобальной субдискретизацией и выводом результатов.
Здесь применяется адаптивное усредняющее объединение для уменьшения пространственного измерения до 1. Затем следует линейный слой, отображающий значение на один выходной сигнал, и сигмоидная функция для ограничения его значения диапазоном от 0 до 1. Это важно, потому что объединение данных суммирует характеристики по всей последовательности, что позволяет получать выходные данные фиксированного размера независимо от длины входных данных. Линейный слой и сигмоидная функция создают скалярную величину для таких задач, как классификация. В нашем случае мы используем эту сеть для получения единственного выходного сигнала. Как правило, в таких случаях следует убедиться, что каналы последнего слоя соответствуют входному параметру линейного слоя.
Далее мы определяем функцию прямого прохода. Эта важнейшая функция обрабатывает входные данные, пропуская их через слои, проводит субдискретизацию и преобразование в конечный результат. Она определяет прямой проход сети, которая обрабатывает входные данные x. Это достигается с помощью сверточных слоев, глобальной субдискретизацией, сжимающей пространственное измерение, и, наконец, с помощью полносвязного слоя. Функция важна, поскольку она определяет поток данных. Это обеспечивает корректные преобразования от входных данных к выходным. Сжатие дополнительного пространственного измерения позволяет исключить измерение единственного элемента для обеспечения совместимости с линейным слоем. При реализации необходимо обеспечить корректность формы тензора. Для устранения несоответствий в форме можно проверить выходные данные слоев.
Наконец, у нас есть функция для отслеживания длины выходных данных. Такое отслеживание обеспечивает совместимость и, как уже говорилось выше, может также использоваться при отладке. Функция возвращает количество выходных каналов из последнего сверточного слоя. Это предоставляет важные метаданные о выходных данных сети, которые полезны для последующих задач или отладки. Эта функция также может использоваться для проверки совместимости с последующими слоями или моделями. Также ее можно расширить, если потребуется больше метаданных, таких как данные и т. д.
Последовательности и обучение
У нас также есть функция создания последовательностей для предварительной обработки данных в нашей нейронной сети. Эта функция служит для подготовки входных данных и меток в последовательности для обработки в одномерной сверточной нейронной сети (1D CNN). Реализация в Python:
def create_sequences(data, labels, sequence_length): num_samples, num_features = data.shape sequences = [] seq_labels = [] # Ensure labels is 1D labels = labels.flatten() for i in range(num_samples - sequence_length + 1): sequences.append(data[i:i+sequence_length].T) # Transpose to (num_features, sequence_length) seq_labels.append(labels[i+sequence_length-1]) # Use label of last sample in sequence sequences = np.array(sequences) # Shape: (num_sequences, num_features, sequence_length) seq_labels = np.array(seq_labels).reshape(-1, 1) # Shape: (num_sequences, 1) return torch.tensor(sequences, dtype=torch.float32), torch.tensor(seq_labels, dtype=torch.float32)
При создании последовательностей сначала мы создаем те, длина которых соответствует входному параметру sequence-length (длина последовательности), на основе входных данных, имеющих форму [образцы, признаки], а также соответствующих меток. Затем эти данные преобразуются в формат, соответствующий входным данным сверточной нейронной сети (CNN), который имеет вид [признаки, длина последовательности]. Метки берутся из последнего временного шага каждой последовательности. Это важно, потому что это подготавливает данные для одномерной сверточной нейронной сети, структурируя их в связанные фрагменты данных, которые мы называем "последовательностями" (sequences). Это соответствует требованиям совместимости с нашей сетью CosineConvID, описанной выше.
Определение длины последовательности основано на временных зависимостях, то есть на расстоянии, на котором данные, например, во временном ряду, имеют повторяющиеся, отслеживаемые закономерности. Крайне важно обеспечить соответствие количества функций количеству входных каналов. Для задач обучения с учителем и сложными наборами данных также потребуется проверка выравнивания меток.
После определения функции создания последовательностей, перейдем к функциям обучения и оценки. Первым делом настроим гиперпараметры. В первом разделе функции, состоящем из четырех строк кода, мы определяем гиперпараметры обучения. Нам необходимо задать размер пакета для обработки мини-пакетов; входные каналы для условий покупки и продажи; длину последовательности, количество эпох и скорость обучения. Длина нашей последовательности установлена на 1, что означает, что ценовые бары напрямую связаны друг с другом без запаздывания. Таким образом, мы предполагаем наличие внутрипаттерновых связей с задержкой в 1 неделю. Функция обучения записывается следующим образом:
def train_and_evaluate(x_train, y_train): # Hyperparameters batch_size = 32 input_channels = 2 # TRIX and WPR sequence_length = 1 # Adjustable based on your needs num_epochs = 10 learning_rate = 0.0005 # Create sequences X_tensor, y_tensor = create_sequences(x_train, y_train, sequence_length) num_sequences = X_tensor.shape[0] # Initialize model model = CosineConv1D( input_channels=input_channels, base_channels=128, num_layers=16, input_length=sequence_length, max_kernel_size=7, frequency=0.5, dropout_rate=0.03 ) # Loss and optimizer criterion = nn.BCELoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) # Training loop model.train() for epoch in range(num_epochs): total_loss = 0 for i in range(0, num_sequences, batch_size): batch_X = X_tensor[i:i+batch_size] # Shape: (batch_size, 2, sequence_length) batch_y = y_tensor[i:i+batch_size] outputs = model(batch_X) loss = criterion(outputs, batch_y) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() avg_loss = total_loss / (num_sequences // batch_size) print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}") # Export model to ONNX model.eval() dummy_input = torch.randn(1, input_channels, sequence_length) torch.onnx.export( model, dummy_input, inp_model_name, export_params=True, opset_version=11, do_constant_folding=True, input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} ) print("\nModel exported to: ", inp_model_name) # Load and verify ONNX model try: onnx_model = onnx.load(inp_model_name) onnx.checker.check_model(onnx_model) print(f" ONNX model '{inp_model_name}' has been successfully exported and validated!") session = ort.InferenceSession(inp_model_name) for i in session.get_inputs(): print(f" Input: {i.name}, Shape: {i.shape}, Type: {i.type}") for o in session.get_outputs(): print(f" Output: {o.name}, Shape: {o.shape}, Type: {o.type}") except onnx.onnx_cpp2py_export.checker.ValidationError as e: print(f" ONNX model validation failed: {e}") except Exception as e: print(f" An error occurred: {e}") # Evaluate on a single sample with torch.no_grad(): single_input = X_tensor[0:1] # Shape: (1, 2, 50) scalar_output = model(single_input).squeeze() print(f"\nSingle sample input shape: {single_input.shape}") print(f"Single sample output shape: {scalar_output.shape}") print(f"Single sample output value: {scalar_output.item():.4f}")
Эти шаги важны, поскольку гиперпараметры контролируют эффективность обучения, возможности модели и скорость сходимости. Небольшая длина последовательности упростит ввод данных, в то время как скорость обучения повлияет на стабильность оптимизации. В зависимости от ограничений памяти, часто целесообразно использовать размер пакета в диапазоне от 16 до 64. Как уже подчеркивалось, входной канал должен соответствовать характеристикам данных. Для лучшей сходимости также можно увеличить количество эпох или настроить скорость обучения, в идеале в диапазоне от 0,0001 до 0,001.
Следующий шаг в нашей функции обучения — инициализация экземпляра модели. Мы решили создать такую модель со 128 базовыми каналами, 16 слоями и низким коэффициентом отсечения (dropout rate) 0,03. Мы проводим обучение с использованием центрального процессора (CPU), а не графического (GPU). Данная конфигурация сетевой архитектуры обрабатывает входные данные в виде последовательностей длиной 1 и обеспечивает баланс между сложностью и регуляризацией.
Затем мы определяем функцию потерь и оптимизатор. В данном случае используется бинарная кросс-энтропийная функция потерь для бинарной классификации и оптимизатор Adam с заданной скоростью обучения. Это крайне важно, поскольку BCE-Loss подходит для сигмоидного выходного сигнала модели. Оптимизатор Adam эффективно работает с адаптивными скоростями обучения. BCELoss идеально подходит для бинарных задач, и в нашем случае мы выводим одно значение в диапазоне от 0 до 1. Однако, если сходимость слишком медленная, можно рассмотреть и другие оптимизаторы, такие как SGD или даже адаптированные скорости планирования обучения.
Далее последует цикл обучения. Это переводит модель в режим обучения путем итерации по эпохам и пакетам. Она вычисляет прогнозы, рассчитывает потери, выполняет обратное распространение ошибки и обновляет веса. Затем она выводит средний размер потерь за эпоху. Это важно, учитывая, что обучение — это основная логика, которая оптимизирует модель, минимизируя потери. Использование пакетной обработки значительно повышает эффективность. При выполнении этой операции важно отслеживать значения функции потерь и оценивать скорость сходимости. В случае, если наблюдается плато потерь, можно внести корректировки в количество эпох или размер пакета. Важно убедиться, что для сброса градиентов вызывается функция zero-grad.
После этого необходимо выполнить проверку и экспорт нашей модели в ONNX. После завершения обучения мы создаем объявленную модель в формате ONNX с фиктивными входными данными, проверяем модель, а затем создаем сессию выполнения ONNX. Это позволяет использовать динамические размеры пакетов. Экспорт в ONNX важен, поскольку он позволяет развертывать модели на различных платформах, наиболее актуальной из которых, на наш взгляд, является MQL5. На этом этапе мы также проверяем целостность советника, что позволяет избежать множества ошибок в последующих этапах, если модель будет использоваться позже. При экспорте важно убедиться, что opset-version совместим с целевой платформой. Значение 12 пока что отлично работает с MQL5. Ошибки валидации можно устранить, проверив совместимость моделей.
Затем у нас есть код для оценки или тестирования нашей модели после обучения. Эта функция выполняет оценку на одной последовательности без вычисления градиента, выводя на экран формы входных/выходных данных и скалярный результат. Это позволяет проверить работоспособность модели на основе обучающих весов, а также формата выходных данных для отдельного образца. Это может быть полезно для отладки. Ее также можно использовать для подтверждения поведения модели. При использовании важно убедиться, что форма входных данных соответствует обучающим данным. Также следует проверить диапазон выходного сигнала, чтобы убедиться, что он находится в пределах от 0 до 1.
Подводя итог, можно сказать, что эти две вспомогательные функции для нашего класса нейронных сетей подготавливают данные и обучают модель CosineConv1D для бинарной классификации. Это достигается с помощью архитектуры с косинусной модуляцией. Важные этапы включают создание последовательности, настройку гиперпараметров, обучение модели, экспорт модели в ONNX и оценку. Для достижения оптимальной производительности необходимо принять дополнительные меры, включая настройку длины последовательности, скорости обучения и количества эпох. Проверка модели ONNX перед экспортом также имеет решающее значение.
Реализация средствами MQL5
В предыдущих статьях, где мы рассматривали применение машинного обучения для расширения использования сигнальных паттернов индикаторов, мы не углублялись в реализацию на языке MQL5, поскольку нам постоянно не хватало "места". В этой статье, поскольку реализация функций в Python стала слишком однообразной, я подумал, что мы рассмотрим некоторые аспекты, которые необходимо учитывать со стороны MQL5 при импорте и использовании экспортированной модели ONNX.
В пользовательском классе сигналов, который импортирует модели ONNX и оформляется в виде советника с помощью Мастера MQL5, наши условия для покупки и продажи формируются следующим образом:
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalML_TRX_WPR::LongCondition(void) { int result = 0, results = 0; vectorf _x; _x.Init(2); _x.Fill(0.0); //--- if the model 1 is used if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_BUY)) { _x[0] = 1.0f; double _y = RunModel(0, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_1; results++; } } //--- if the model 4 is used if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_BUY)) { _x[0] = 1.0f; double _y = RunModel(0, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_4; results++; } } //--- if the model 5 is used if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_BUY)) { _x[0] = 1.0f; double _y = RunModel(0, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_5; results++; } } //--- return the result //if(result > 0)printf(__FUNCSIG__+" result is: %i",result); if(results > 0 && result > 0) { return(int(round(result / results))); } return(0); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalML_TRX_WPR::ShortCondition(void) { int result = 0, results = 0; vectorf _x; _x.Init(2); _x.Fill(0.0); //--- if the model 1 is used if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_SELL)) { _x[1] = 1.0f; double _y = RunModel(0, POSITION_TYPE_SELL, _x); if(_y < 0.0) { result += m_pattern_1; results++; } } //--- if the model 4 is used if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_SELL)) { _x[1] = 1.0f; double _y = RunModel(0, POSITION_TYPE_SELL, _x); if(_y < 0.0) { result += m_pattern_4; results++; } } //--- if the model 5 is used if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_SELL)) { _x[1] = 1.0f; double _y = RunModel(0, POSITION_TYPE_SELL, _x); if(_y < 0.0) { result += m_pattern_5; results++; } } //--- return the result //if(result > 0)printf(__FUNCSIG__+" result is: %i",result); if(results > 0 && result > 0) { return(int(round(result / results))); } return(0); }
Как видно из этих двух функций, мы очень часто используем функцию RunModel. Код выглядит так:
//+------------------------------------------------------------------+ //| Forward Feed Network, to Get Forecast State. | //+------------------------------------------------------------------+ double CSignalML_TRX_WPR::RunModel(int Index, ENUM_POSITION_TYPE T, vectorf &X) { vectorf _y(1); _y.Fill(0.0); ResetLastError(); if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, X, _y)) { printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError()); return(double(_y[0])); } //printf(__FUNCSIG__ + " y: "+DoubleToString(_y[0],5)); if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } return(double(_y[0])); }
Класс-якорь для всех этих функций наследует базовый класс CExpertSignal для обработки сигналов советников MQL5. Это позволяет интегрироваться с экосистемой или файлами классов, используемыми советниками, собранными с помощью мастера. Мы обучили 3 модели для каждого из паттернов, прошедших форвард-тест: 1, 4 и 5. После импорта в MQL5 тестовые запуски выдали нам следующие отчеты:


Для паттерна 1


Для паттерна 4


Для паттерна 5
Новички могут найти вводное руководство здесь с дополнительными ссылками на инструкции по использованию прилагаемого кода для создания советника с помощью Мастера MQL5. Созданный нами пользовательский класс сигналов разработан для простой интеграции с Мастером MQL5, что позволяет использовать его в различных советниках. Интеграция машинного обучения теперь стала реальностью, поскольку мы можем использовать модели ONNX для повышения точности прогнозирования с помощью советников. Для этого необходим надежный набор обучающих данных. Проверка моделей с помощью тестирования на выборках, не входящих в обучающую, важна для предотвращения переобучения, поэтому для наших целей мы используем разделение данных 50/50: обучение проводится в течение одного года, а форвард-тест — в течение следующего.
Судя по результатам тестовых запусков, все модели 1, 4 и 5 прошли форвард-тест, хотя, по-видимому, только паттерн 4 сделал это более "убедительно". Помимо короткого тестового периода, эти тестовые запуски имеют существенные ограничения. Главным из них будет проведение тестирования в случаях, когда открытые позиции имеют уровень тейк-профита без стоп-лосса. Использование лимитных ордеров для совершения сделок также делает эти результаты несколько более оптимистичными, чем они были бы в противном случае. Все это — соображения, которые читатель должен учитывать при интерпретации или оценке целесообразности дальнейшей разработки прилагаемого исходного кода.
Заключение
Мы рассмотрели, как сигналы осциллятора тройного экспоненциального скользящего среднего могут быть объединены с осциллятором процентного диапазона Уильямса и обработаны моделью машинного обучения для составления прогнозов. Поскольку рассмотренные нами паттерны уже прошли форвард-тест, наша модель машинного обучения, по сути, выступала в качестве фильтра для сделок, которые, как мы знали, могли быть реализованы в тестовом году. Производительность немного улучшилась, однако в будущих статьях мы рассмотрим тестирование с использованием паттернов машинного обучения, которые не смогли пройти форвард-тест также хорошо.
| Имя | Описание |
|---|---|
| wz-68.mq5 | Созданный Мастером советник, в заголовке которого отображаются использованные в сборке файлы |
| SignalWZ_68.mqh | Пользовательский файл класса сигнала, используемый в сборке Мастера |
| 68_1.mqh | Экспортированная ONNX-модель для паттерна 0 |
| 68_4.mqh | Экспортированная ONNX-модель для паттерна 4 |
| 68_5.mqh | Экспортированная ONNX-модель для паттерна 5 |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18305
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Как подключить LLM к советнику MQL5 через Python-сервер
Торговые инструменты на языке MQL5 (Часть 7): Информационная панель для мониторинга позиций на счете в разрезе символов
От начального до среднего уровня: Наследование
Нейросети в трейдинге: Единая архитектура взаимодействия рыночных признаков и торгового контекста (OneTrans)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования