Python + MetaTrader 5: быстрый исследовательский контур для данных, признаков и прототипов
Введение
Python сегодня стал одним из самых удобных инструментов для работы с данными. Он предлагает широкий набор библиотек, с помощью которых можно быстро выполнять статистический анализ, проверять гипотезы и наглядно представлять результаты без лишних затрат времени и ресурсов. Это важно для решения задач, связанных с финансовыми рынками: здесь ценится не только скорость обработки данных, но и возможность оперативно переходить от анализа к практическим выводам.
В MetaTrader 5 реализована прямая интеграция с Python, и это заметно расширяет возможности прикладной работы с рыночной информацией. Исследователь или разработчик может использовать привычный инструментарий Python для изучения котировок, построения статистических моделей и проверки рабочих предположений, не разрывая связь с торговой платформой. Такой подход делает процесс анализа гибким и позволяет выстраивать единый цикл: от данных — к гипотезе, от гипотезы — к модели, от модели — к практическому применению.

В этой статье мы покажем:
- как организуется интеграция Python с MetaTrader 5;
- как на ее основе проводить анализ финансовых данных и проверять гипотезы;
- как построить и обучить небольшую модель, а затем перенести обученный результат в советник с помощью ONNX.
Это позволит пройти путь от исследовательского эксперимента до прикладной реализации в торговой системе.
1. Установка и подключение
Прежде чем переходить к анализу данных необходимо последовательно подготовить рабочее окружение для совместного использования Python и MetaTrader 5. Задача простая, но требующая аккуратности. Корректная настройка на старте избавляет от десятков мелких проблем в дальнейшем.
Прежде всего установим торговый терминал MetaTrader 5, загрузив дистрибутив с официального сайта.
Далее потребуется актуальная версия Python. На момент написания статьи это 3.14.3. При установке обязательно активируйте опцию добавления Python в системную переменную PATH — это позволит работать с интерпретатором напрямую из командной строки, без лишних ручных настроек.

Ключевой момент — изоляция окружения. Практика показывает: проекты, работающие с данными и моделями, быстро обрастают зависимостями. Чтобы сохранить порядок и воспроизводимость результатов, для каждого проекта создается отдельная виртуальная среда. В Python это решается встроенным инструментом venv.
Рабочий процесс выглядит следующим образом.
- Открываем среду выполнения команд. Проще всего воспользоваться сочетанием клавиш Win+R. В появившемся окне ввести команду cmd и нажать Enter. Это откроет командную строку Windows. При желании можно использовать PowerShell — принцип работы в данном случае идентичен.
- Переходим в каталог проекта, в котором будет развернута рабочая среда. Делается это стандартной командой:
- Создаем виртуальную среду.
- Устанавливаем Python-модуль для взаимодействия с MetaTrader 5.
- Для полноценного анализа финансовых данных имеет смысл сразу развернуть базовый, но уже практически боевой стек библиотек. Он покрывает ключевые задачи обработки данных, построения моделей и технического анализа.
cd /path/to/your/project
python -m venv integration
И активируем ее.
integration\Scripts\activate
С этого момента все устанавливаемые пакеты будут изолированы в рамках текущего проекта.
pip install MetaTrader5
В первую очередь устанавливаем NumPy — фундамент для численных вычислений. Это основа, на которой держится весь дальнейший стек.
Далее подключаем Pandas — основной инструмент для работы с табличными данными и временными рядами, без которого анализ котировок превращается в мучение.
Для визуализации используем связку Matplotlib и Seaborn. Первый дает полный контроль над графиками, второй — ускоряет построение статистически выразительных визуализаций. В тандеме они позволяют быстро увидеть рынок, а не только считать его.
Для задач машинного обучения добавляем Scikit-Learn — проверенный временем инструмент для построения и валидации моделей. Он хорошо подходит для первых прототипов и базовых стратегий.
А для прикладного анализа рынка подключаем TA — библиотеку технических индикаторов. Это удобный способ быстро обогатить данные сигналами без ручной реализации классических формул.
Установка библиотек выполняется одной командой.
pip install numpy pandas matplotlib seaborn scikit-learn ta
Отдельно стоит добавить библиотеку pytz. На первый взгляд вспомогательную, но на практике критически важную для работы с временными зонами.
pip install pytz
Финансовые данные жёстко привязаны ко времени. Биржи работают в разных часовых поясах.
По умолчанию Python при создании объекта datetime опирается на локальное время системы. Это поведение удобно в бытовых задачах, но в финансовом контексте становится источником системных ошибок. Терминал MetaTrader 5 хранит время тиков и баров в формате UTC — без смещения и без привязки к локальной зоне.
Возникает классическое расхождение. Модель работает в одном времени, данные — в другом. На практике это приводит к неприятным эффектам.
Поэтому правило здесь жёсткое. Все операции, связанные со временем, должны выполняться в UTC. Объекты datetime необходимо явно создавать в UTC-зоне, а любые локальные значения — приводить к единому стандарту. Это выравнивает данные и модель в одной временной системе координат.
Хорошей практикой является использование pytz для явного управления временными зонами. Это исключает неявные преобразования и делает поведение системы предсказуемым.
Данные, полученные из MetaTrader 5, уже находятся в UTC. Их не нужно исправлять. Нужно корректно интерпретировать и согласовывать с логикой модели. В финансовых задачах время — это не просто метка, а координатная ось. Любая ошибка в ней ломает всю геометрию анализа.
Такой набор выглядит сдержанно, но на практике закрывает до 80% типовых задач. Классический подход: минимум лишнего, максимум эффективности.
Если требуется завершить работу в текущей среде, используем стандартную команду:
deactivate
На этом этапе инфраструктура полностью готова. Установлены терминал, интерпретатор и необходимые библиотеки. Среда изолирована и воспроизводима. Это именно тот фундамент, на котором удобно и безопасно строить дальнейший анализ, тестировать гипотезы и постепенно переходить к разработке торговых моделей.
2. Загрузка данных
После настройки инфраструктуры переходим к первому практическому шагу — написанию программы. Здесь нет жёстких ограничений: подойдет любой привычный редактор. Однако в контексте интеграции логично использовать встроенный в MetaTrader 5 редактор MetaEditor, который уже поддерживает работу с Python и позволяет держать весь процесс в едином контуре.
Чтобы запускать Python-скрипты напрямую из MetaEditor или терминала, достаточно один раз указать путь к интерпретатору в настройках платформы. Это базовая операция, но здесь есть важная деталь. Если вы используете ранее созданную виртуальную среду, то путь должен указывать именно на её интерпретатор. А не на глобальную установку Python.

Такой подход сохраняет изоляцию проекта и гарантирует, что все зависимости остаются контролируемыми. В противном случае вы рискуете получить трудноуловимые ошибки, когда один и тот же код ведет себя по-разному в зависимости от активного окружения. В финансовых задачах это недопустимая роскошь.
На первом этапе выстраиваем базовую связку: Python → MetaTrader 5 → исторические данные. Задача простая по формулировке и критическая по сути — подключиться к терминалу из скрипта и получить котировки по заданному инструменту и таймфрейму. Именно с этого момента начинается любой осмысленный анализ.
Структуру скрипта логично выстроить по блокам. Вначале подключаем необходимые библиотеки — это формирует рабочий инструментарий.
from datetime import datetime import MetaTrader5 as mt5 import pandas as pd import numpy as np import pytz import seaborn as sns import matplotlib.pyplot as plt import ta
Далее инициализируется соединение с терминалом через модуль MetaTrader5. Это точка входа в систему. Если подключение не установлено, продолжать выполнение бессмысленно. Поэтому проверка статуса выполняется сразу и без компромиссов.
# Display data on the MetaTrader 5 package print("MetaTrader5 package author: ", mt5.__author__) print("MetaTrader5 package version: ", mt5.__version__) # Connection to MetaTrader 5 terminal if not mt5.initialize(): print("initialize() failed, error code =", mt5.last_error()) quit()
Следующий шаг — задание временного интервала. Здесь используется pytz для явного указания UTC-зоны. Это не техническая деталь, а обязательное условие. Терминал сохраняет котировки в UTC, и любое расхождение на стороне Python приводит к смещению данных. Ошибка тихая, но последствия — системные.
# Set time zone to UTC timezone = pytz.timezone("Etc/UTC") # Create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset utc_from = datetime(2020, 1, 1, tzinfo=timezone) utc_to = datetime.now(timezone) # Set to the current date and time
После этого выполняется запрос исторических данных. В примере используются часовые бары по инструменту EURUSD. Здесь стоит обратить внимание на название инструмента. Оно должно строго соответствовать написанию в терминале, включая суффиксы и префиксы.
# Get bars from EURUSD H1 (hourly timeframe) within the specified interval rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)
На выходе получаем массив структур с ценами и временными метками — сырой рынок без фильтров и интерпретаций. Именно такой вид данных и нужен на первом этапе.
Далее соединение с терминалом корректно закрывается. Это дисциплина исполнения: аккуратное завершение работы предотвращает скрытые сбои при последующих запусках и делает поведение системы предсказуемым.
# Shut down connection to the MetaTrader 5 terminal
mt5.shutdown()
Затем следует базовая проверка результата. Если данные получены — выводятся первые записи для быстрой валидации. Если нет — скрипт завершает работу.
# Check if data was retrieved if rates is None or len(rates) == 0: print("No data retrieved. Please check the symbol or date range.") quit() # Print the first 10 raw records for a quick data sanity check print("Display obtained data 'as is'") for rate in rates[:10]: print(rate)
Дополнительно выполняется визуализация: строится график цен закрытия и объёмов. При этом объемы целесообразно вынести на отдельную ось. Максимальное значение по оси объёмов зададим с запасом — в пять раз превышающем наблюдаемый максимум. Такой приём выглядит простым, но работает безотказно. Гистограмма прижимается к нижней части графика и перестаёт конкурировать с ценой за внимание.
В результате линия цен закрытия остаётся чистой и читаемой, а объёмы сохраняют информативность, не перегружая визуальное представление. Это классический баланс между полнотой данных и их восприятием. График должен не только содержать информацию, но и позволять быстро её интерпретировать без лишнего напряжения.
# Create a DataFrame from the retrieved tick data rates_frame = pd.DataFrame(rates) # Convert the timestamp column from seconds since epoch to datetime rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s') # Use datetime as the DataFrame index for time series plotting and analysis rates_frame.set_index('time', inplace=True) # Plot closing price and tick volume fig, ax1 = plt.subplots(figsize=(12, 6)) # Close price on primary y-axis ax1.set_xlabel('Date') ax1.set_ylabel('Close Price', color='tab:blue') ax1.plot(rates_frame.index, rates_frame['close'], color='tab:blue', label='Close Price') ax1.tick_params(axis='y', labelcolor='tab:blue') # Tick volume on secondary y-axis ax2 = ax1.twinx() ax2.set_ylabel('Tick Volume', color='tab:green') max_tick = rates_frame['tick_volume'].max() ax2.set_ylim(0, max_tick * 5) ax2.plot(rates_frame.index, rates_frame['tick_volume'], color='tab:green', label='Tick Volume') ax2.tick_params(axis='y', labelcolor='tab:green') # Show the plot plt.title('Close Price and Tick Volume Over Time') fig.tight_layout() plt.show() fig.savefig('close_price.png')

С практической точки зрения процедура выглядит элементарно. Но по факту это ключевой этап контроля качества данных. Вы чётко понимаете, что поступает в модель: формат, временные метки, значения. Такой первичный аудит — классика инженерного подхода. Он экономит время, нервы и, что особенно важно в торговых системах, деньги.
3. Проверка гипотез и отбор признаков
Теперь, когда в нашем распоряжении исторические котировки, переходим к первичному анализу. Начнём с предельно простой, почти школьной гипотезы: рынок склонен продолжать движение последнего бара. Проверим есть ли инерция в краткосрочной динамике.
Инструментарий Python позволяет проверить такую гипотезу буквально в несколько строк. Логика следующая. Берём ряд цен закрытия и переходим к разностям цен от бара к бару. Получаем динамику цены, что принципиально важно для анализа.
# Correlation analysis between adjacent bar moves close = rates_frame['close'].to_numpy(dtype=float) # last and next price move differences diff = close[1:] - close[:-1]
Далее формируем два временных ряда: последнее и следующее изменения. Технически это реализуется через сдвиг массива на один элемент. В результате получаем пары значений, где каждое наблюдение отвечает на простой вопрос: если рынок двигался вверх (или вниз), что происходит на следующем баре?
diff = np.column_stack((diff[:-1], diff[1:])) data_matrix = pd.DataFrame(diff, columns=['last', 'next'])
После этого рассчитывается коэффициент корреляции Пирсона между этими двумя рядами. Положительная корреляция укажет на выраженную инерцию, отрицательная — преобладает откат.
Для наглядности результат визуализируется с помощью Seaborn — строится тепловая карта корреляции. Это быстрый способ увидеть структуру зависимости без углубления в числовые детали.
correlation_matrix = data_matrix.corr('pearson') plt.subplots(figsize=(3, 2)) sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm') plt.title('Correlation Bar to Bar') plt.savefig('bar_to_bar.png') plt.show()
Ключевой момент здесь не в самой стратегии — она заведомо примитивна. Ценность в другом. Мы демонстрируем базовый цикл проверки гипотезы. Сформулировали предположение, преобразовали данные, провели расчёт, визуализировали результат. Такой подход дисциплинирует анализ и не позволяет строить выводы на глаз.

Но все же оценим полученные результаты. Наблюдаемая корреляция составляет −0.018 — значение близкое к нулю, но с отрицательным знаком.
Это означает, что выраженной зависимости между соседними барами нет. Более того, слабый отрицательный знак указывает на едва заметный эффект возврата к среднему. После движения в одну сторону следующий бар с небольшой вероятностью склонен двигаться в противоположную. Однако величина эффекта настолько мала, что с практической точки зрения он находится на грани статистического шума.
Гипотеза продолжения движения не получает подтверждения. Рынок на таймфрейме H1 ведет себя ближе к случайному процессу, чем к инерционной системе. Это важное наблюдение. Оно сразу отсекает целый класс наивных стратегий и задает более трезвую точку отсчета для дальнейшего анализа.
Одна свеча — слишком мелкий масштаб. В таких данных доминирует шум, а не структура. Поэтому делаем следующий логичный шаг: укрупняем наблюдения и проверяем уже не отдельные изменения, а усреднённые движения.
Вместо одиночного бара берём среднее изменение цены за исторический интервал от 1 до 23 баров. Это сглаживает случайные колебания и позволяет выделить более устойчивую компоненту движения. Аналогично формируем будущее среднее ценовое изменение на горизонте от 1 до 9 баров. Таким образом переходим от точечных наблюдений к агрегированным сигналам.
Реализация аккуратно раскладывается на два блока. В первом рассчитываются скользящии среднии для прошлых изменений.
# Add rolling mean features for the previous and future moves for period in range(2, 24, 1): data_matrix[f'last_mean_{period:02d}'] = data_matrix['last'].rolling(window=period).mean()
Во втором — для будущих, с обязательным сдвигом, чтобы исключить утечку информации из будущего в прошлое. Это критически важный момент: без него анализ теряет смысл.
for period in range(2, 10, 1): data_matrix[f'next_mean_{period}'] = data_matrix['next'].rolling(window=period).mean().shift(-(period-1))
После расчёта удаляются строки с пропущенными значениями — неизбежное следствие оконных операций.
# Remove rows with missing values created by rolling calculations data_matrix.dropna(inplace=True)
Далее строится матрица корреляций Пирсона, из которой выделяется интересующая нас подматрица: зависимость будущего от прошлого. Здесь ответ на главный вопрос — есть ли предсказательная сила у усреднённого движения.
correlation_matrix = data_matrix.corr('pearson') # Match columns that begin with "next" reg = r'^next.*$' selected_cols = correlation_matrix.filter(regex=reg).columns remaining_rows = correlation_matrix.index.difference(selected_cols) correlation_matrix = correlation_matrix.loc[remaining_rows, selected_cols]
Визуализация средствами Seaborn в виде тепловой карты особенно уместна. Позволяет быстро оценить структуру зависимостей по всей сетке параметров.
plt.figure(figsize=(12, 7)) plt.subplots_adjust(left=0.15, right=1, bottom=0.16, top=0.95) sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm') plt.title('Correlation Means Last to Next Bars') plt.savefig('mean_to_bar.png') plt.show()
С методологической точки зрения это уже более зрелый эксперимент. Мы уходим от наивной проверки бар к бару и переходим к анализу агрегированных эффектов. Если рынок и содержит слабые закономерности, то именно на таком уровне они начинают проявляться.

Полученный результат выглядит интереснее, но общий вывод остается сдержанным. По-прежнему наблюдаем отрицательные значения корреляции. Однако теперь они структурированы. В центральной области матрицы (окна 8–14 баров по истории и 5–8 баров по горизонту) эффект усиливается и достигает значений −0.02…−0.03. Это слабый, но устойчивый сигнал возврата к среднему.
Логика читается достаточно ясно. Если рынок в течение некоторого времени двигался в одном направлении, то с повышенной вероятностью в последующих барах увидим частичную коррекцию. При этом эффект не линейный:
- на коротких окнах — тонет в шуме;
- на слишком длинных — размывается и теряет силу;
- максимум проявляется в средних диапазонах.
Отдельно стоит отметить правый нижний угол матрицы. Там корреляция стремится к нулю и даже местами становится положительной. Это классический признак потери предсказательной силы при чрезмерном сглаживании: сигнал исчезает вместе с вариативностью данных.
Визуализация средствами Seaborn здесь хорошо подчёркивает рельеф зависимостей. Картина получается вполне осмысленной.
Итог аккуратный, без иллюзий: рынок не демонстрирует сильной инерции, но слабый эффект возврата к среднему присутствует. Это не даёт готовой стратегии, но задаёт направление.
Так как полученные значения кореляции низкие, то напрямую, в линейной постановке, такой сигнал в торговлю не масштабируется. Но именно здесь начинается более интересная часть работы. Если простая зависимость слаба, можно попытаться усилить через комбинацию признаков — то есть перейти к модели.
Ключевая задача — сформировать информативное пространство признаков. Одного ценового ряда недостаточно. Поэтому следующий шаг — поиск дополнительных фичей, способных уловить скрытую структуру рынка. В качестве базового набора логично использовать классические технические индикаторы. Это проверенный временем инструментарий, который, при всей своей простоте, часто даёт полезные сигналы.
Далее применяем тот же дисциплинированный подход — проверка через корреляцию. Оцениваем связь между будущим движением цены и значениями различных индикаторов. При этом параметры индикаторов варьируются в цикле. Это позволяет сразу охватить широкий диапазон конфигураций и увидеть, где сигнал проявляется сильнее.
С практической точки зрения происходящее напоминает перебор параметров с последующей фильтрацией. Но по сути это формирование и отбор признакового пространства. Корреляция здесь выполняет роль диагностического инструмента. Измеряем, какие преобразования данных взаимосвязаны с будущим движением. Это принципиально другой уровень мышления: сначала — понять структуру, потом — извлекать прибыль.
В коде этот подход реализован достаточно системно. С одной стороны, используются простые производные от цены — скользящие средние, их первые и вторые разности, отклонения от текущего значения. Это попытка зафиксировать локальную динамику и ускорение рынка.
# Recreate the base matrix for indicator engineering data_matrix = pd.DataFrame(diff, columns=['last', 'next']) # Add 11-period previous move averages and derived momentum features data_matrix[f'last_mean_11'] = data_matrix['last'].rolling(window=11).mean() data_matrix[f'Dlast_mean_11'] = data_matrix[f'last_mean_11'].diff() data_matrix[f'DDlast_mean_11'] = data_matrix[f'Dlast_mean_11'].diff() # Feature representing the gap between the rolling mean and the current move data_matrix[f'last_last_11'] = data_matrix[f'last_mean_11'] - data_matrix['last'] data_matrix[f'Dlast_last_11'] = data_matrix[f'last_last_11'].diff() data_matrix[f'DDlast_last_11'] = data_matrix[f'Dlast_last_11'].diff() # Add short-term future sum targets for the next bars for period in range(2, 10, 1): data_matrix[f'next_{period}'] = data_matrix['next'].rolling(window=period).sum().shift(-(period-1))
С другой — добавляются классические индикаторы из библиотеки TA: SMA, RSI, MACD. Причём сразу в нескольких параметризациях. Такой охват позволяет не гадать правильный период, а увидеть поведение на всем диапазоне.
# Build additional technical indicators using the close price series close = pd.DataFrame(close[:-1], columns=['close']) indicator_cols = {} for period in [4, 8, 12, 24, 36, 48]: sma = ta.trend.sma_indicator(close['close'], window=period, fillna=True) dsma = sma.diff() ddsma = dsma.diff() rsi = ta.momentum.rsi(close['close'], window=period, fillna=True) drsi = rsi.diff() ddrsi = drsi.diff() macd = ta.trend.MACD( close['close'], window_slow=2 * period, window_fast=period, window_sign=period * 3 // 4, fillna=True, ) macd_main = macd.macd() dmacd = macd_main.diff() ddmacd = dmacd.diff() macd_diff = macd.macd_diff() dmacd_diff = macd_diff.diff() ddmacd_diff = dmacd_diff.diff() macd_signal = macd.macd_signal() dmacd_signal = macd_signal.diff() ddmacd_signal = dmacd_signal.diff() macd_sig_main = macd_signal - macd_main dmacd_sig_main = macd_sig_main.diff() ddmacd_sig_main = dmacd_sig_main.diff() indicator_cols[f'SMA_{period:02d}'] = sma indicator_cols[f'DSMA_{period:02d}'] = dsma indicator_cols[f'DDSMA_{period:02d}'] = ddsma indicator_cols[f'RSI_{period:02d}'] = rsi indicator_cols[f'DRSI_{period:02d}'] = drsi indicator_cols[f'DDRSI_{period:02d}'] = ddrsi indicator_cols[f'MACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_main indicator_cols[f'DMACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd indicator_cols[f'DDMACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd indicator_cols[f'MACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_diff indicator_cols[f'DMACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_diff indicator_cols[f'DDMACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_diff indicator_cols[f'MACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_signal indicator_cols[f'DMACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_signal indicator_cols[f'DDMACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_signal indicator_cols[f'MACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_sig_main indicator_cols[f'DMACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_sig_main indicator_cols[f'DDMACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_sig_main # Append all indicator columns to the feature matrix in one operation # This avoids repeated DataFrame assignment and keeps the DataFrame compact data_matrix = pd.concat([data_matrix, pd.DataFrame(indicator_cols)], axis=1) # Remove any rows with NaN values created by indicator calculations data_matrix.dropna(inplace=True)
Особенно показательно добавление производных второго порядка (diff и diff от diff). Это уже попытка уловить изменение самого сигнала — переход к анализу ускорения и замедления рыночного движения. В финансовых рядах такие эффекты часто оказываются информативнее, чем сами уровни.
Далее включается фильтрация. Из всей матрицы корреляций остаются только признаки, у которых максимальная связь с целевой переменной превышает заданный порог (в данном случае 0.02). Это важный момент. Сознательно отсекаем слабые и нестабильные зависимости, оставляя лишь те, которые хотя бы минимально держатся в данных.
correlation_matrix = data_matrix.corr('pearson') selected_cols = correlation_matrix.filter(regex=reg).columns remaining_rows = correlation_matrix.index.difference(selected_cols) correlation_matrix = correlation_matrix.loc[remaining_rows, selected_cols] # Delete rows with low correlations correlation_matrix = correlation_matrix[correlation_matrix.abs().max(axis=1) >= 0.02]
Визуализация через Seaborn завершает процесс.
plt.figure(figsize=(12, 7)) plt.subplots_adjust(left=0.2, right=1, bottom=0.05, top=0.95) sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm') plt.title('Correlation Indicators to Next Bars') plt.savefig('trend_to_bar.png') plt.show()
Тепловая карта уже не выглядит хаотичным шумом, а превращается в карту сигналов. Видно, какие группы индикаторов начинают откликаться на будущие движения. При каких параметрах этот отклик усиливается. И где он полностью исчезает.

Именно здесь набор гипотез начинает приобретать структуру. Мы переходим от случайного подбора индикаторов к осознанному формированию признаков. Это еще не модель, но уже её каркас. Дальше задача — собрать эти слабые, разрозненные сигналы в единую систему, способную извлекать устойчивую зависимость там, где по отдельности её почти не видно.
4. Построение и обучение модели
На предыдущем этапе мы получили карту корреляций и отобрали признаки с максимальными значениями прямой и обратной корреляции. Это принципиальный момент: отрицательная корреляция — такой же сигнал, просто с обратным знаком. В терминах модели это не ограничение, а дополнительная информация.
Далее переходим к обучению. Экосистема Scikit-Learn предлагает широкий выбор алгоритмов — от линейных моделей до ансамблей. В статье "Регрессионные модели библиотеки Scikit-learn и их экспорт в ONNX" представлено сравнение 55 моделей регрессии. Но для решения практической задачи разумно сосредоточиться на устойчивых и проверенных алгоритмах. В данном случае воспользуемся RandomForestRegressor.
Классический лес — это компромисс между простотой и выразительностью. Он хорошо работает с нелинейными зависимостями, устойчив к шуму и не требует агрессивной нормализации данных. Именно то, что нужно на первом этапе моделирования.
Далее ключевой этап — подбор гиперпараметров. Воспользуемся прямым перебором по двум параметрам: количеству деревьев (n_estimators) и глубине дерева (max_depth). Это разумный выбор: первый параметр отвечает за силу ансамбля, второй — за степень погружения отдельного дерева в данные и контроль переобучения.
С этой целью создаем новый скрипт. Алгоритм подключения к MetaTrader 5 и загрузки исторических котировок остается прежним, поэтому опустим его описание.
После получения исторических данных начинается формирование признакового пространства. Строится базовая матрица: разности цен, их сглаженные версии и производные признаки.
macd_settings = [(8,16,6),(12,24,9),(36,72,27),(48,96,36)] features = [] # Build the base feature matrix from close price changes close = pd.DataFrame(rates_frame['close'][:-1].to_numpy(dtype=float), columns=['close']) diff = rates_frame['close'].diff().to_numpy(dtype=float) # Pair consecutive differences into 'last' and 'next' columns diff = np.column_stack((diff[:-1], diff[1:])) data_matrix = pd.DataFrame(diff, columns=['last', 'next']) features.append('last') # Add to features list for later use # Add a 11-period rolling mean of the previous bar move data_matrix['last_11'] = data_matrix['last'].rolling(window=11).mean() features.append('last_11') # Add to features list for later use # Add the difference between the rolling mean and current bar move data_matrix['last_last_11'] = data_matrix['last_11'] - data_matrix['last'] features.append('last_last_11') # Add to features list for later use
Далее добавляются технические индикаторы: SMA и набор конфигураций MACD.
# Add a 12-period simple moving average as a technical feature data_matrix['SMA_12'] = ta.trend.sma_indicator(close['close'], window=12, fillna=True) features.append('SMA_12') # Add to features list for later use # Add MACD-based technical indicators for the selected parameter sets for fast, slow, sign in macd_settings: macd = ta.trend.MACD( close['close'], window_slow=slow, window_fast=fast, window_sign=sign, fillna=True, ) macd_main = macd.macd() dmacd = macd_main.diff() macd_signal = macd.macd_signal() dmacd_signal = macd_signal.diff() macd_sig_main = macd_signal - macd_main sufix = f"{fast:02d},{slow:02d},{sign:02d}" data_matrix[f'MACD_MAIN_{sufix}'] = macd_main features.append(f'MACD_MAIN_{sufix}') # Add to features list for later use data_matrix[f'DMACD_MAIN_{sufix}'] = dmacd features.append(f'DMACD_MAIN_{sufix}') # Add to features list for later use data_matrix[f'MACD_SIGNAL_{sufix}'] = macd_signal features.append(f'MACD_SIGNAL_{sufix}') # Add to features list for later use data_matrix[f'DMACD_SIGNAL_{sufix}'] = dmacd_signal features.append(f'DMACD_SIGNAL_{sufix}') # Add to features list for later use data_matrix[f'MACD_Sig_Main_{sufix}'] = macd_sig_main features.append(f'MACD_Sig_Main_{sufix}') # Add to features list for later use data_matrix[f'DMACD_Sig_Main_{sufix}'] = macd_sig_main.diff() features.append(f'DMACD_Sig_Main_{sufix}') # Add to features list for later use
Все признаки последовательно накапливаются в единой структуре данных, при этом их имена фиксируются в отдельном списке features. Это упрощает дальнейшую работу и исключает случайные потери переменных.
На этом же шаге формируется целевая переменная — суммарное движение цены на заданном горизонте (next_9).
# Add a 9-period future return target for the next bars data_matrix['next_9'] = data_matrix['next'].rolling(window=9).sum().shift(-8)
Таким образом, задача формализуется как регрессия: по текущим признакам предсказать будущее изменение цены.
Следующий этап — очистка данных. После применения скользящих окон и дифференцирования неизбежно появляются пропущенные значения. Они удаляются, чтобы обеспечить корректность обучения модели.
data_matrix.dropna(inplace=True)
Далее происходит разделение данных по времени: первые 90% используются для обучения, оставшиеся 10% — для тестирования. Это принципиально важный момент. В отличие от классических задач машинного обучения, здесь нельзя перемешивать данные. Строго сохраняем хронологию, моделируя реальный процесс: сначала прошлое, затем будущее.
# ===== 1) Data preparation ===== # Copy the raw feature matrix (preserves original data for later reference) df = data_matrix.copy() # Keep only features that are actually present in the DataFrame features = [c for c in features if c in data_matrix.columns] df = df[features + ["next_9"]] X = df[features] y = df["next_9"] # ===== 2) Time-based split ===== split_idx = int(len(X) * 0.9) X_train = X.iloc[:split_idx] X_test = X.iloc[split_idx:] y_train = y.iloc[:split_idx] y_test = y.iloc[split_idx:]
После этого запускается цикл перебора гиперпараметров модели RandomForestRegressor. Для каждой комбинации параметров выполняется полный цикл:
- модель обучается на тренировочной выборке;
# ===== 3) Model ===== results = [] for est in range(60, 111, 5): for dep in range(2, 14, 1): print(f"\n=== Estimators: {est}, Max Depth: {dep} ===") model = RandomForestRegressor( n_estimators = est, max_depth = dep, max_leaf_nodes = None, min_samples_split = 6, min_samples_leaf = 3, bootstrap = True, random_state = 42, n_jobs = -1 ) model.fit(X_train, y_train)
# ===== 4) Evaluation ===== pred_train=np.nan_to_num(model.predict(X_train), nan=0.0, posinf=0.0, neginf=0.0) pred_test = np.nan_to_num(model.predict(X_test), nan=0.0, posinf=0.0, neginf=0.0)
pt_corr = np.corrcoef(pred_test, y_test)[0, 1] results.append((est, dep, pt_corr)) print("Train R2:", round(r2_score(y_train, pred_train), 6)) print("Test R2:", round(r2_score(y_test, pred_test), 6)) print("Test MAE:", round(mean_absolute_error(y_test, pred_test), 8)) print("Pred/Target corr:", round(pt_corr, 6))
Ключевая метрика — корреляция между прогнозом и фактическим значением на тестовой выборке. Именно она отражает способность модели улавливать направление движения. Дополнительно рассчитываются R² и MAE, чтобы контролировать общее качество аппроксимации и уровень ошибки.
Все результаты сохраняются в таблицу, каждая строка которой соответствует конкретной комбинации гиперпараметров. Далее эта таблица преобразуется в матричный вид, что позволяет визуализировать зависимость качества модели от параметров.
# --- results -> DataFrame --- df_results = pd.DataFrame( results, columns=["Estimators", "Max Depth", "Test Correlation"] ) # --- pivot table --- heatmap_data = df_results.pivot( index="Estimators", columns="Max Depth", values="Test Correlation" ).sort_index()
Финальный шаг — визуализация средствами Seaborn. Тепловая карта наглядно показывает, в какой области параметров достигается наилучший результат. Это позволяет не только выбрать оптимальную конфигурацию, но и оценить устойчивость модели — насколько сильно меняется качество при небольших изменениях параметров.
# --- heatmap --- plt.figure(figsize=(14, 10)) sns.heatmap( heatmap_data, annot=True, fmt=".4f", cmap="coolwarm", linewidths=0.5, cbar_kws={"label": "Test Correlation"} ) plt.title("Heatmap of Test Correlation by n_estimators and max_leaf_nodes") plt.xlabel("Max Nodes") plt.ylabel("Estimators") plt.tight_layout() plt.show()

Здесь проявляется важный момент: модель не создает сигнал из ничего. Она лишь агрегирует слабые зависимости, найденные ранее. Если на этапе анализа не было даже намека на структуру, никакая модель ситуацию не спасет. Но если сигнал есть, пусть и слабый — ансамбль способен его усилить и сделать пригодным для практического использования.
На графике первой итерации подбора гиперпараметров отчетливо выделяется область с max_depth = 10. Это значение демонстрирует наиболее устойчивый баланс между способностью модели улавливать зависимости и контролем переобучения. Фактически, здесь достигается тот самый рабочий режим, при котором модель уже не примитивна, но еще не начинает подгонять шум.
Далее логика естественно развивается. Зафиксировав max_depth, переходим ко второму этапу — настройке структуры деревьев через max_leaf_nodes. Параллельно сужаем диапазон по количеству деревьев (n_estimators), оставляя только зону, где ранее наблюдались стабильные результаты. Это позволяет повысить разрешение поиска: шаг перебора уменьшается, а внимание концентрируется на действительно значимой области параметров.
Такой подход напоминает классическую процедуру локальной оптимизации. Сначала определяется грубая область максимума, затем — аккуратная доводка внутри неё. В результате мы не распыляем вычислительные ресурсы на заведомо слабые конфигурации и быстрее выходим на устойчивую комбинацию параметров.
Изменения в коде при этом носят точечный характер: уточняется диапазон перебора и изменяется второй параметр. Архитектура скрипта остается прежней.

После отбора фиксируем оптимальные гиперпараметры и переходим к финальному обучению модели. Структурно ничего не меняется: из скрипта убираются циклы перебора, а при инициализации RandomForestRegressor значения параметров задаются напрямую. Вся остальная логика — подготовка данных, разделение выборки, обучение и базовая валидация — остается неизменной.
Далее появляется более тонкий, но принципиальный момент. Модель неравномерно уверена в своих прогнозах. В одних случаях она дает сильный сигнал, в других — значения близки к нулю и фактически находятся в зоне неопределенности. Если воспринимать все прогнозы одинаково, стратегия неизбежно начинает торговать шум.
Отсюда возникает естественная гипотеза: игнорировать слабые сигналы и работать только там, где модель демонстрирует достаточную уверенность или ожидаемое движение покрывает издержки. Это уже переход от модели-функции к модели торговому фильтру.
В коде эта идея реализована аккуратно и без лишней сложности. На основе обучающей выборки рассчитываются пороговые значения абсолютных прогнозов.
# ===== 5) Simple PnL prototype ===== # Calculate strategy metrics for a vector of thresholds without an explicit loop percentiles = np.arange(10, 100, 5) thresholds = np.percentile(np.abs(pred_train), percentiles)
Это важный момент: пороги определяются на train и применяются к test, что сохраняет корректность эксперимента.
Далее формируется матрица прогнозов и соответствующих порогов. Для каждого уровня рассчитывается маска — какие сигналы проходят фильтр. Позиция определяется знаком прогноза с учетом общей корреляции, что позволяет согласовать направление торговли с характером модели.
# Build a matrix where each column repeats the test predictions pred_matrix = np.tile(pred_test[:, None], (1, thresholds.size)) threshold_matrix = thresholds[None, :] # Generate a mask per threshold and compute sign positions mask = np.abs(pred_matrix) >= threshold_matrix position = np.sign(pred_matrix) * np.sign(pt_corr) * mask.astype(float)
После этого доходность формируется как произведение позиции на фактическое движение. Вычитается фиксированные издержки (спред/комиссия) и строится кумулятивная кривая капитала.
# Broadcast y_check to match the threshold matrix shape y_check_matrix = np.tile(y_check.values[:, None], (1, thresholds.size)) # Subtracting swap cost from the target to get a more realistic PnL estimate strategy_ret = position * y_check_matrix - np.abs(position)*(0.00021) # Compute equity curves for each threshold column equity = np.cumsum(strategy_ret, axis=0)
Результаты агрегируются в таблицу:
- final_equity — итоговая доходность;
- mean_return — средний результат сделки;
- win_rate — доля прибыльных входов.
# Aggregate results into a DataFrame results = pd.DataFrame({ 'percentile': percentiles, 'threshold': thresholds, 'final_equity': equity[-1, :], 'mean_return': np.sum(strategy_ret, axis=0)/(np.sum(strategy_ret != 0, axis=0)+1e-9), 'win_rate': np.sum(strategy_ret > 0, axis=0)/(np.sum(strategy_ret != 0, axis=0)+1e-9) }) print(results.to_string(index=False, float_format='%.8f'))
С практической точки зрения это уже не просто оценка модели, а зачаток торговой системы. Мы не только проверяем точность прогноза, но и сразу оцениваем, как он монетизируется при разных уровнях фильтрации.

5. Трансформация в ONNX
Мы обучили модель. Следующий логичный шаг — убрать человека из контура принятия решений. Ручная торговля по сигналам модели почти всегда уступает автоматизированной: нет непрерывности, теряется скорость реакции, добавляется психологический фактор. В реальной работе это превращается в системные искажения — пропущенные входы, преждевременные выходы, недоверие к собственной модели.
Платформа MetaTrader 5 предлагает два пути автоматизации. Первый — запуск Python-скрипта с прямым исполнением сделок. Второй — перенос модели в формат ONNX с последующим использованием в MQL5-эксперте. На практике второй вариант выглядит более зрелым.
Формат ONNX решает сразу несколько задач. Модель фиксируется в компактном и независимом виде. Легко переносится между машинами — достаточно самого терминала. Появляется возможность полноценного тестирования в тестере стратегий. И не теряется производительность: терминал поддерживает аппаратное ускорение, включая работу с GPU (CUDA), что особенно актуально при использовании ансамблевых моделей.
Процесс конвертации достаточно прямолинеен. Сначала описывается вход модели — размерность признакового пространства.
# Number of features used for model input n_features = X_train.shape[1] # Describe the model input shape for ONNX conversion initial_type = [("float_input", FloatTensorType([None, n_features]))]
Далее обученная модель из Scikit-Learn преобразуется в ONNX через соответствующий конвертер и сохраняется на диск.
# Convert the trained sklearn model to ONNX format onnx_model = convert_sklearn(model, initial_types=initial_type) # Save the ONNX model to disk with open(onnx_model_path, "wb") as f: f.write(onnx_model.SerializeToString())
После этого выполняется обязательный этап валидации. Модель загружается через ONNX Runtime, и на тех же данных рассчитываются прогнозные значения.
# Load the ONNX model for inference sess = rt.InferenceSession(onnx_model_path) input_name = sess.get_inputs()[0].name # ONNX runtime expects float32 input arrays X_test_np = X_test.astype(np.float32).values onnx_preds = sess.run(None, { input_name: X_test_np })[0].ravel()
Далее они сравниваются с исходными результатами Sklearn-модели.
# Compare ONNX predictions with sklearn predictions sk_preds = model.predict(X_test) print("Correlation:", np.corrcoef(sk_preds, onnx_preds)[0, 1]) print("Max diff:", np.max(np.abs(sk_preds - onnx_preds)))
Здесь два ключевых критерия:
- корреляция между прогнозами должна стремиться к 1;
- максимальное расхождение — быть пренебрежимо малым.
Если эти условия выполняются, можно считать, что перенос прошел корректно и модель готова к интеграции в торговую систему.
С практической точки зрения это финальный переход от исследовательской среды Python к прикладной эксплуатации. Модель перестает быть экспериментом и становится частью инфраструктуры — автономной, воспроизводимой и пригодной для тестирования и реальной торговли.
6. Тестирование в тестере стратегий
После завершения работы на стороне Python логика переносится в среду исполнения MetaTrader 5. Здесь модель перестаёт быть исследовательским инструментом и становится частью торгового алгоритма. Важно, что структура кода повторяет уже знакомый поток операций: инициализация → подготовка данных → прогноз → торговое решение.
Инициализация выполняется в методе OnInit. На этом этапе загружается ONNX-модель из ресурса и создаётся среда выполнения через OnnxCreateFromBuffer.
int OnInit() { //--- if(!Symb.Name("EURUSD_i")) return INIT_FAILED; Symb.Refresh(); //--- if(!Trade.SetTypeFillingBySymbol(Symb.Name())) return INIT_FAILED; //--- load models onnx = OnnxCreateFromBuffer(model, ONNX_DEFAULT); if(onnx == INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ", GetLastError()); return INIT_FAILED; } const ulong input_state[] = {1, Inputs.Size()}; if(!OnnxSetInputShape(onnx, 0, input_state)) { Print("OnnxSetInputShape error ", GetLastError()); OnnxRelease(onnx); return INIT_FAILED; } const ulong output_forecast[] = {1, Forecast.Size()}; if(!OnnxSetOutputShape(onnx, 0, output_forecast)) { Print("OnnxSetOutputShape error ", GetLastError()); OnnxRelease(onnx); return INIT_FAILED; }
Далее явно задаются формы входа и выхода — это критично, так как модель ожидает строго фиксированное количество признаков. Ошибка на этом этапе приведет к некорректному инференсу.
Параллельно инициализируются индикаторы — SMA и набор MACD с теми же параметрами, что использовались при обучении.
//--- Indicators if(!ciSMA.Create(Symb.Name(), TimeFrame, 12, 0, MODE_SMA, PRICE_CLOSE)) { Print("SMA create error ", GetLastError()); OnnxRelease(onnx); return INIT_FAILED; } ciSMA.BufferResize(2); for(uint i = 0; i < ciMACD.Size(); i++) { if(!ciMACD[i].Create(Symb.Name(), TimeFrame, int(macd_set[i, 0]), int(macd_set[i, 1]), int(macd_set[i, 2]), PRICE_CLOSE)) { PrintFormat("MACD %d create error %d", i, GetLastError()); OnnxRelease(onnx); return INIT_FAILED; } ciMACD[i].BufferResize(4); } //--- return(INIT_SUCCEEDED); }
Это принципиальный момент: признаки в MQL5 должны быть идентичны тем, что подавались в модель на этапе обучения. Любое расхождение разрушает предсказательную способность.
Основная логика сосредоточена в методе OnTick, но с фильтром события открытия нового бара IsNewBar. Это защищает от пересчёта модели на каждом тике и синхронизирует расчёты с таймфреймом.
void OnTick() { //--- if(!IsNewBar()) return;
Далее идёт блок учета текущих позиций — простая агрегация объёмов и прибыли по направлениям. Это необходимо для контроля уже открытых сделок.
double buy_value = 0, sell_value = 0, buy_profit = 0, sell_profit = 0; int total = PositionsTotal(); for(int i = 0; i < total; i++) { if(PositionGetSymbol(i) != Symb.Name()) continue; double profit = PositionGetDouble(POSITION_PROFIT); switch((int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_BUY: buy_value += PositionGetDouble(POSITION_VOLUME); buy_profit += profit; break; case POSITION_TYPE_SELL: sell_value += PositionGetDouble(POSITION_VOLUME); sell_profit += profit; break; } }
Затем формируется вектор исходных данных Inputs. По сути, здесь вручную воспроизводится Feature Engineering из Python:
- базовые признаки;
//--- prepare input data ciSMA.Refresh(); for(uint i = 0; i < ciMACD.Size(); i++) ciMACD[i].Refresh(); if(!Rates.CopyRates(Symb.Name(), TimeFrame, COPY_RATES_CLOSE, 1, 12)) { Print("CopyRates error ", GetLastError()); return; } Inputs[0] = float(Rates[11] - Rates[10]); Inputs[1] = float(Rates[11] - Rates[0]) / 11; Inputs[2] = float(Inputs[1] - Inputs[0]);
Inputs[3] = float(ciSMA.Main(1));
for(uint i = 0; i < ciMACD.Size(); i++) { Inputs[4 + i * 6] = float(ciMACD[i].Main(1)); Inputs[5 + i * 6] = float(Inputs[4 + i * 6] - ciMACD[i].Main(2)); Inputs[6 + i * 6] = float(ciMACD[i].Signal(1)); Inputs[7 + i * 6] = float(Inputs[6 + i * 6] - ciMACD[i].Signal(2)); Inputs[8 + i * 6] = Inputs[6 + i * 6] - Inputs[4 + i * 6]; Inputs[9 + i * 6] = Inputs[7 + i * 6] - Inputs[5 + i * 6]; }
Обратите внимание на индексацию: каждый признак занимает строго определенное место. Это контракт между моделью и исполнением. Нарушение порядка — и модель начинает работать с искажёнными данными.
После подготовки признаков вызывается OnnxRun.
//--- run the inference if(!OnnxRun(onnx, ONNX_LOGLEVEL_INFO, Inputs, Forecast)) { Print("OnnxRun error ", GetLastError()); return; }
На выходе получаем прогноз — ожидаемое движение цены. Далее начинается практическая часть — интерпретация сигнала.
В коде используется простая, но эффективная логика:
- Вводится пороговое значение, отсекающее слабые сигналы. Его берём из результатов обучения.
- Учитывается направление корреляции, позволяющий инвертировать модель при необходимости.
- Если прогноз превышает порог — открывается позиция. Если сигнал исчезает — позиция закрывается.
Symb.Refresh(); Symb.RefreshRates(); double min_lot = Symb.LotsMin(); double step_lot = Symb.LotsStep(); double stops = (MathMax(Symb.StopsLevel(), 1) + Symb.Spread()) * Symb.Point(); //--- buy control if(Forecast[0]*direction >= threshold) { double buy_lot = min_lot; if(buy_value <= 0) Trade.Buy(buy_lot, Symb.Name(), Symb.Ask(), 0, 0); } else { if(buy_value > 0) CloseByDirection(POSITION_TYPE_BUY); } //--- sell control if(Forecast[0]*direction <= -threshold) { double sell_lot = min_lot; if(sell_value <= 0) Trade.Sell(sell_lot, Symb.Name(), Symb.Bid(), 0, 0); } else { if(sell_value > 0) CloseByDirection(POSITION_TYPE_SELL); } }
Таким образом, модель используется как фильтр направленного движения. Это важное различие: мы не торгуем каждое прогнозное значение, а только проходяшие по силе сигнала.
Полученный советник далее проходит ключевую проверку — тестирование в тестере стратегий MetaTrader 5 на исторических данных за первый квартал 2026 года. Это уже не абстрактная оценка модели, а приближенный к реальности сценарий исполнения.
Такой формат тестирования принципиально важен. Если на этапе Python мы оценивали модель через метрики, то здесь проверяется вся система целиком — от генерации признаков до логики открытия и закрытия позиций. По сути, это первый экзамен стратегии в условиях, максимально приближённых к боевым.

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

Такой подход имеет свои преимущества. Он позволяет гибко комбинировать сигналы модели с классическим анализом и снижает требования к надёжности алгоритма — модель становится помощником, а не единственным источником решений.
Заключение
Интеграция Python и MetaTrader 5 формирует цельный, инженерно выверенный контур разработки торговых решений — от идеи до практической реализации. В рамках статьи мы прошли этот путь последовательно: от получения и анализа данных, через проверку гипотез и построение модели, к ее внедрению и тестированию в реальной среде исполнения.
Ключевое преимущество такого подхода — разделение ролей. Python берёт на себя исследовательскую часть: обработку данных, генерацию признаков, статистический анализ и обучение моделей. MetaTrader 5, в свою очередь, обеспечивает исполнение: доступ к рыночным данным, тестирование стратегий и торговую инфраструктуру. Это классическая связка лаборатория — производственный контур, где каждая среда используется по своему прямому назначению.
Дополнительную ценность дает использование формата ONNX. Модель становится переносимой, независимой от среды разработки и готовой к воспроизведению на любом устройстве с установленным терминалом. Это упрощает масштабирование, ускоряет тестирование и снижает риски, связанные с несовместимостью окружений.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Experts\Integration\Integration.mq5 | Советник | Советник тестирования модели в терминале |
| 2 | Indicators\Integration\Integration.mq5 | Индикатор | Индикатор демонстрации сигналов на графике |
| 3 | Scripts\Integration\load_data.py | Скрипт | Скрипт загрузки данных |
| 4 | Scripts\Integration\look_model_param_rf.py | Скрипт | Скрипт перебора гиперпарамертов |
| 5 | Scripts\Integration\create_model_rf.py | Скрипт | Скрипт обучения модели и сохранения в ONNX |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Нейронные сети на практике: Практика ведет к совершенству
Создание самооптимизирующихся советников на MQL5 (Часть 13): Введение в теорию управления с использованием факторизации матриц
Статистический арбитраж на основе коинтегрированных акций (Часть 3): Настройка базы данных
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования