От матриц к модели: Как запустить ML-пайплайн в MQL5 и довести его до ONNX
Введение
Машинное обучение (ML, Machine Learning) в терминале пугает не столько модель, сколько всем, что её окружает. Кажется, что без отдельного стека, Python-скриптов и сложной интеграции здесь не обойтись. В итоге возникает ощущение: ML — это что-то внешнее по отношению к MetaTrader 5.
Но стоит убрать лишние слои, и картина упрощается. Любой ML-пайплайн — это последовательность базовых операций: сформировать признаки, привести их к масштабу, убрать избыточность и передать результат в модель. Это не магия нейросетей, а обычная линейная алгебра.
И здесь появляется ключевой момент. Подготовка данных — это задача MQL5. Именно в терминале формируются признаки, выполняется нормализация и, при необходимости, применяется PCA (Principal Component Analysis, метод главных компонент) — способ уменьшить размерность данных и убрать коррелированный шум. То есть весь путь данных до модели должен воспроизводиться там, где модель используется. Без этого совпадение результатов невозможно.
Это критично потому, что любая попытка частично перенести пайплайн ломает согласованность. В Python данные проходят один путь, в терминале — другой. Разница может быть минимальной, но для модели это уже другое пространство. В результате сигнал не исчезает — он просто искажается.
На этом фоне роль Python становится гораздо проще и точнее. Он используется для обучения модели и расчёта параметров — нормализации, PCA и самих весов. После этого модель экспортируется в ONNX (Open Neural Network Exchange) — формат, который сохраняет вычислительный граф и позволяет перенести модель без переписывания кода.
Дальше всё решает MQL5. ONNX-модель выполняет вычисления, но не контролирует вход. А значит, именно в терминале необходимо воспроизвести тот же путь данных, который был при обучении.
И вот здесь матрицы выходят на первый план. Встроенные matrix и vector позволяют описывать все преобразования напрямую — так же, как они заданы в математике. Нормализация, проекция, подготовка входа — всё это выполняется как последовательность линейных операций без промежуточных искажений. Код не интерпретирует формулы, а повторяет их.
Именно в этот момент исчезает ощущение сложности. ML перестаёт выглядеть как набор разрозненных технологий и превращается в последовательность понятных шагов, полностью контролируемых внутри терминала.
В этой статье мы пройдём этот путь последовательно. Без лишней теории. С опорой на матрицы и векторы как основной инструмент. И с фокусом на главном: как получить результат, который воспроизводится и в обучении, и в реальной торговой среде.

Матрицы как основа пайплайна: от признаков к пространству данных
Рыночные данные — это непрерывный поток цен. Но сама модель напрямую с ним не работает. Ей нужен уже подготовленный набор признаков: доходности, диапазоны, отклонения, лаги. И здесь сразу встаёт практический вопрос: держать каждый признак отдельно или собрать их в единую структуру. На практике второй путь оказывается намного устойчивее.
Поэтому признаки удобно сводить в матрицу X, строки которой соответствуют наблюдениям, а столбцы — признакам. С этого момента данные перестают быть россыпью отдельных величин. Данные становятся цельным объектом для последовательной обработки. Встроенные матричные операции MQL5 позволяют выполнять преобразования сразу над всем пространством данных, а не над отдельными значениями. Это меняет не только код, но и логику обработки.
Особенно важно это для нормализации. Рыночные признаки почти всегда живут в разных масштабах: цена, диапазон, доходность, отклонение, объём. Если не привести их к сопоставимому виду, модель начнёт реагировать на величину чисел, а не на структуру сигнала. Поэтому нормализация нужна как обязательный шаг подготовки данных. И делать её удобнее именно на стороне MQL5. Здесь встроенные matrix и vector позволяют сразу получать рыночные ряды, а затем применять статистические операции вроде Mean и Std прямо к подготовленной матрице.
matrix<double> vRates; if(!vRates.CopyRates(cSymbol.Name(), PERIOD_CURRENT, COPY_RATES_OHLC | COPY_RATES_VERTICAL, 1, HistoryBars)) { PrintFormat("Error of load rates %d", GetLastError()); return; } matrix<double> means=matrix<double>::Zeros(vRates.Rows(),vRates.Cols()); matrix<double> STDs=matrix<double>::Zeros(vRates.Rows(),vRates.Cols()); means.Row(vRates.Mean(0),0); STDs.Row(vRates.Std(0)+DBL_EPSILON,0); means=means.CumSum(0); STDs=STDs.CumSum(0); vRates=(vRates-means)/STDs;
На первый взгляд может показаться, что нормализацию проще спрятать внутрь ONNX-модели. Но у этого решения есть пределы. В MQL5ONNX задуман прежде всего как слой исполнения. Модель загружается через OnnxCreate. Затем для неё задаются формы входа и выхода. После чего она запускается через OnnxRun. Это отлично подходит для инференса, но не даёт такого же удобства и прозрачности для управления входным пространством. Когда нормализация вынесена в MQL5, её проще проверить, повторить, изменить и сопоставить с реальными рыночными данными прямо в тестере или на графике.
Есть и ещё один важный момент. При смене символа или таймфрейма распределение данных меняется. Если нормализация зашита внутрь ONNX-графа, модель оказывается жёстко привязана к старому распределению входа. Тогда любое существенное изменение среды требует уже не точечной правки, а полноценного переобучения и повторного экспорта. Если нормализация вынесена в MQL5, достаточно обновить её параметры и подать в модель данные нужного масштаба Это сохраняет структуру входа и позволяет обойтись без полной пересборки всей модели.
Именно поэтому внешний матричный слой в терминале даёт больше гибкости, чем попытка замкнуть всё внутри ONNX. В Python модель обучается один раз. В MQL5 данные каждый раз приводятся к нужному виду одинаково. В этом и состоит сильная сторона архитектуры: модель остаётся моделью, а матрицы и векторы в терминале берут на себя работу по подготовке и контролю входа.
Сжатие и стабилизация: PCA как продолжение матричной логики
Рыночные признаки почти никогда не бывают независимыми. Цена, доходность, диапазон, отклонения — всё это разные проекции одного и того же движения. В результате внутри данных возникает избыточность: несколько признаков описывают одно и то же поведение рынка, только под разными углами. Для модели это не усиление сигнала, а источник шума и нестабильности.
Именно здесь естественным продолжением матричного подхода становится PCA (Principal Component Analysis, метод главных компонент). Идея его использования проста: не пытаться кормить модель всем сразу, а собрать информацию в более компактные направления, несущие основную вариативность данных.
С математической точки зрения ничего нового не появляется. Это всё та же линейная алгебра. Берётся матрица признаков X, центрируется и умножается на матрицу собственных векторов. В результате получается новое пространство, в котором признаки представляют независимые компоненты изменения рынка.
И важный момент здесь не в формуле, а в эффекте. PCA одновременно решает две задачи. С одной стороны, он сжимает пространство признаков, убирая лишние измерения. С другой — стабилизирует данные, потому что шум и корреляции перестают размазывать сигнал между множеством переменных. Модель начинает работать с более чистой структурой информации.
Практический результат ощущается сразу. Обучение становится устойчивее, поведение модели — предсказуемее, а чувствительность к мелким и случайным колебаниям снижается. При этом код не усложняется вообще. Всё, что меняется — это ещё одна матричная операция над уже подготовленным X.
И здесь важно сохранить ту же архитектурную логику, что и раньше. PCA не выносится в разрозненные вычисления и циклы. Он остаётся частью единого матричного контура в MQL5.
В результате данные становятся не только приведёнными к одному масштабу, но и структурно упрощёнными. Они теряют избыточность, но сохраняют информационное ядро. И это ключевой эффект PCA в контексте трейдинга: модель получает меньше шума, но больше смысла.
Так формируется следующий уровень пайплайна. После нормализации данных мы упорядочиваем их структуру. И чем чище становится это пространство, тем стабильнее ведёт себя модель.
Модель и ONNX: от обучения к исполнению
После того как пространство данных очищено и приведено к устойчивому виду, логика пайплайна закономерно переходит к следующему шагу — самой модели. Но важно сразу зафиксировать принцип: модель здесь не центр системы, а её итоговое выражение.
Обучение происходит вне терминала, в Python. И это не случайный выбор, а вопрос удобства и экосистемы. Обучение модели — это два чётко разделённых этапа, и именно это определяет стабильность всей системы.
На первом этапе используется весь объём обучающей выборки. Здесь не строится модель как таковая, а формируется её система координат. Вычисляются параметры нормализации: средние значения и стандартные отклонения для каждого признака. Параллельно рассчитывается PCA. Он задаёт преобразование исходного пространства признаков в более компактное и упорядоченное представление.
# Standardize features and apply PCA to retain 99% variance scaler = StandardScaler() X_scaled = scaler.fit_transform(X) pca = PCA(n_components=NComponents, random_state=42) X_pca = pca.fit_transform(X_scaled)
Этап подготовки данных критически важен. По сути, здесь фиксируется пространство, в котором модель будет работать. Любое отклонение на этом уровне автоматически переносится дальше. Поэтому он определяет всю последующую устойчивость системы.
После того как пространство данных зафиксировано, происходит переход ко второму этапу. Cобирается модель как вполне конкретный механизм. К этому моменту признаки прошли нормализацию и PCA, так что в сеть попадает собранное, плотное представление рынка. Это важный момент: LSTM не приходится разбираться в хаосе исходных величин. Она сразу работает с пространством, которое для неё подготовили.
Сначала данные переводятся в формат, удобный для PyTorch.
# --- LSTM training block with PyTorch --- # Prepare data for LSTM: form rolling sequence windows X_np = X_pca.astype(np.float32) y_np = y.to_numpy(dtype=float) n_sequences = X_np.shape[0] if n_sequences <= 0: print("Not enough samples for sequence windows; reduce window_size or collect more data.") quit() X_tensor = torch.tensor(X_np, dtype=torch.float32).unsqueeze(1) # (n_samples, 1, n_features) y_tensor = torch.tensor(y_np.reshape(-1, 1), dtype=torch.float32)
На этом этапе ничего умного не происходит, но именно здесь закладывается техническая аккуратность всей дальнейшей работы. Массив превращается в тензор, и к нему добавляется дополнительная размерность. Это маленькая деталь, но для LSTM она принципиальна: сеть ждёт не просто набор признаков, а последовательность. Даже если длина последовательности равна единице, сама форма входа соответствует архитектуре рекуррентной сети.
После этого данные делятся на обучающую и валидационную части. Обычный, на первый взгляд, шаг здесь играет важную роль: модель не просто запоминает историю, а получает возможность проверить себя на отложенном куске данных. Именно так становится видно, учится ли она находить закономерность или просто повторяет шум.
# Train/validation split split = int(Train_test_split * len(X_tensor)) train_ds = TensorDataset(X_tensor[:split], y_tensor[:split]) val_ds = TensorDataset(X_tensor[split:], y_tensor[split:]) train_loader = DataLoader(train_ds, batch_size=Batch_size, shuffle=False) val_loader = DataLoader(val_ds, batch_size=Batch_size, shuffle=False)
Затем данные подаются в DataLoader. И вот здесь процесс приобретает рабочий ритм. Вместо того чтобы кормить сеть по одному примеру, мы подаём ей батчи. Это делает обучение устойчивее и заметно удобнее для оптимизации.
Дальше уже строится сама модель. Здесь важно не перегружать её лишней сложностью. В коде это аккуратная, сдержанная архитектура: несколько рекуррентных слоёв, скрытое состояние фиксированного размера, Dropout для снижения переобучения и финальный линейный слой, который превращает внутреннее состояние сети в одно число прогноза.
class LSTMRegressor(nn.Module): def __init__(self, input_size, hidden_size=64, num_layers=3, dropout=0.1): super().__init__() self.lstm = nn.LSTM(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout) self.fc = nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, (hn, cn) = self.lstm(x) last_h = hn[-1] return self.fc(last_h) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"Using device: {device}") model = LSTMRegressor(input_size=X_np.shape[1], hidden_size=64, num_layers=Layers, dropout=0.2).to(device)
Логика простая, но очень выразительная: сеть сначала собирает смысл, а потом формулирует ответ.
Модель переносится на доступное устройство. Если есть GPU, вычисления уходят туда. Если нет — всё работает на CPU. Для финансовых данных это не роскошь, а нормальная инженерная практика: последовательные расчёты и большие массивы данных быстро становятся тяжёлыми, и ускорение здесь действительно имеет значение.
После этого задаются оптимизатор Adam и функция потерь MSELoss.
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.MSELoss() И только на этом этапе начинается настоящее обучение. До этого момента мы лишь аккуратно выстраивали сцену: подготовили данные, задали форму входа, собрали архитектуру, настроили механизм обновления весов.
for epoch in range(1, Epochs + 1): model.train() train_loss = 0.0 for xb, yb in train_loader: xb, yb = xb.to(device), yb.to(device) optimizer.zero_grad() preds = model(xb) loss = criterion(preds, yb) loss.backward() optimizer.step() train_loss += loss.item() * xb.size(0) train_loss /= len(train_loader.dataset) model.eval() val_loss = 0.0 with torch.no_grad(): for xb, yb in val_loader: xb, yb = xb.to(device), yb.to(device) preds = model(xb) loss = criterion(preds, yb) val_loss += loss.item() * xb.size(0) val_loss /= len(val_loader.dataset) if len(val_loader.dataset) > 0 else float('nan') print(f"Epoch {epoch}/{Epochs} — train_loss: {train_loss:.6f} val_loss: {val_loss:.6f}")
Сила конвейера в последовательности шагов: подготовка данных → сборка модели → обучение. Такой порядок упрощает перенос в MQL5 через ONNX без потери соответствия входов.
В процессе обучения формируется поведенческая часть системы — веса модели, её реакция на анализируемые данные, структура прогнозирования. Но важно понимать: модель обучается не на сырых данных, а на уже приведённом и сжатом представлении.
В результате получаем два блока, которые нельзя смешивать. Первый — это статистическая основа: нормализация и PCA. Второй — сама модель, обученная в этом пространстве. Вместе они формируют единую магистраль потока данных.
После обучения модель экспортируется в ONNX. Формат фиксирует вычислительный граф и делает модель переносимой.
# Try exporting to ONNX try: onnx_path = os.path.join(data_path, 'MQL5', 'Files', 'lstm3.onnx') dummy_input = torch.randn(1, 1, X_np.shape[1], device=device) model.eval() torch.onnx.export( model, dummy_input, onnx_path, input_names=['PCA_features'], output_names=['Forecast'], opset_version=18, dynamo=True, external_data=False, verify=True ) print(f"Exported model to ONNX: {onnx_path}") except Exception as e: print("ONNX export failed:", e)
ONNX в этой архитектуре остаётся контейнером вычислений. Он сохраняет модель в виде фиксированной функции, готовой к исполнению в MQL5 через OnnxCreate и OnnxRun. Но вся логика подготовки данных остаётся за пределами ONNX и строго повторяется в терминале через матрицы и векторы.
Именно поэтому Python в этой схеме играет строго ограниченную роль. Он нужен один раз — чтобы обучить модель и зафиксировать её параметры. После экспорта он больше не участвует в процессе принятия решений. Вся дальнейшая работа переносится в терминал.
Так формируется чёткое разделение ролей. Python отвечает за построение модели. ONNX фиксирует её как вычислительный объект. А MQL5 обеспечивает воспроизведение входных данных и выполнение инференса. И чем точнее соблюдается это разделение, тем стабильнее ведёт себя система в реальной торговой среде.
Воспроизведение и контроль данных в MQL5
После экспорта модели в ONNX работа переносится в терминал. И здесь особенно хорошо видно, почему матрицы и векторы были ключевыми элементами всей архитектуры с самого начала. MQL5 в этой схеме воспроизводит весь путь подготовки данных, на котором эта модель обучалась.
И здесь сразу нужно разделить два разных режима. Первый — инициализация, которая выполняется один раз при запуске советника. Второй — рабочий цикл, который повторяется на каждом новом баре. Это разделение очень важное. Именно оно делает вычислительный процесс устойчивым. Всё, что можно, должно быть подготовлено заранее. А на каждом баре должна оставаться только живая рыночная логика.
На этапе инициализации MQL5 поднимает всю инфраструктуру, на которой будет держаться модель. Сначала загружаются параметры нормализации, центр PCA-пространства и матрица компонент.
if(!LoadBinaryParams(sFileName, vMeans, vScales, vPCAmeans, mPCAcomponents, vEvr, iNFeatures, iNComponents)) { PrintFormat("Error of load PCA params: %d", GetLastError()); return INIT_FAILED; } PrintFormat("Loaded PCA params: features=%d components=%d", iNFeatures, iNComponents);
И здесь стоит обратить внимание на одну важную деталь. Параметры нормализации загружаются в вектора, а PCA-компоненты — в матрицу. Это выглядит естественно, но на самом деле хорошо показывает структуру подготовки данных.
Средние значения и стандартные отклонения — это одномерные статистики. Для каждого признака существует своё среднее и своё отклонение. Поэтому они представлены векторами. Каждый элемент такого вектора соответствует отдельному признаку входного пространства. Логика линейная и прозрачная.
С PCA ситуация уже другая. Здесь речь идёт не о наборе отдельных коэффициентов, а о преобразовании всего пространства признаков. Именно поэтому компоненты загружаются в матрицу. И это уже не просто контейнер данных. Матрица задаёт само правило перехода из исходного пространства признаков в новое, сжатое пространство компонент. Каждая строка здесь описывает направление новой оси после PCA-преобразования. Фактически терминал получает готовую геометрию пространства, построенную в Python на обучающей выборке.
Это очень важный момент для понимания всей архитектуры. Вектора отвечают за локальные операции над отдельными признаками — центрирование и масштабирование. Матрица отвечает за коллективное преобразование всего пространства данных. Встроенные matrix и vectorMQL5 идеально отражают эту математическую структуру прямо в коде.
Затем загружается ONNX-модель, задаются размеры входа и выхода.
//--- load models hONNX = OnnxCreateFromBuffer(model, ONNX_DEFAULT); if(hONNX == INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ", GetLastError()); return INIT_FAILED; } const ulong input_state[] = {1, 1, iNComponents}; if(!OnnxSetInputShape(hONNX, 0, input_state)) { PrintFormat("OnnxSetInputShape error: %d ", GetLastError()); OnnxRelease(hONNX); return INIT_FAILED; } const ulong output_forecast[] = {1, vForecast.Size()}; if(!OnnxSetOutputShape(hONNX, 0, output_forecast)) { Print("OnnxSetOutputShape error ", GetLastError()); OnnxRelease(hONNX); return INIT_FAILED; }
Вот здесь появляется очень важный архитектурный момент. Размер входа модели больше не равен количеству исходных признаков. Он равен количеству PCA-компонент, то есть размерности уже сжатого пространства данных.
Это означает, что модель работает не с полным набором исходных индикаторов и производных величин, а с их компактным представлением после PCA. Шум и избыточные корреляции остаются в исходном пространстве, а в ONNX попадает уже более стабильный и концентрированный сигнал.
Практически это даёт сразу несколько эффектов. Уменьшается размер входного тензора. Снижается вычислительная нагрузка. Упрощается задача самой модели. Но главное — входное пространство становится устойчивее. Сеть перестаёт тратить ресурсы на обработку взаимно дублирующих признаков и начинает работать с более плотным представлением структуры рынка.
Именно поэтому PCA здесь — не просто способ уменьшить размерность. Он становится промежуточным слоем между сырыми рыночными данными и моделью. А матрицы MQL5 позволяют воспроизводить этот слой прямо внутри терминала почти в той же форме, в которой он существовал в Python.
После этого подключаются индикаторы и готовятся рабочие буферы.
//--- Indicators if(!ciSMA.Create(Symb.Name(), TimeFrame, 12, 0, MODE_SMA, PRICE_CLOSE)) { Print("SMA create error ", GetLastError()); OnnxRelease(hONNX); return INIT_FAILED; } ciSMA.BufferResize(2); for(uint i = 0; i < ciMACD.Size(); i++) { if(!ciMACD[i].Create(Symb.Name(), TimeFrame, int(mMACDset[i, 0]), int(mMACDset[i, 1]), int(mMACDset[i, 2]), PRICE_CLOSE)) { PrintFormat("MACD %d create error %d", i, GetLastError()); OnnxRelease(hONNX); return INIT_FAILED; } ciMACD[i].BufferResize(4); }
Это не просто техническая формальность. Это момент, когда терминал получает ту же статистическую основу, на которой модель обучалась в Python. Иначе говоря, MQL5 восстанавливает среду, в которой генерируемый моделью прогноз имеет смысл.
Особенно наглядно это видно на загрузке параметров подготовки данных. Они не вычисляются заново и не подбираются на месте. Они приходят в терминал уже готовыми. В этом сильная сторона матричного подхода. Параметры подготовки данных загружаются один раз и фиксируются в вычислительной схеме; они не пересчитываются на месте. В результате терминал начинает работать с моделью, привязанной к конкретному пространству данных.
После этого начинается рабочий цикл. Он уже совсем другого типа. На каждом новом баре советник собирает свежие рыночные данные.
ciSMA.Refresh(); for(uint i = 0; i < ciMACD.Size(); i++) ciMACD[i].Refresh(); if(!vRates.CopyRates(Symb.Name(), TimeFrame, COPY_RATES_CLOSE, 1, 12)) { Print("CopyRates error ", GetLastError()); return; }
Формирует вектор признаков.
vInputs[0] = vRates[11] - vRates[10]; vInputs[1] = (vRates[11] - vRates[0]) / 11; vInputs[2] = vInputs[1] - vInputs[0]; vInputs[3] = float(ciSMA.Main(1)); for(uint i = 0; i < ciMACD.Size(); i++) { vInputs[4 + i * 6] = float(ciMACD[i].Main(1)); vInputs[5 + i * 6] = float(ciMACD[i].Main(1) - ciMACD[i].Main(2)); vInputs[6 + i * 6] = float(ciMACD[i].Signal(1)); vInputs[7 + i * 6] = float(ciMACD[i].Signal(1) - ciMACD[i].Signal(2)); vInputs[8 + i * 6] = vInputs[6 + i * 6] - vInputs[4 + i * 6]; vInputs[9 + i * 6] = vInputs[7 + i * 6] - vInputs[5 + i * 6]; }
И прогоняет его через те же преобразования, которые были использованы при обучении. Сначала идёт нормализация, а затем PCA-проекция.
bool TransformPCA(const vector<float> &data, const vector<float> &scaler_mean, const vector<float> &scaler_scale, const vector<float> &pca_mean, const matrix<float> &pca_components, vector<float> &out) { ulong n = data.Size(); if(n == 0) return false; //--- vector<float> centered = (data - scaler_mean) / scaler_scale - pca_mean; //--- projection: out = pca_components * centered (matrix * vertical vector) out = pca_components.MatMul(centered); return true; }
Здесь особенно хорошо видно преимущество встроенной математики MQL5. Код практически повторяет саму математическую запись. Терминал просто воспроизводит те же операции, которые использовались при обучении модели.
Только после этого подготовленный вектор передаётся в ONNX.
//--- ONNX if(!OnnxRun(hONNX, ONNX_LOGLEVEL_INFO, vCompressed, vForecast)) { PrintFormat("OnnxRun error: %d ", GetLastError()); return; }
Здесь уже нет места тяжёлой подготовке или повторной инициализации. Всё работает как конвейер. Новый бар — новый вход. Но маршрут этого входа остаётся неизменным.
Именно в этом и проявляется практическая ценность встроенных матриц и векторов MQL5. Они позволяют один раз собрать всю структуру данных, а потом воспроизводить её столько раз, сколько нужно, без лишней суеты и без потери согласованности. Инициализация задаёт каркас. Рабочий цикл просто использует его. Это делает систему не только компактной, но и дисциплинированной. А для ML в терминале дисциплина важнее эффектных слов. Потому что именно она удерживает модель в том же пространстве, в котором она действительно умеет работать.
Проверка результата: от модели к торговому поведению
Модель обучена в Python, а вычислительный конвейер аккуратно воспроизведён в MQL5. Нормализация и PCA работают через матрицы и векторы. ONNX-модель загружена в терминал. Все элементы соединены в единую систему. Теперь главный вопрос звучит просто: как эта система поведёт себя на рынке.
Именно здесь на первый план выходит тестер стратегий MetaTrader 5. Он показывает не только сам факт работы модели, но и то, насколько устойчиво ведёт себя весь ML-конвейер — от подготовки данных до торгового решения. Это полноценная проверка всей архитектуры в условиях живой рыночной динамики.
И в этом состоит одна из сильнейших сторон платформы. В Python легко получить хорошие значения функции потерь или высокую точность на обучающей выборке. Но рынок быстро проверяет такие результаты на прочность. Тестер MetaTrader 5 позволяет увидеть этот момент сразу. Важна совокупность метрик, которые предоставляет MetaTrader 5. И именно в этом проявляется настоящая ценность тестера. Он позволяет смотреть на стратегию как на живую систему с собственным поведением, риском, устойчивостью и внутренней структурой.

- Качество истории (History Quality) — это важная отправная точка.
Условия тестирования убирают сомнения в корректности прогона. Результаты можно считать технически надёжными, а значит, анализ метрик имеет смысл. - Финансовый результат (Total Net Profit) составил 373.80 USD при начальном депозите 1000.00 USD.
Это означает, что стратегия завершила тест в плюсе. Но сам по себе этот показатель ещё ничего не говорит о качестве логики, поэтому его нужно читать вместе с остальными метриками. - Gross Profit оказался равен 612.94 USD, а Gross Loss — -239.14 USD.
Это означает, что прибыльные сделки в сумме заметно перевесили убыточные, и именно это обеспечило положительный результат. Стратегия зарабатывает за счёт устойчивого перевеса положительных исходов. - Profit Factor равен 2.56.
Это сильный показатель. Прибыль более чем в два с половиной раза превышает убыток. Для торговой системы это уже признак того, что положительные сделки действительно перевешивают отрицательные. - Expected Payoff составил 7.19.
Это означает, что в среднем каждая сделка приносила положительный математический вклад. Система живёт не за счёт одной-двух удачных серий, а сохраняет положительное ожидание на уровне сделки. - Recovery Factor, равный 1.58, показывает, насколько результат оправдывает пережитую просадку.
В данном случае система умеет восстанавливаться после потерь, хотя запас прочности нельзя назвать огромным. Стратегия рабочая, но просадки требуют уважения. - Sharpe Ratio достиг 15.38.
Это очень сильное значение, и оно говорит о высоком отношении доходности к волатильности результата. Но здесь важно помнить, что в тестере этот показатель нужно читать вместе с просадками и серийностью сделок. - AHPR составил 1.0065, то есть 0.65%. А GHPR — 1.0061, то есть 0.61%.
Эти метрики показывают среднюю доходность сделки в арифметическом и геометрическом смысле. Вывод здесь аккуратный: рост есть, и он встроен в структуру торгового потока. - Абсолютная просадка по балансу (Balance Drawdown Absolute) составила 55.57 USD, а максимальная (Balance Drawdown Maximal) — 84.62 USD, что соответствует 6.46% (Balance Drawdown Relative).
Это означает, что по балансу стратегия держалась относительно спокойно. Закрытые сделки формировали приемлемую кривую капитала. - Совсем другая картина у Equity Drawdown. Абсолютная просадка составила 99.08 USD, а максимальная — 235.84 USD, что соответствует 17.12%.
Это важный момент: открытые позиции создавали рыночную нагрузку, даже если итоговый баланс выглядел умеренно. Вывод здесь честный: стратегия умеет зарабатывать, но делает это не без внутреннего напряжения. - Margin Level составил 125.35%.
Это безопасный уровень, но без значительного запаса. Он говорит о том, что запас по марже был достаточным. Однако система не работала в стерильных условиях. Торговая логика оставалась в рабочем диапазоне, без критического давления на счёт. - Z-Score оказался равен -0.70 при уровне значимости 51.61%.
Это указывает на отсутствие ярко выраженной аномальной серии выигрышей и потерь. Иными словами, результаты не выглядят как случайная вспышка удачи. Последовательность сделок ближе к рабочей торговой структуре, чем к случайному шуму. - LR Correlation равен 0.90, а LR Standard Error — 64.55.
Корреляция указывает на достаточно ровную линейную динамику equity-кривой. А стандартная ошибка показывает, что движение не было идеально гладким. Рост есть, но он формировался через обычные рыночные колебания. - Всего было открыто 52 позиции (Total Trades) и зафиксировано 95 сделок (Total Deals).
Это уже достаточный объём, чтобы увидеть характер стратегии. Тест не сводится к нескольким случайным входам, а даёт вполне содержательную картину. - Short Trades составили 19 сделок с 68.42% прибыльных, тогда как Long Trades — 33 сделки с 39.39% прибыльных.
По направлению сделок видно интересное расхождение. Это очень важный сигнал. Стратегия заметно лучше чувствует короткие позиции, чем длинные. Значит, у неё может быть выраженный уклон в сторону нисходящих движений. - Profit Trades и Loss Trades оказались одинаковыми — по 26 сделок, то есть по 50.00%.
На первый взгляд это паритет, но он не мешает системе оставаться прибыльной. Стратегия зарабатывает за счёт качества сделок. - Максимальная прибыльная сделка (Largest profit trade) составила 171.36 USD, а убыточная (Largest loss trade) — -31.46 USD.
Средняя прибыльная сделка (Average profit trade) равна 23.57 USD, а убыточная (Average loss trade) — -9.20 USD. Это, пожалуй, одни из самых важных метрик в отчёте. Средняя прибыльная сделка более чем в два раза сильнее средней убыточной. Система умеет вытягивать перевес даже при неидеальной доле успешных входов. - Максимальная прибыльная серия (Maximum consecutive wins) составила 5 сделок на сумму 139.90 USD, а убыточная (Maximum consecutive losses) — 6 сделок на сумму -61.24 USD.
Это показывает, что стратегия способна ловить хорошие серии, но не застрахована от серии неудач.
За каждой метрикой скрывается отдельный аспект механики стратегии. И в этом главное достоинство платформы: она позволяет видеть не только результат, но и его структуру.
Заключение
Машинное обучение в трейдинге часто выглядит чем-то тяжёлым и перегруженным. Но практика показывает другую картину. MetaTrader 5 содержит большую часть инструментов, необходимых для построения полноценного ML-конвейера. Именно это и было главной целью статьи. Не продемонстрировать волшебную модель и не показать очередную попытку предсказать рынок, а разобрать сам путь: от обычных рыночных данных до работающей ONNX-модели внутри терминала. И здесь особенно важно, что ключевую роль играют встроенные матрицы и векторы MQL5. Они превращают подготовку данных из набора разрозненных операций в понятную и управляемую вычислительную схему.
Нормализация, статистические вычисления, работа с пространством признаков, PCA-преобразование — всё это воспроизводится прямо внутри терминала практически в той же форме, в которой описывается математически. Код перестаёт быть перегруженным техническими деталями и начинает отражать саму логику вычислений. Это резко снижает порог входа в ML-разработку для трейдера.
Не менее важна и поддержка ONNX. MetaTrader 5 позволяет использовать модель как готовый вычислительный блок. Python остаётся инструментом подготовки модели, а терминал становится средой её стабильного исполнения и контроля. Такое разделение делает архитектуру значительно чище и практичнее. Модель обучается один раз, а дальнейшая работа переносится в MQL5. Это даёт прозрачность, воспроизводимость и полный контроль над входными данными.
Отдельную ценность даёт тестер стратегий MetaTrader 5. Он позволяет оценивать не только итоговую прибыль, но и внутреннее поведение всей системы. Просадки, устойчивость equity, структура сделок, качество восстановления после убытков, стабильность торгового ритма — всё это становится измеримым через богатый набор встроенных метрик. За каждой из них скрывается отдельная грань поведения стратегии. И именно такой подход превращает тестирование из формальной проверки в полноценный инженерный анализ.
В результате MetaTrader 5 может использоваться для построения и воспроизведения ML-пайплайнов без переноса логики во внешнюю инфраструктуру. Главный вывод: ML в MetaTrader 5 реализуется через линейную математику, матрицы и прозрачную подготовку данных. Без ощущения, что ML существует где-то отдельно от терминала.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | create_pca_lstm.py | Скрипт | Скрипт построения и обучения модели |
| 2 | MLpipeline.mq5 | Советник | Советник тестирования ONNX-модели |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Разработка инструментария для анализа Price Action (Часть 42): Интерактивное тестирование на графике с кнопочной логикой и статистическими уровнями
Нейросети в трейдинге: Внимание, память и рыночные паттерны в GDformer
Разработка инструментария для анализа Price Action (Часть 43): Вероятностный анализ свечных паттернов и пробоев
Сеточный советник на клеточном автомате с онлайн-обучением в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
ИМХО, нормализация и вычисление PCA на всем наборе данных и только потом разделение его на обучающую и валидационную выборки означает заглядывание в будущее. При работе онлайн вы не имеете возможность изменить нормализацию или адаптировать PCA с учетом последних данных. Честным экспериментом было бы начальное разделение данных на два набора и потом нормализация+PCA только на in-sample части, а затем применение найденных метапараметров преобразования входных данных на out-of-sample.
В результате даже обычный стрелочный индикатор показывает стрелки "на самых раворотах".
Опасная штука, сразу не поймёшь, в чём подвох.