preview
Разработка робота на Python и MQL5 (Часть 1): Препроцессинг данных

Разработка робота на Python и MQL5 (Часть 1): Препроцессинг данных

MetaTrader 5Торговые системы | 29 марта 2024, 15:39
875 62
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Введение

Рынок становится все сложнее. Сегодня он превращается в битву алгоритмов. Свыше 95% торгового оборота набирают роботы. 

Новый шаг — машинное обучение. Это не сильный ИИ, но и не простые линейные алгоритмы. Модель машинного обучения способна получать прибыль в сложных условиях. Интересно применить машинное обучение для создания торговых систем. Благодаря нейросетям, торговый робот будет анализировать большие данные, находить закономерности и прогнозировать движение цены.


Мы рассмотрим цикл разработки торгового робота: сбор данных, обработка, расширение выборки, инженерия признаков, выбор и обучение модели, создание торговой системы через Python, мониторинг торговли.

Работа через Python имеет преимущества: быстродействие в области машинного обучения, возможность отбора и генерации признаков. Экспорт моделей в ONNX требует в точности повторять логику генерации признаков, как в Python, что непросто. Поэтому выбран путь с онлайн-торговлей через Python.

Постановка задачи и выбор инструмента

Цель проекта — создать прибыльную и устойчивую модель машинного обучения для торговли через Python. Этапы работы:

  • Сбор данных из MetaTrader 5, загрузка первичных признаков.
  • Аугментация данных для расширения выборки.  
  • Разметка данных метками.
  • Инженерия признаков: генерация, кластеризация, отбор.
  • Выбор и обучение модели ML, возможно ансамблирование.
  • Оценка моделей по метрикам.
  • Разработка тестера для оценки прибыльности.  
  • Достижение позитивного результата на новых данных.
  • Реализация торгового алгоритма через Python MQL5.
  • Интеграция с MetaTrader 5.
  • Улучшение моделей.

Инструменты: Python MQL5, библиотеки ML на Python для быстродействия и функциональности.

Итак, с целями и задачами мы определились. В рамках данной статьи будут осуществлены следующие работы:

  • Сбор данных из MetaTrader 5, загрузка первичных признаков.
  • Аугментация данных для расширения выборки.  
  • Разметка данных метками.
  • Инженерия признаков: генерация, кластеризация, отбор.

Настройка окружения, импортов, сбор данных

Сначала нужно получить исторические данные можно через MetaTrader 5. Код устанавливает соединение с торговой платформой, передавая путь к файлу терминала в метод инициализации.

В цикле получаем данные методом mt5.copy_rates_range() с параметрами: инструмент, таймфрейм, начальная и конечная даты. Есть счетчик неудачных попыток и задержка для стабильного соединения.

Функция завершает работу, отключаясь от платформы методом mt5.shutdown().

Это отдельная функция для дальнейшего вызова в программе. 

Для запуска скрипта вам понадобится установить следующие библиотеки:

  1. NumPy: Библиотека для работы с многомерными массивами и математическими функциями.

  2. Pandas: Инструмент для анализа данных, предоставляет удобные структуры данных для работы с таблицами и временными рядами.

  3. Random: Модуль для генерации случайных чисел и выбора случайных элементов из последовательностей.

  4. Datetime: Предоставляет классы и функции для работы с датами и временем.

  5. MetaTrader5: Библиотека для взаимодействия с торговым терминалом MetaTrader 5.

  6. Time: Модуль для работы со временем и задержками выполнения программы.

  7. Concurrent.futures: Инструмент для выполнения параллельных и асинхронных задач. Это нам будет необходимо в будущем. для параллельной мультивалютной работы.

  8. Tqdm: Библиотека для отображения индикаторов прогресса при выполнении итеративных операций. Это нам будет необходимо в будущем. для параллельной мультивалютной работы.

  9. Train_test_split: Функция для разделения набора данных на обучающую и тестовую выборки при обучении моделей машинного обучения.

Вы можете установить их с помощью pip, выполнив следующие команды: Вы можете установить их с помощью pip, выполнив следующие команды:

pip install numpy pandas MetaTrader5 concurrent-futures tqdm sklearn matplotlib imblearn
import numpy as np
import pandas as pd
import random
from datetime import datetime
import MetaTrader5 as mt5
import time
import concurrent.futures
from tqdm import tqdm
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.utils import class_weight
from imblearn.under_sampling import RandomUnderSampler

# GLOBALS
MARKUP = 0.00001
BACKWARD = datetime(2000, 1, 1)
FORWARD = datetime(2010, 1, 1)
EXAMWARD = datetime(2024, 1, 1)
MAX_OPEN_TRADES = 3
symbol = "EURUSD"

def retrieve_data(symbol, retries_limit=300):
    terminal_path = "C:/Program Files/MetaTrader 5/Arima/terminal64.exe"

    attempt = 0
    raw_data = None

    while attempt < retries_limit:
        if not mt5.initialize(path=terminal_path):
            print("MetaTrader initialization failed")
            return None

        instrument_count = mt5.symbols_total()
        if instrument_count > 0:
            print(f"Number of instruments in the terminal: {instrument_count}")
        else:
            print("No instruments in the terminal")

        rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_H1, BACKWARD, EXAMWARD)
        mt5.shutdown()

        if rates is None or len(rates) == 0:
            print(f"Data for {symbol} not available (attempt {attempt+1})")
            attempt += 1
            time.sleep(1)
        else:
            raw_data = pd.DataFrame(rates[:-1], columns=['time', 'open', 'high', 'low', 'close', 'tick_volume'])
            raw_data['time'] = pd.to_datetime(raw_data['time'], unit='s')
            raw_data.set_index('time', inplace=True)
            break

    if raw_data is None:
        print(f"Data retrieval failed after {retries_limit} attempts")
        return None

    # Add simple features
    raw_data['raw_SMA_10'] = raw_data['close'].rolling(window=10).mean()
    raw_data['raw_SMA_20'] = raw_data['close'].rolling(window=20).mean()
    raw_data['Price_Change'] = raw_data['close'].pct_change() * 100

    # Additional features
    raw_data['raw_Std_Dev_Close'] = raw_data['close'].rolling(window=20).std()
    raw_data['raw_Volume_Change'] = raw_data['tick_volume'].pct_change() * 100

    raw_data['raw_Prev_Day_Price_Change'] = raw_data['close'] - raw_data['close'].shift(1)
    raw_data['raw_Prev_Week_Price_Change'] = raw_data['close'] - raw_data['close'].shift(7)
    raw_data['raw_Prev_Month_Price_Change'] = raw_data['close'] - raw_data['close'].shift(30)

    raw_data['Consecutive_Positive_Changes'] = (raw_data['Price_Change'] > 0).astype(int).groupby((raw_data['Price_Change'] > 0).astype(int).diff().ne(0).cumsum()).cumsum()
    raw_data['Consecutive_Negative_Changes'] = (raw_data['Price_Change'] < 0).astype(int).groupby((raw_data['Price_Change'] < 0).astype(int).diff().ne(0).cumsum()).cumsum()
    raw_data['Price_Density'] = raw_data['close'].rolling(window=10).apply(lambda x: len(set(x)))
    raw_data['Fractal_Analysis'] = raw_data['close'].rolling(window=10).apply(lambda x: 1 if x.idxmax() else (-1 if x.idxmin() else 0))
    raw_data['Price_Volume_Ratio'] = raw_data['close'] / raw_data['tick_volume']
    raw_data['Median_Close_7'] = raw_data['close'].rolling(window=7).median()
    raw_data['Median_Close_30'] = raw_data['close'].rolling(window=30).median()
    raw_data['Price_Volatility'] = raw_data['close'].rolling(window=20).std() / raw_data['close'].rolling(window=20).mean()

    print("\nOriginal columns:")
    print(raw_data[['close', 'high', 'low', 'open', 'tick_volume']].tail(100))

    print("\nList of features:")
    print(raw_data.columns.tolist())

    print("\nLast 100 features:")
    print(raw_data.tail(100))

    # Replace NaN values with the mean
    raw_data.fillna(raw_data.mean(), inplace=True)

    return raw_data

retrieve_data(symbol)

Определяются глобальные переменные: издержки на спред и комиссии брокеров, даты для обучающей и тестовой выборок, максимальное число сделок, символ.

Выполняется загрузка котировок из MetaTrader5 в цикле. Данные конвертируются в DataFrame и обогащаются признаками:

  • Скользящие средние SMA за 10 и 20 периодов
  • Изменение цены 
  • Стандартное отклонение цены
  • Изменение объема
  • Дневное/месячное изменение цены
  • Медианы цены

Эти признаки помогают учитывать факторы, влияющие на цену.

Выводится информация о столбцах и последних записях DataFrame.

Смотрим, как выполнилась наша первая функция:

Первая функция

Все работает. Код успешно загрузил котировки, выполнил подготовку и загрузку признаков. Переходим к следующей части кода.


Аугментация данных для расширения выборки

Аугментация данных — генерация новых обучающих примеров на основе существующих для увеличения выборки и повышения качества модели. Актуальна для прогнозирования временных рядов с ограниченными данными. Снижает ошибку моделей, повышает робастность.

Методы аугментации финансовых данных:

  • Добавление шума (случайных отклонений) для устойчивости к зашумленности
  • Сдвиг по времени для моделирования разных сценариев развития
  • Масштабирование для моделирования скачков/падений цены
  • Инверсия исходных данных

Реализована функция аугментации входных данных, принимающая DataFrame и количество новых примеров для каждого метода. Генерирует новые данные разными подходами и конкатенирует с оригинальным DataFrame.

def augment_data(raw_data, noise_level=0.01, time_shift=1, scale_range=(0.9, 1.1)):
    print(f"Number of rows before augmentation: {len(raw_data)}")

    # Copy raw_data into augmented_data
    augmented_data = raw_data.copy()

    # Add noise
    noisy_data = raw_data.copy()
    noisy_data += np.random.normal(0, noise_level, noisy_data.shape)

    # Replace NaN values with the mean
    noisy_data.fillna(noisy_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, noisy_data])
    print(f"Added {len(noisy_data)} rows after adding noise")

    # Time shift
    shifted_data = raw_data.copy()
    shifted_data.index += pd.DateOffset(hours=time_shift)

    # Replace NaN values with the mean
    shifted_data.fillna(shifted_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, shifted_data])
    print(f"Added {len(shifted_data)} rows after time shift")

    # Scaling
    scale = np.random.uniform(scale_range[0], scale_range[1])
    scaled_data = raw_data.copy()
    scaled_data *= scale

    # Replace NaN values with the mean
    scaled_data.fillna(scaled_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, scaled_data])
    print(f"Added {len(scaled_data)} rows after scaling")

    # Inversion
    inverted_data = raw_data.copy()
    inverted_data *= -1

    # Replace NaN values with the mean
    inverted_data.fillna(inverted_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, inverted_data])
    print(f"Added {len(inverted_data)} rows after inversion")

    print(f"Number of rows after augmentation: {len(augmented_data)}")

    # Print dates by years
    print("Print dates by years:")
    for year, group in augmented_data.groupby(augmented_data.index.year):
        print(f"Year {year}: {group.index}")

    return augmented_data

Вызываем код, получаем следующие результаты:

2

После применения методов аугментации данных наш исходный набор из 150 000 часовых ценовых баров был расширен до впечатляющих 747 000 строк. Многие авторитетные исследования в области машинного обучения показывают, что такое существенное увеличение объема тренировочных данных за счет генерации синтетических примеров положительно сказывается на метриках качества обученных моделей. Рассчитываем, что и в нашем случае этот прием даст желаемый эффект.


Разметка данных

Разметка данных критически важна для успеха алгоритмов обучения с учителем. Она позволяет снабдить исходные данные метками, которые модель затем изучает. Размеченные данные повышают точность, улучшают обобщение, ускоряют обучение и облегчают оценку моделей. В задаче прогнозирования EURUSD мы добавили бинарный столбец "labels", указывающий, превысило ли следующее изменение цены спред и комиссию. Это позволяет модели выучить паттерны переигрывания спреда и безоткатных трендов.

    Разметка играет ключевую роль, позволяя алгоритмам машинного обучения находить сложные закономерности в данных, недоступные для восприятия в чистом необработанном виде. А теперь перейдем к обзору кода.

    def markup_data(data, target_column, label_column, markup_ratio=0.00002):
        data.loc[:, label_column] = np.where(data.loc[:, target_column].shift(-1) > data.loc[:, target_column] + markup_ratio, 1, 0)
    
        data.loc[data[label_column].isna(), label_column] = 0
    
        print(f"Number of markups on price change greater than markup: {data[label_column].sum()}")
    
        return data

    Выполняем данный код, получаем количество меток в данных. Функция возвращает фрейм. Все работает. Кстати, из свыше 700 000 единиц данных, цена менялась больше чем на величину спреда только в 70 000 случаях.

    3

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


    Функция целевых меток

    def label_data(data, symbol, min=2, max=48):
        terminal_path = "C:/Program Files/MetaTrader 5/Arima/terminal64.exe"
    
        if not mt5.initialize(path=terminal_path):
            print("Error connecting to MetaTrader 5 terminal")
            return
    
        symbol_info = mt5.symbol_info(symbol)
        stop_level = 100 * symbol_info.point
        take_level = 800 * symbol_info.point
    
        labels = []
    
        for i in range(data.shape[0] - max):
            rand = random.randint(min, max)
            curr_pr = data['close'].iloc[i]
            future_pr = data['close'].iloc[i + rand]
            min_pr = data['low'].iloc[i:i + rand].min()
            max_pr = data['high'].iloc[i:i + rand].max()
    
            price_change = abs(future_pr - curr_pr)
    
            if price_change > take_level and future_pr > curr_pr and min_pr > curr_pr - stop_level:
                labels.append(1)  # Growth
            elif price_change > take_level and future_pr < curr_pr and max_pr < curr_pr + stop_level:
                labels.append(0)  # Fall
            else:
                labels.append(None)
    
        data = data.iloc[:len(labels)].copy()
        data['labels'] = labels
    
        data.dropna(inplace=True)
    
        X = data.drop('labels', axis=1)
        y = data['labels']
    
        rus = RandomUnderSampler(random_state=2)
        X_balanced, y_balanced = rus.fit_resample(X, y)
    
        data_balanced = pd.concat([X_balanced, y_balanced], axis=1)
    
        return data

    Функция получает целевые метки для обучения моделей машинного обучения торговой прибылью. Она соединяется с MetaTrader 5, извлекает информацию о символе и рассчитывает уровни стопа/тейка. Затем для каждой точки входа определяется будущая цена через случайный период. Если изменение цены превышает тейк и не задевает стоп, и удовлетворяет условиям роста/падения, добавляется метка 1.0/0.0 соответственно. Иначе - None. Создается новый датафрейм только с размеченными данными. None заменяются на средние.

    Выводится количество меток роста/падения.

    Итак, все работает. У нас получены данные и их производные, данные аугументированы, существенно дополнены, размечены двумя разными функциями. Переходим к дальнейшему шагу - балансировке классов.

    Кстати, искушенный в машинном обучении читатель уже давно понял, что мы в итоге разработаем модель классификации, а не регрессии. Мне больше нравятся регрессионные модели, я в них вижу немного больше логики для прогнозирования, нежели в моделях классификации. 

    Итак, следующий наш ход - балансировка классов.


    Балансировка классов. Что такое классификация?

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

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

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

    pip install imblearn

        data = data.iloc[:len(labels)].copy()
        data['labels'] = labels
    
        data.dropna(inplace=True)
    
        X = data.drop('labels', axis=1)
        y = data['labels']
    
        rus = RandomUnderSampler(random_state=2)
        X_balanced, y_balanced = rus.fit_resample(X, y)
    
        data_balanced = pd.concat([X_balanced, y_balanced], axis=1)

    Итак, классы у нас теперь сбалансированы, у нас равное количество меток каждого класса (падение и рост цен).

    Переходим к самому важному в прогнозировании данных — признакам.


    Что такое признаки в машинном обучении?

    Признаки и характеристики — базовые понятия, знакомые каждому с детства. Когда мы описываем объект, мы перечисляем его свойства — это и есть признаки. Набор таких характеристик позволяет полно охарактеризовать предмет.

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

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

    Таким образом, человеческий опыт познания мира через описание объектов их признаками переносится в науку на уровне формализации и автоматизации.


    Автоматическое извлечение признаков

    Фича инжиниринг — преобразование исходных данных в набор признаков для обучения моделей машинного обучения. Цель — найти наиболее информативные признаки. Есть ручной подход (человек выбирает признаки) и автоматический (с помощью алгоритмов).

    Мы будем использовать автоматический подход. Применим метод создания новых признаков для автоматического извлечения лучших признаков из наших данных. Затем отберем самые информативные из полученного набора.

    Метод генерации новых признаков: 

    def generate_new_features(data, num_features=200, random_seed=1):
        random.seed(random_seed)
        new_features = {}
    
        for _ in range(num_features):
            feature_name = f'feature_{len(new_features)}'
    
            col1_idx, col2_idx = random.sample(range(len(data.columns)), 2)
            col1, col2 = data.columns[col1_idx], data.columns[col2_idx]
    
            operation = random.choice(['add', 'subtract', 'multiply', 'divide', 'shift', 'rolling_mean', 'rolling_std', 'rolling_max', 'rolling_min', 'rolling_sum'])
    
            if operation == 'add':
                new_features[feature_name] = data[col1] + data[col2]
            elif operation == 'subtract':
                new_features[feature_name] = data[col1] - data[col2]
            elif operation == 'multiply':
                new_features[feature_name] = data[col1] * data[col2]
            elif operation == 'divide':
                new_features[feature_name] = data[col1] / data[col2]
            elif operation == 'shift':
                shift = random.randint(1, 10)
                new_features[feature_name] = data[col1].shift(shift)
            elif operation == 'rolling_mean':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).mean()
            elif operation == 'rolling_std':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).std()
            elif operation == 'rolling_max':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).max()
            elif operation == 'rolling_min':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).min()
            elif operation == 'rolling_sum':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).sum()
    
        new_data = pd.concat([data, pd.DataFrame(new_features)], axis=1)
    
        print("\nGenerated features:")
        print(new_data[list(new_features.keys())].tail(100))
    
        return data

    Обратите внимание на данный параметр:

    random_seed=42

    Параметр random_seed необходим для воспроизводимости результатов генерации новых признаков. Функция generate_new_features создает новые признаки из исходных данных. На вход: данные, количество признаков, сид.

    Инициализируется рандом с заданным сидом. В цикле: генерируется имя, случайно выбираются 2 существующих признака и операция (сложение, вычитание и т.д.). Вычисляется новый признак по выбранной операции.

    После генерации новые признаки добавляются к исходным данным. Возвращаются обновленные данные с автоматически сгенерированными признаками.

    Код позволяет автоматически создавать новые признаки для улучшения качества машинного обучения.

    Запустили код, смотрим результат:

    5

    Сгенерировано 100 новых признаков. Переходим к следующему этапу — кластеризации признаков.


    Кластеризация признаков

    Кластеризация признаков объединяет похожие признаки в группы, чтобы уменьшить их количество. Это помогает избавиться от избыточных данных, снизить корреляцию и упростить модель, не допуская переобучения.

    Популярные алгоритмы: к-средних (задается число кластеров, признаки группируются вокруг центроидов) и иерархическая кластеризация (многоуровневая древовидная структура).

    Кластеризация признаков позволяет разобраться с ворохом бесполезных признаков и повысить эффективность модели.

    Рассмотрим код кластеризации признаков:

    from sklearn.mixture import GaussianMixture
    
    def cluster_features_by_gmm(data, n_components=4):
        X = data.drop(['label', 'labels'], axis=1)
    
        X = X.replace([np.inf, -np.inf], np.nan)
        X = X.fillna(X.median())
    
        gmm = GaussianMixture(n_components=n_components, random_state=1)
    
        gmm.fit(X)
    
        data['cluster'] = gmm.predict(X)
    
        print("\nFeature clusters:")
        print(data[['cluster']].tail(100))
    
        return data

    Мы используем алгоритм GMM (Gaussian Mixture Model) для кластеризации признаков. Основная идея в том, что данные как бы порождаются смесью нормальных распределений, где каждое распределение — это один кластер.

    Сначала задаем число кластеров. Затем задаем начальные параметры модели: средние, ковариационные матрицы и вероятности кластеров. Алгоритм циклически пересчитывает эти параметры методом максимального правдоподобия, пока они не перестанут меняться.

    В итоге получаем финальные параметры для каждого кластера, по которым можно определять, к какому кластеру относится новый признак.

    GMM крутая штука, ее применяют в разных задачах. Она хороша для данных, у которых кластеры имеют сложную форму и размытые границы.

    Этот код использует GMM для разбиения признаков на кластеры. Берутся исходные данные, убираются метки классов. Применяется GMM для разбиения на заданное число кластеров. Номера кластеров добавляются как новый столбец. В конце печатается таблица полученных кластеров.

    Запускаем код кластеризации, видим результаты:

    6

    Перейдем к функции отбора лучших признаков.


    Отбор лучших признаков

    from sklearn.feature_selection import RFECV
    from sklearn.ensemble import RandomForestClassifier
    import pandas as pd
    
    def feature_engineering(data, n_features_to_select=10):
        # Remove the 'label' column as it is not a feature
        X = data.drop(['label', 'labels'], axis=1)
        y = data['labels']
    
        # Create a RandomForestClassifier model
        clf = RandomForestClassifier(n_estimators=100, random_state=1)
    
        # Use RFECV to select n_features_to_select best features
        rfecv = RFECV(estimator=clf, step=1, cv=5, scoring='accuracy', n_jobs=-1, verbose=1,
                      min_features_to_select=n_features_to_select)
        rfecv.fit(X, y)
    
        # Return a DataFrame with the best features, 'label' column, and 'labels' column
        selected_features = X.columns[rfecv.get_support(indices=True)]
        selected_data = data[selected_features.tolist() + ['label', 'labels']]  # Convert selected_features to a list
    
        # Print the table of best features
        print("\nBest features:")
        print(pd.DataFrame({'Feature': selected_features}))
    
        return selected_data
    
    labeled_data_engineered = feature_engineering(labeled_data_clustered, n_features_to_select=10)

    Эта функция выделяет из наших данных самые крутые и полезные признаки (фичи). На вход идут исходные данные с признаками и метками классов, плюс нужное количество отбираемых фич.

    Сначала метки классов сбрасываются, потому что они не помогут в выборе фич. Затем запускается алгоритм Random Forest — модель, строящая кучу деревьев решений на случайных наборах фич.

    После обучения всех деревьев Random Forest оценивает, насколько каждая фича важна и влияет на классификацию. Основываясь на этих оценках важности, функция отбирает самые топовые фичи.

    В финале отобранные фичи добавляются к данным, а метки классов возвращаются. Функция печатает таблицу с выбранными фичами и возвращает обновленные данные.

    Зачем все это? Ну так мы избавляемся от мусорных фич, которые только тормозят процесс. Оставляем самые крутые рулящие фичи, и модель будет показывать лучшие результаты, обучаясь на таких данных.

    Запустили функцию, смотрим что получилось:

    8

    Лучшим признаком для прогноза цен оказалась сама цена открытия. В топ вошли признаки на основе скользящих средних, приращений цен, стандартного отклонения, дневного и месячного изменения цены. Автоматически сгенерированные признаки оказались неинформативными.

    Код позволяет автоматически отобрать важные признаки, что может улучшить производительность и обобщающую способность модели.


    Заключение

    Итак, мы создали код работы с данными, который предвосхищает разработку нашей будущей модели машинного обучения. Кратко повторим пройденные шаги:

    • Исходные данные по валютной паре евро/доллар извлекались из платформы MetaTrader 5. Затем выполнялись преобразования с применением случайных операций - наложения шума, сдвигов, масштабирования для существенного увеличения размера выборки.
    • Следующим шагом стала разметка ценовых значений специальными метками тренда — роста и падения, с учетом уровней стоп-лосса и тейк-профита. Для балансировки классов применялось удаление избыточных примеров.
    • Далее осуществлялась сложная процедура инженерии признаков. Сначала автоматически генерировались сотни новых производных характеристик из исходных временных рядов. Затем выполнялась кластеризация методом гауссовых смесей для выявления избыточности. И наконец, алгоритм случайного леса использовался для ранжирования и отбора наиболее информативного подмножества переменных.
    • В результате был сформирован высококачественный обогащенный набор данных с оптимальными признаками. Он послужит базой для дальнейшего обучения моделей машинного обучения и разработки торговой стратегии на Python с интеграцией в MetaTrader 5.


    В следующей статье мы займемся выбором оптимальной модели классификации, ее улучшением, внедрением кросс-валидации, и написанием функции тестера для связки Python/MQL5.

    Прикрепленные файлы |
    synergy_ml_bot.py (11.74 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (62)
    Aleksey Vyazmikin
    Aleksey Vyazmikin | 1 апр. 2024 в 13:24
    Aleksandr Slavskii #:

    Попробуйте прямо из терминала его запустить. Просто кидаете скрипт на график и он распечатывает результаты во вкладке "эксперты"

    Возможно нужно в метаэдиторе прописать путь к папку с питоном.

    У меня так работает. Правда после долгих танцев)))

    Вы в портативном режиме используете терминал?

    В ME прописан путь (появился автоматом).

    Aleksandr Slavskii
    Aleksandr Slavskii | 1 апр. 2024 в 14:59
    Aleksey Vyazmikin #:

    Вы в портативном режиме используете терминал?

    В ME прописан путь (появился автоматом).

    Проверил в портабельной версии, всё работает.

    Если запущенно два терминала, а в скрипте путь к терминалу не указан, то в одном из терминалов при попытке скомпилировать вылетает ошибка.

    Aleksey Vyazmikin
    Aleksey Vyazmikin | 1 апр. 2024 в 16:26
    Aleksandr Slavskii #:

    Проверил в портабельной версии, всё работает.

    Если запущенно два терминала, а в скрипте путь к терминалу не указан, то в одном из терминалов при попытке скомпилировать вылетает ошибка.

    Запустил из терминала

    2024.04.01 17:22:57.397 synergy_ml_bot (EURUSD,Monthly) Number of instruments in the terminal: 140
    2024.04.01 17:22:57.397 synergy_ml_bot (EURUSD,Monthly) Data for EURUSD not available (attempt 1)
    2024.04.01 17:22:58.416 synergy_ml_bot (EURUSD,Monthly) Number of instruments in the terminal: 140
    2024.04.01 17:22:58.416 synergy_ml_bot (EURUSD,Monthly) Data for EURUSD not available (attempt 2)
    2024.04.01 17:22:59.416 synergy_ml_bot (EURUSD,Monthly) Number of instruments in the terminal: 140
    2024.04.01 17:22:59.416 synergy_ml_bot (EURUSD,Monthly) Data for EURUSD not available (attempt 3)
    2024.04.01 17:23:00.418 synergy_ml_bot (EURUSD,Monthly) Number of instruments in the terminal: 140
    2024.04.01 17:23:00.418 synergy_ml_bot (EURUSD,Monthly) Data for EURUSD not available (attempt 4)
    2024.04.01 17:23:01.421 synergy_ml_bot (EURUSD,Monthly) Number of instruments in the terminal: 140
    2024.04.01 17:23:01.421 synergy_ml_bot (EURUSD,Monthly) Data for EURUSD not available (attempt 5)
    

    Путь указывал и так и сяк до терминала

    terminal_path = "C:/FX/MT5_02/terminal64.exe"
    #terminal_path = "C:\\FX\\MT5_02\\terminal64.exe"

    Где то он не там ищет - история в терминале есть.

    Aleksandr Slavskii
    Aleksandr Slavskii | 1 апр. 2024 в 16:52
    Aleksey Vyazmikin #:

    Запустил из терминала

    Путь указывал и так и сяк до терминала

    Где то он не там ищет - история в терминале есть.

    Убить все терминалы, оставить один и можно не указывать путь.сам найдёт.

    if not mt5.initialize():

    Но у вас пишет, что не хватает истории баров.

    Поставьте в настройках терминала unlimited

    222


    А EURUSD точно есть, а то я пытался её загрузить из терминала, где её нет в обзоре рынка)))

    Aleksey Vyazmikin
    Aleksey Vyazmikin | 1 апр. 2024 в 16:56
    Aleksandr Slavskii #:

    Убить все терминалы, оставить один и можно не указывать путь.сам найдёт.

    Но у вас пишет, что не хватает истории баров.

    Поставьте в настройках терминала unlimited


    Терминал один запущен.

    Поставил в терминале  unlimited - похоже, что помогло, но ранее ставил - когда из питона запускал - эффекта не было.

    Ещё связь с сервером MQ ужасная - может и это влияло как то?

    В общем, не правильно, что зависимость есть от настройки для отображения истории на чарте.

    Спасибо.

    Популяционные алгоритмы оптимизации: Алгоритм птичьего роя (Bird Swarm Algorithm, BSA) Популяционные алгоритмы оптимизации: Алгоритм птичьего роя (Bird Swarm Algorithm, BSA)
    В статье исследуется алгоритм BSA, основанный на поведении птиц, который вдохновлен коллективным стайным взаимодействием птиц в природе. Различные стратегии поиска индивидов в BSA, включая переключение между поведением в полете, бдительностью и поиском пищи, делают этот алгоритм многоаспектным. Он использует принципы стайного поведения, коммуникации, адаптивности, лидерства и следования птиц для эффективного поиска оптимальных решений.
    Нейросети — это просто (Часть 83): Алгоритм пространственно-временного преобразователя постоянного внимания (Conformer) Нейросети — это просто (Часть 83): Алгоритм пространственно-временного преобразователя постоянного внимания (Conformer)
    Предлагаемый Вашему вниманию алгоритм Conformer был разработан для целей прогнозирования погоды, которую по изменчивости и капризности можно сравнить с финансовыми рынками. Conformer является комплексным методом. И сочетает в себе преимущества моделей внимания и обычных дифференциальных уравнений.
    Интерпретация моделей: Более глубокое понимание моделей машинного обучения Интерпретация моделей: Более глубокое понимание моделей машинного обучения
    Машинное обучение — сложная и полезная область для любого человека независимо от опыта. В этой статье мы погрузимся во внутренние механизмы, лежащие в основе создаваемых моделей, исследуем сложный мир функций, прогнозов и эффективных решений и получим четкое понимание интерпретации моделей. Научитесь искусству поиска компромиссов, улучшения прогнозов, ранжирования важности параметров и принятия надежных решений. Статья поможет вам повысить производительность моделей машинного обучения и извлечь больше пользы от применения методологий машинного обучения.
    Кластеризация временных рядов в причинно-следственном выводе Кластеризация временных рядов в причинно-следственном выводе
    Алгоритмы кластеризации в машинном обучении — это важные алгоритмы обучения без учителя, которые позволяют разделять исходные данные на группы с похожими наблюдениями. Используя эти группы, можно проводить анализ рынка для конкретного кластера, искать наиболее устойчивые кластеры на новых данных, а также делать причинно-следственный вывод. В статье предложен авторский метод кластеризации временных рядов на языке Python.