От одиночного CatBoost к Cross-Fitted Stack в MQL5: ансамбль без утечки будущего и отдельный audit-forward 2026
Введение
Ансамбли моделей часто используют для того, чтобы объединить несколько разных источников сигнала и получить более устойчивое торговое решение. Однако в задачах с временными рядами сам по себе стэкинг не гарантирует улучшения результата. Более того, если второй уровень ансамбля обучен на прогнозах, полученных с нарушением временного порядка, его качество может выглядеть значительно лучше реального.
В статье описана практическая реализация ансамбля для MetaTrader 5. Базовый уровень составляют модели MIDAS-SINERGY. Поверх них строится отдельный CatBoost stack, оценивающий каждого торгового кандидата по исходному сигналу, треугольному контексту, времени, инструменту и согласованности классификаторов
Основное внимание уделено двум вопросам. Первый — как получить данные для обучения второго уровня так, чтобы будущие результаты не попадали в его признаки даже косвенно. Для этого используется purged expanding walk-forward: каждый очередной год прогнозируется моделями, которые обучались только на более ранней истории, а между train и validation оставляется 72-часовой embargo.
Второй вопрос — как перенести уже обученный ансамбль в MQL5 без внешнего Python-процесса. В итоговом советнике встроена 31 CatBoost-модель: базовые модели, модели треугольного контекста, три участника stack-ансамбля, калибратор вероятности и модель управления размером позиции.. Их деревья выполняются непосредственно средствами MQL5.
Эксперимент проводится на десяти валютных парах с принятием решений по закрытым барам H1. Для оценки качества используется несколько уровней проверки: out-of-fold результаты 2022–2025 годов, отдельный audit 2026 года и последующий тест готового многовалютного советника в Strategy Tester.
Цель работы — не показать очередную более сложную модель, а построить воспроизводимую цепочку от честного формирования данных второго уровня до исполнения тех же деревьев непосредственно в торговом терминале.
Практическая проблема: как построить stack без утечки данных
Главная сложность stacking в торговых системах возникает ещё до выбора параметров CatBoost. Необходимо правильно получить прогнозы моделей первого уровня, которые затем станут входными данными для второго уровня.
Рассмотрим распространённую ошибку. Базовая модель обучается на всей доступной выборке. После этого для тех же строк вычисляются её прогнозы, а полученные значения передаются в meta-модель. Формально всё работает: второй уровень получает признаки, CatBoost обучается, метрики растут. Но такой эксперимент уже содержит утечку.
Причина заключается в том, что прогноз первого уровня для конкретной строки был получен моделью, которая во время обучения уже видела target этой строки. Meta-модель фактически получает не настоящий прогноз неизвестного будущего, а результат модели, частично сформированный на основании уже известного ответа.
Для временных рядов проблема становится ещё серьёзнее. Нельзя просто случайно разбить данные на folds, потому что строки одного периода связаны между собой, а торговые события могут пересекаться по времени. Поэтому в V5 используется последовательный purged expanding walk-forward.
Для каждого validation-года действует правило:
- train содержит только историю, расположенную раньше начала проверяемого года;
- между последней обучающей строкой и validation оставляется 72-часовой embargo;
- прогнозы validation-года сохраняются как out-of-fold и больше не пересчитываются моделью, обученной на этом же периоде.
Таким образом формируется последовательность OOF-прогнозов за 2022, 2023, 2024 и 2025 годы. Именно эти значения используются для построения второго уровня, выбора его policy и обучения калибраторов.
Отдельная проблема появляется уже после завершения ML-части. Даже идеально организованный cross-fitting бесполезен, если итоговый советник считает признаки иначе, чем Python.
Для встроенного CatBoost-инференса должны полностью совпадать:
- порядок входных признаков;
- индексы признаков каждого split;
- пороговые значения border;
- направление сравнения;
- индексы листьев;
- leaf values;
- scale и bias модели;
- порядок моделей внутри ансамбля;
- формула вычисления ensemble mean и disagreement;
- входы probability calibrator и lot calibrator.
Поэтому перенос в MQL5 рассматривается как часть эксперимента, а не как отдельная задача после обучения. Один и тот же feature vector должен приводить к тому же raw-score как в CatBoost, так и во встроенном evaluator советника.
В работе используется MetaTrader 5 и десять валютных пар: AUDUSD, CADJPY, CHFJPY, EURJPY, EURUSD, GBPCHF, GBPUSD, USDCAD, USDCHF и USDJPY.
Основной временной масштаб — H1. Решение принимается по уже сформированному состоянию рынка, а торговый терминал во время работы не обращается к Python и не загружает модель через ONNX.
Финальные deploy-модели V5 обучаются только на данных, доступных до конца 2025 года. Период 2026 года затем используется отдельно, чтобы проверить поведение уже замороженного ансамбля на новых данных.
В советнике также присутствует экспериментальный Soft Grid. Он является самостоятельным execution-слоем и не используется при расчёте основных OOF-метрик и audit-метрик stack-модели. Его влияние рассматривается отдельно в терминальном тесте, чтобы не смешивать качество ML-фильтра с результатом дополнительного управления позицией.
Архитектура V5 и критерии воспроизводимости
V5 не заменяет предыдущие модели. Immutable V3.10 и V4.00 сохраняются отдельно и используются как baseline. Это позволяет сравнивать новый stack с прежней системой без незаметного переобучения базовой линии.

Рисунок 1. Путь решения в V5: immutable base models и triangle context формируют 39 признаков, три CatBoost members дают raw-score, после чего отдельные модели оценивают вероятность и размер позиции.
| Блок | Количество |
|---|---|
| Base models | 10 |
| Triangle models | 16 |
| V5 stack/calibration models | 5 |
| Всего моделей | 31 |
| Деревьев | 2 360 |
| Split conditions | 10 672 |
| Leaf values | 60 420 |
Критерии успешной сборки задаются до тестирования: OOF-прогнозы не должны использовать будущие строки; финальные deploy-members обучаются только до конца 2025 года; 2026 оценивается после заморозки policy; одинаковый feature vector должен приводить к одинаковому raw-score в CatBoost и MQL5.
Откуда берутся candidate rows
Stack не создаёт торговый сигнал из OHLC с нуля. Он получает уже сформированные кандидаты от базовых и triangle-моделей и отвечает на более узкий вопрос: насколько конкретному кандидату можно доверять в текущем контексте.
| Источник | Строк | Роль |
|---|---|---|
| V2_DIRECTION | 1 182 | основное направление базовой модели |
| V3_STRONG_MOVE | 145 | кандидаты сильного движения |
| TRIANGLE_NATIVE | 174 | нативные сигналы triangle-моделей |
| Всего | 1 501 | candidate dataset второго уровня |
Candidate dataset начинается 17 января 2020 года и заканчивается 30 июля 2026 года. До построения stack-признаков triangle-выходы агрегируются по времени и символу: учитываются доля согласия с базовым направлением, статистики meta-probability, lot multiplier и сила triangle direction.
Purged expanding walk-forward и embargo
Для каждого календарного года validation используется только история, которая закончилась минимум за 72 часа до начала этого года. Обучающая выборка постепенно расширяется, поэтому процедура называется purged expanding walk-forward. Трёхдневный embargo превышает основной 24-часовой горизонт удержания и снижает риск пересечения зависимых торговых событий на границе train и validation.
train(Y): time < 1 января Y - 72 часа
validation(Y): календарный год Y
| OOF год | Train | Validation | Предыдущие OOF для calibration |
|---|---|---|---|
| 2022 | 429 | 255 | 0 |
| 2023 | 687 | 153 | 253 |
| 2024 | 842 | 295 | 408 |
| 2025 | 1 135 | 197 | 701 |
| 2026 | 1 329 | 167 | 895 |
Probability calibrator и lot calibrator также обучаются только на OOF-строках предыдущих лет. Поэтому прогноз второго уровня для 2024 года не может использовать ошибки или исходы 2024-2026 годов.
Контракт из 39 stack-признаков
Второй уровень использует 39 полей. Порядок закреплён в feature_contract_stack.json и должен совпадать между Python и MQL5.
| Группа | Количество | Примеры |
|---|---|---|
| Исходный кандидат | 6 | side, score, abs_score, signed_conf, source_native, source_base |
| Triangle-контекст | 14 | согласие, probability/lot statistics, direction и confidence |
| Время | 6 | hour, day-of-week и month в sin/cos представлении |
| Symbol one-hot | 10 | AUDUSD ... USDJPY |
| Model-family one-hot | 3 | V2_DIRECTION, V3_STRONG_MOVE, TRIANGLE_NATIVE |
Полная функция BuildStackFeatures() ниже показывает тот же порядок, который использует советник. Временные признаки кодируются через sin/cos, а символ и семейство модели передаются one-hot полями.
//+------------------------------------------------------------------+ //| Формирует 39 признаков второго уровня | //| Вход: базовый сигнал, triangle-контекст и время решения | //+------------------------------------------------------------------+ void BuildStackFeatures(const int symbol_index, const int signal, const double score, const bool native_signal, const double triangle_count, const double agreement, const double probability_mean, const double probability_max, const double probability_min, const double probability_std, const double lot_mean, const double lot_max, const double lot_min, const double lot_std, const double direction_mean, const double direction_max, const double confidence_mean, const double confidence_max, double &features[]) { ArrayResize(features,STACK_FEATURE_COUNT); int index=0; //--- признаки исходного кандидата features[index++]=signal; features[index++]=score; features[index++]=MathAbs(score); features[index++]=signal*score; features[index++]=(native_signal ? 1.0 : 0.0); features[index++]=(native_signal ? 0.0 : 1.0); //--- агрегированный контекст четырех triangle-моделей features[index++]=triangle_count; features[index++]=agreement; features[index++]=probability_mean; features[index++]=probability_max; features[index++]=probability_min; features[index++]=probability_std; features[index++]=lot_mean; features[index++]=lot_max; features[index++]=lot_min; features[index++]=lot_std; features[index++]=direction_mean; features[index++]=direction_max; features[index++]=confidence_mean; features[index++]=confidence_max; //--- циклическое представление часа, дня недели и месяца MqlDateTime time_data; TimeToStruct(iTime(g_symbols[symbol_index],PERIOD_H1,0),time_data); features[index++]=MathSin(2.0*PI_CONST*time_data.hour/24.0); features[index++]=MathCos(2.0*PI_CONST*time_data.hour/24.0); features[index++]=MathSin(2.0*PI_CONST*time_data.day_of_week/7.0); features[index++]=MathCos(2.0*PI_CONST*time_data.day_of_week/7.0); features[index++]=MathSin(2.0*PI_CONST*(time_data.mon-1)/12.0); features[index++]=MathCos(2.0*PI_CONST*(time_data.mon-1)/12.0); //--- one-hot идентификатор символа for(int symbol=0;symbol<SYMBOL_COUNT;symbol++) features[index++]=(symbol==symbol_index ? 1.0 : 0.0); //--- one-hot семейство модели features[index++]=(!native_signal && G_MODEL_FAMILY[symbol_index]==2 ? 1.0 : 0.0); features[index++]=(!native_signal && G_MODEL_FAMILY[symbol_index]==1 ? 1.0 : 0.0); features[index++]=(native_signal ? 1.0 : 0.0); }
Функция не использует будущий результат сделки. Поле net_pips существует только в обучающем наборе как target и для расчёта sample weights.
Три heterogeneous CatBoost members
V5 использует три классификатора с разной глубиной и bootstrap. Цель состоит не в усреднении трёх одинаковых seed, а в получении немного разных границ решений.
| Member | Depth | L2 | Bootstrap | Subsample | RSM |
|---|---|---|---|---|---|
| 1 | 4 | 18 | MVS | 0.82 | 0.88 |
| 2 | 5 | 28 | Bernoulli | 0.78 | 0.82 |
| 3 | 6 | 40 | Bayesian | - | 0.76 |
Общие параметры OOF-моделей: 220 деревьев, learning_rate 0.025, Logloss, AUC как eval metric, random_strength 0.18 и nan_mode Min. Для Bayesian-member используется bagging_temperature 0.7.
Sample weight состоят из двух частей. Свежесть имеет half-life примерно 2,5 года, а экономически заметные сделки получают дополнительный вес через ограниченную степень абсолютного net_pips. Это сохраняет старую историю, но снижает её влияние относительно более свежих режимов.
Disagreement, probability calibrator и lot calibrator
После трёх raw-score вычисляются среднее и стандартное отклонение. Среднее описывает общее мнение ансамбля, а стандартное отклонение используется как disagreement. Два кандидата с одинаковым mean score могут иметь разную устойчивость, если members сильно расходятся между собой.
Probability calibrator получает только ensemble_raw_mean и ensemble_raw_std. Для него заданы monotone constraints: вероятность не должна падать при росте среднего raw-score и не должна расти при увеличении disagreement. Lot calibrator получает 39 stack-признаков, calibrated probability и disagreement, то есть всего 41 поле.
//+------------------------------------------------------------------+ //| Выполняет инференс V5 Cross-Fitted Stack | //| Выход: вероятность, множитель лота и disagreement | //+------------------------------------------------------------------+ bool StackPredict(const double &features[], double &probability, double &lot_multiplier, double &uncertainty) { double raw[3]; //--- три heterogeneous CatBoost members for(int member=0;member<3;member++) raw[member]=Eval1(26+member,features); double raw_mean=(raw[0]+raw[1]+raw[2])/3.0; //--- disagreement используется как отдельная оценка неопредёленности uncertainty=MathSqrt(((raw[0]-raw_mean)*(raw[0]-raw_mean) +(raw[1]-raw_mean)*(raw[1]-raw_mean) +(raw[2]-raw_mean)*(raw[2]-raw_mean))/3.0); double raw_probability=Sigmoid(raw_mean); double calibrator_features[2]; calibrator_features[0]=raw_mean; calibrator_features[1]=uncertainty; double calibrated_probability=Clamp(Eval1(29,calibrator_features),0.01,0.99); double blend=ProfileCalibrationBlend(); //--- Audit Robust частично сохраняет некалиброванную вероятность probability=Clamp((1.0-blend)*raw_probability +blend*calibrated_probability,0.01,0.99); double lot_features[]; ArrayResize(lot_features,STACK_LOT_FEATURE_COUNT); for(int i=0;i<STACK_FEATURE_COUNT;i++) lot_features[i]=features[i]; lot_features[STACK_FEATURE_COUNT]=calibrated_probability; lot_features[STACK_FEATURE_COUNT+1]=uncertainty; //--- отдельная regression-модель оценивает множитель риска lot_multiplier=Clamp(Eval1(30,lot_features),0.25,1.35); return(MathIsValidNumber(probability) && MathIsValidNumber(lot_multiplier)); }
Важная деталь реализации: lot calibrator получает именно calibrated_probability, как в экспортированном советнике. Профиль Audit Robust затем смешивает raw и calibrated probability только для торгового gate.
Перенос деревьев в MQL5
Финальный советник не вызывает Python и не использует ONNX. Все деревья превращены в массивы depths, индексы признаков, borders, leaf values, scale и bias. Ниже показан центральный evaluator, общий для 31 встроенной модели.
//+------------------------------------------------------------------+ //| Выполняет точный инференс встроенной модели CatBoost | //| Вход: индекс модели и вектор признаков | //+------------------------------------------------------------------+ bool EvalVector(const int model_index,const double &features[],double &output[]) { if(model_index<0 || model_index>=MODEL_COUNT) return(false); int output_dim=G_MODEL_OUTPUT_DIM[model_index]; ArrayResize(output,output_dim); ArrayInitialize(output,0.0); int tree_first=G_MODEL_TREE_OFFSET[model_index]; int tree_last =tree_first+G_MODEL_TREE_COUNT[model_index]; //--- последовательно обходим симметричные деревья модели for(int tree=tree_first;tree<tree_last;tree++) { int leaf_index=0; int split_offset=G_TREE_SPLIT_OFFSET[tree]; int depth=G_TREE_DEPTH[tree]; //--- строим индекс листа из решений feature > border for(int level=0;level<depth;level++) { int split=split_offset+level; double value=features[G_SPLIT_FEATURE[split]]; if(MathIsValidNumber(value) && value>G_SPLIT_BORDER[split]) leaf_index|=(1<<level); } int leaf_offset=G_TREE_LEAF_OFFSET[tree]+leaf_index*output_dim; for(int j=0;j<output_dim;j++) output[j]+=G_LEAF_VALUE[leaf_offset+j]; } int bias_offset=G_MODEL_BIAS_OFFSET[model_index]; //--- применяем scale и bias, экспортированные из CatBoost for(int j=0;j<output_dim;j++) output[j]=output[j]*G_MODEL_SCALE[model_index]+G_MODEL_BIAS[bias_offset+j]; return(true); }
Статическая проверка пакета насчитала 31 модель, 2 360 деревьев, 10 672 split conditions и 60 420 leaf values. Unsupported split types не обнаружены, а максимальные индексы признаков находятся внутри соответствующих feature contracts.
Профили V5 и policy freeze
После получения probability система применяет один из четырёх V5-профилей. Порог и доля calibration задаются отдельными функциями.
//+------------------------------------------------------------------+ //| Возвращает минимальную вероятность выбранного V5-профиля | //+------------------------------------------------------------------+ double ProfileThreshold() { if(InpProfile==V5_AUDIT_ROBUST) return(0.5400); if(InpProfile==V5_BALANCED) return(0.5375); if(InpProfile==V5_SELECTIVE) return(0.6000); if(InpProfile==V5_ULTRA_SELECTIVE) return(0.6200); return(0.0); } //+------------------------------------------------------------------+ //| Возвращает долю калиброванной вероятности | //+------------------------------------------------------------------+ double ProfileCalibrationBlend() { if(InpProfile==V5_AUDIT_ROBUST) return(0.25); return(1.0); }
| Профиль | Threshold | Calibration blend | Назначение |
|---|---|---|---|
| V5 Audit Robust | 0.5400 | 0.25 | policy выбран по OOF 2022-2025 |
| V5 Balanced | 0.5375 | 1.00 | больше кандидатов |
| V5 Selective | 0.6000 | 1.00 | сильная фильтрация |
| V5 Ultra Selective | 0.6200 | 1.00 | максимальная селективность |
Для Audit Robust threshold 0.54 и calibration blend 0.25 выбирались только по OOF 2022-2025. После этого 2026 использовался как отдельный audit без повторной настройки policy.
Интеграция stack gate в торговый цикл
После появления базового или native triangle-кандидата советник собирает triangle statistics, строит 39 полей и пропускает сигнал через StackPredict(). Если вероятность ниже профильного threshold или uncertainty превышает разрешённый предел, сигнал обнуляется до открытия позиции.
//--- фрагмент ProcessSymbol(): V5 gate после формирования кандидата if(signal!=0 && IsV5Profile()) { double triangle_count; double agreement; double probability_mean; double probability_max; double probability_min; double probability_std; double lot_mean; double lot_max; double lot_min; double lot_std; double direction_mean; double direction_max; double confidence_mean; double confidence_max; //--- для native-кандидата triangle-контекст уже известен if(native_signal) { triangle_count=1.0; agreement=1.0; probability_mean=triangle_probability; probability_max=triangle_probability; probability_min=triangle_probability; probability_std=0.0; lot_mean=triangle_lot; lot_max=triangle_lot; lot_min=triangle_lot; lot_std=0.0; } else { TriangleAggregate(symbol_index,signal, triangle_count,agreement, probability_mean,probability_max, probability_min,probability_std, lot_mean,lot_max,lot_min,lot_std, direction_mean,direction_max, confidence_mean,confidence_max); } double stack_features[]; BuildStackFeatures(symbol_index,signal,score,native_signal, triangle_count,agreement, probability_mean,probability_max, probability_min,probability_std, lot_mean,lot_max,lot_min,lot_std, direction_mean,direction_max, confidence_mean,confidence_max, stack_features); if(!StackPredict(stack_features,model_probability, model_lot,uncertainty) || model_probability<ProfileThreshold() || uncertainty>InpMaximumStackUncertainty) signal=0; }
Это именно фрагмент ProcessSymbol(), а не полный обработчик. После gate код отдельно проверяет число уже открытых корзин, ATR, spread, длительность существующей позиции и размер риска.
Сравнение V4 и V5 на 2020-2026
Общий исторический результат не показывает безусловного превосходства V5. Immutable V4 остаётся лучшим по accumulated net pips, тогда как Selective-профили V5 уменьшают просадку и повышают Profit Factor ценой меньшего числа сделок.
| Профиль | Сделки | Net, pips | PF | Max DD, pips | Win rate |
|---|---|---|---|---|---|
| V4 Immutable | 1 005 | 14 267.7 | 1.654 | -1 800.3 | 56.1% |
| V5 Audit Robust | 989 | 12 352.8 | 1.585 | -1 573.1 | 54.9% |
| V5 Balanced | 1 069 | 13 675.2 | 1.605 | -1 083.1 | 55.3% |
| V5 Selective | 406 | 6 948.1 | 1.877 | -562.0 | 55.7% |
| V5 Ultra Selective | 388 | 6 876.5 | 1.943 | -588.3 | 56.2% |

Рисунок 2. Immutable V4 сохраняет максимальный accumulated Net. Более строгие V5-профили уменьшают число сделок.

Рисунок 3. Selective и Ultra Selective повышают исторический Profit Factor, но это преимущество необходимо проверять отдельно на новом режиме.
Отдельный deploy audit 2026
Финальные три stack-members обучены только на данных до 31 декабря 2025 года. Период с 1 января по 31 июля 2026 года содержит 167 candidate rows и используется как отдельный deploy audit.
| Профиль | Сделки | Net, pips | PF | Max DD, pips |
|---|---|---|---|---|
| V4 Immutable | 124 | -260.6 | 0.910 | -917.3 |
| V5 Audit Robust | 129 | +221.4 | 1.091 | -524.4 |
| V5 Balanced | 164 | -475.1 | 0.870 | -858.9 |
| V5 Selective | 0 | 0.0 | 0.000 | 0.0 |
| V5 Ultra Selective | 0 | 0.0 | 0.000 | 0.0 |

Рисунок 4. На замороженном audit-отрезке положительный Net сохранил только профиль V5 Audit Robust. Balanced и immutable V4 оказались отрицательными, а два наиболее строгих профиля не открыли сделок.
PF 1.091 у Audit Robust нельзя интерпретировать как доказанный стабильный edge. Это слабый положительный audit, который прежде всего показывает различие между выбором policy на OOF и поведением после смены режима.
Почему калибровка ухудшилась
На 2026 audit calibrated probability оказалась хуже raw ensemble сразу по трём прогнозным метрикам.
| Метрика | Raw ensemble | Calibrated |
|---|---|---|
| AUC | 0.51005 | 0.47552 |
| Brier | 0.25514 | 0.25638 |
| LogLoss | 0.70359 | 0.70603 |
Это полезный отрицательный результат. Даже OOF-calibrator способен устареть при смене режима. Именно поэтому Audit Robust использует 75% raw probability и только 25% calibrated probability вместо полного доверия к калибратору.
Риск, удержание и Soft Grid
По умолчанию советник использует риск 0.20% equity на начальную позицию, ATR period 24, virtual risk distance 3 ATR, максимум пять одновременно активных символов и hold limit 24 H1-бара. Для live-режима предусмотрены ATR-protected SL/TP.
Soft Grid является отдельным экспериментальным слоем и по умолчанию выключен: InpUseSoftGrid=false. Ни одна таблица performance выше не включает grid. В коде ограничены максимальное число уровней, шаг добавления в ATR, максимальный basket risk и отключение новых добавлений при equity drawdown.
Такое разделение важно для воспроизводимости: результат stack нельзя приписывать martingale-логике, которая не участвовала в расчёте опубликованных метрик.
Tree parity и статическая проверка
Все пять новых V5-моделей проверены flat evaluator против CatBoost. Максимальная ошибка raw prediction равна 0.0 для stack_member_1, stack_member_2, stack_member_3, probability calibrator и lot calibrator.
Дополнительная static validation подтвердила баланс фигурных, круглых и квадратных скобок, отсутствие duplicate function names, отсутствие ONNX references, корректные индексы признаков и выключенный grid по умолчанию.
При этом компиляция в MetaEditor не выполнялась. Поэтому статическую проверку нельзя выдавать за «0 errors, 0 warnings». Перед публикацией исходный .mq5 необходимо открыть в MetaEditor, применить MetaQuotes Style через Ctrl+<, скомпилировать и заменить листинги в статье из окончательно отформатированного исходника.
Форвардный бэктест итогового советника в Strategy Tester

До этого момента результаты V5 относились преимущественно к исследовательскому candidate/trade simulation. После экспорта деревьев появился следующий уровень проверки: запуск уже готового многовалютного советника MIDAS_SINERGY_V5_CROSSFIT_STACK_SAFE_GRID непосредственно в Тестере стратегий.

Здесь важно разделить два интервала. Отчёт тестера охватывает 01.01.2025–09.08.2026, но финальные deploy-members V5 обучены только на данных до конца 2025 года. Поэтому весь интервал нельзя называть независимым форвардом. Участок 2025 года используется как проверка интеграции и исполнения, а строгим post-training forward в этом отчёте является только 2026 год.
Параметры терминального теста
| Параметр | Значение |
|---|---|
| Сервер | RoboForex-ECN, Build 6090 |
| Период отчёта | 01.01.2025 - 09.08.2026 |
| Рабочий таймфрейм | H1 |
| Профиль | V5_AUDIT_ROBUST |
| Торгуемых символов | 9 символов |
| Риск начальной позиции | 0.20% equity |
| Выход | MIDAS_LIVE_ATR_PROTECTED |
| Максимальное удержание | 24 H1-бара |
| Calibrated lots | включены, диапазон 0.35-1.25 |
| Soft Grid | включён |
| Максимум grid-уровней | 3 уровня |
| Шаг grid | 0.90 ATR |
| Максимальный basket risk | 0.45% equity |
| Минимальная probability для grid | 0.58 probability |
| Начальный депозит | 100 000 USD |
| Кредитное плечо | 1:500 |
Хотя тестер был запущен с EURUSD как основным символом, торговля непосредственно по EURUSD была отключена параметром InpTradeEURUSD=false. Советник работал как многовалютный: в отчёте зафиксировано девять реально использованных символов.
Ещё одна особенность этого запуска — InpMaximumStackUncertainty=99.0. Такое значение практически отключает фильтрацию по disagreement. Поэтому данный тест проверяет прежде всего probability gate, calibrated position sizing, ATR-защиту и Soft Grid, но не является строгой проверкой ограничительного uncertainty gate.
Результат полного терминального теста 2025-2026
| Метрика | Значение |
|---|---|
| History Quality | 100% |
| Bars | 9 918 |
| Ticks | 2 296 804 |
| Total Trades | 1 090 |
| Total Net Profit | 20 205.42 USD |
| Gross Profit | 58 616.06 USD |
| Gross Loss | -38 410.64 USD |
| Profit Factor | 1.53 |
| Expected Payoff | 18.54 USD |
| Sharpe Ratio | 3.20 |
| Recovery Factor | 6.49 |
| Balance Drawdown Maximal | 2 196.23 USD (1.87%) |
| Equity Drawdown Maximal | 3 113.83 USD (2.65%) |
| Profit Trades | 668 (61.28%) |
| Loss Trades | 422 (38.72%) |
Баланс вырос со 100 000 до 120 205.42 USD, то есть примерно на 20.21%. При этом максимальная equity-просадка составила 2.65%. На всём тестовом интервале отношение итоговой прибыли к максимальной equity-просадке соответствует Recovery Factor 6.49.
Структура сделок заметно асимметрична. Советник выполнил 942 long-сделки и 148 short-сделок. Доля прибыльных long составила 59,98%, short — 69,59%. Среднее время удержания позиции равно 22:52:01, минимальное - один час, максимальное - 61:11:00.
Последняя величина особенно важна. Реальный терминальный execution contract уже не идентичен упрощённому исследовательскому правилу «ровно 24 H1-бара». В тесте используются ATR-protected exits и Soft Grid, поэтому фактическая жизнь корзины может отличаться от первоначального фиксированного горизонта.
Строгий forward после заморозки deploy-моделей
Для оценки участка, который финальные модели уже не могли видеть во время обучения, таблица Deals была отдельно пересчитана начиная с 1 января 2026 года. Последний баланс 2025 года составлял 113 078.06 USD. К завершению теста он вырос до 120 205.42 USD.
| Метрика 2026 forward | Значение |
|---|---|
| Начальный баланс | 113 078.06 USD |
| Конечный баланс | 120 205.42 USD |
| Net | +7 127.36 USD |
| Доходность относительно баланса на 01.01.2026 | +6.30% |
| Закрытых сделок | 424 |
| Gross Profit | 22 052.65 USD |
| Gross Loss с учётом комиссий входа | -14 925.29 USD |
| Profit Factor | 1.478 |
| Max Balance Drawdown | 2 196.23 USD (1.87%) |
Таким образом, после границы финального обучения система не просто сохранила положительный баланс, но и осталась прибыльной при достаточно большом числе сделок. Profit Factor уменьшился с 1.53 на полном тесте до примерно 1.48 на строгом forward, однако остался выше единицы.
Для проверки того, не сформирован ли итог несколькими удачными сделками или одним месяцем, forward можно разложить по календарным месяцам.
| Месяц 2026 | Сделки | Net, USD | PF |
|---|---|---|---|
| Январь | 56 | +93.09 | 1.035 |
| Февраль | 52 | +2 509.44 | 2.535 |
| Март | 54 | +763.89 | 1.336 |
| Апрель | 56 | +801.72 | 1.436 |
| Май | 69 | -19.57 | 0.993 |
| Июнь | 78 | +1 447.13 | 1.778 |
| Июль | 45 | +1 214.85 | 1.745 |
| Август до окончания теста | 14 | +316.81 | 1.886 |
Картина по месяцам важнее самого итогового Net. Семь из восьми календарных отрезков завершились положительно. Единственным отрицательным месяцем оказался май, причём результат -19.57 USD практически нейтрален относительно размера счёта. Самый сильный вклад дал февраль: +2 509.44 USD при PF 2.535.
При этом результат нельзя напрямую сравнивать с таблицей deploy audit из предыдущего раздела. Там использовалась исследовательская симуляция с другим execution contract и без Soft Grid. Здесь тестируется готовый MQL5-советник с ATR-protected exits, динамическим lot multiplier и включённым Soft Grid. Следовательно, разница между +221.4 pips исследовательского audit и +7 127.36 USD терминального forward не является улучшением одной и той же метрики - это два разных эксперимента.

Особенно важно, что в этом запуске Soft Grid действительно активен. В исходной части статьи он был отключён и не участвовал в опубликованных исторических результатах. Поэтому новый тест впервые проверяет совместную систему Cross-Fitted Stack + calibrated risk + ATR protection + ограниченный Soft Grid.
Но из этого следует и ограничение интерпретации. По одному отчёту нельзя определить, какую долю результата создал сам stack, а какую - ATR-выходы или grid additions. Для строгой абляции необходим второй тест на том же периоде и тех же котировках с InpUseSoftGrid=false. Только разница этих двух запусков позволит отдельно оценить добавочную стоимость grid-слоя.
Также отчёт содержит History Quality 100% и 2 296 804 обработанных ticks, но сам HTML-файл не указывает название режима моделирования. Поэтому здесь корректно говорить о подтверждённом тесте Strategy Tester с 100%-ным качеством доступной истории, но не следует автоматически подписывать его как "Every tick based on real ticks", пока это отдельно не подтверждено настройками тестера или скриншотом.
Итог терминальной проверки. После заморозки V5 deploy-моделей советник сохранил положительный результат на 2026 году: +7 127.36 USD, около +6.30% к начальному балансу forward-периода, 424 закрытые сделки, PF около 1.48 и максимальная balance-просадка 1.87%. Это уже значительно более сильная проверка, чем offline candidate simulation, поскольку здесь проходят полный MQL5 inference, portfolio logic, расчёт объёма, комиссии, swap, ATR-exit и многовалютное исполнение. Однако это всё ещё исторический тест, а не live-forward. Включённый Soft Grid требует отдельной no-grid абляции.
Как воспроизвести результат
- Распаковать MQL5.zip в каталог данных MetaTrader 5.
- Открыть MQL5/Experts/MIDAS_V5/MIDAS_SINERGY_V5_CROSSFIT_STACK_SAFE_GRID.mq5 в MetaEditor.
- Применить MetaQuotes Style и скомпилировать исходник.
- Добавить требуемые валютные пары в Market Watch или указать брокерские prefix/suffix.
- Для воспроизведения исследовательской логики оставить InpUseSoftGrid=false и InpExitMode=MIDAS_AUDIT_HOLD24.
- Выбрать профиль. Production default исходного пакета - V4_MAX_NET_PF_IMMUTABLE; V5_AUDIT_ROBUST используется для отдельной проверки stack.
- Сопоставить журнал и сделки терминала с OOF/audit CSV, не изменяя thresholds после просмотра 2026.
//+------------------------------------------------------------------+ //| Инициализирует символы, состояние риска и секундный таймер | //+------------------------------------------------------------------+ int OnInit() { if(InpTimerSeconds<1) return(INIT_PARAMETERS_INCORRECT); g_equity_peak=AccountInfoDouble(ACCOUNT_EQUITY); //--- разрешаем брокерские символы и восстанавливаем grid-состояние for(int i=0;i<SYMBOL_COUNT;i++) { g_symbols[i]=InpSymbolPrefix+G_BASE_SYMBOLS[i]+InpSymbolSuffix; g_enabled[i]=EnabledInput(i); g_last_bar[i]=0; LoadGridState(i); if(g_enabled[i] && !SymbolSelect(g_symbols[i],true)) g_enabled[i]=false; } EventSetTimer(InpTimerSeconds); Print("MIDAS-SINERGY V5 Cross-Fitted Stack + Safe Grid initialized"); return(INIT_SUCCEEDED); }
Строка журнала при успешной инициализации остаётся на английском, как требует единый программный интерфейс: MIDAS-SINERGY V5 Cross-Fitted Stack + Safe Grid initialized.
Ограничения
- Метрики разделов 11-13 относятся к historical candidate/trade simulation; отдельный подтверждённый Strategy Tester report рассмотрен в разделе 16.
- Отдельный журнал компиляции MetaEditor и явное подтверждение режима "Every tick based on real ticks" в приложенном Strategy Tester report отсутствуют.
- 2026 audit содержит только семь месяцев.
- OOF уменьшает leakage, но не устраняет model-selection risk полностью.
- Пороговые значения остаются результатом исторического выбора.
- Полная probability calibration ухудшила AUC, Brier и LogLoss в 2026.
- Selective и Ultra Selective не открыли сделок в 2026 audit.
- Soft Grid участвовал в терминальном тесте раздела 16, но для оценки его отдельного вклада требуется повторный no-grid тест на тех же данных.
- Exact tree parity доказывает совпадение инференса только при одинаковом входном векторе; она не моделирует spread, slippage и broker execution.
Заключение
После реализации разработчик получает воспроизводимый второй уровень торговой ML-системы: 39-полевой stack contract, три heterogeneous CatBoost members, disagreement, отдельную probability calibration, отдельную ML-регрессию размера позиции и embedded evaluator для 31 модели в MQL5. OOF-прогнозы формируются через purged expanding walk-forward с 72-часовым embargo, а deploy-members замораживаются до начала audit 2026.
Эксперимент не показывает, что более сложная версия автоматически лучше. Immutable V4 остаётся сильнее по общей прибыли на 2020-2026, а некоторые V5-профили улучшают PF и просадку ценой меньшей активности. На отдельном 2026 audit только Audit Robust сохранил положительный Net, тогда как полная калибровка вероятности ухудшила прогнозные метрики.
Практический результат статьи состоит не в обещании прибыли, а в разделении трёх проверяемых задач: отсутствие leakage при stacking, frozen policy перед audit и точный перенос деревьев в терминал. Следующий обязательный шаг перед реальным использованием - локальная компиляция MetaEditor, тест "Every tick based on real ticks" и новый forward после 31 июля 2026 года без изменения уже замороженных моделей и thresholds.
Приложенные файлы
Публикационный архив собран в минимальном воспроизводимом виде. Для работы готового советника требуется только основной файл .mq5: все 31 модели уже встроены в него. Готовые CSV с прогнозами и сделками в архив не включаются. При необходимости читатель может экспортировать собственную историю из MetaTrader 5 и заново получить промежуточные наборы данных, OOF-прогнозы, отчёты и экспортированный советник с помощью приложенного Python-pipeline.
| Имя файла | Тип файла | Описание |
|---|---|---|
| MQL5/Experts/MIDAS_V5/MIDAS_SINERGY_V5_CROSSFIT_STACK_SAFE_GRID.mq5 | Исходный код советника | Основной торговый модуль с 31 встроенной CatBoost-моделью. Для работы советника внешние CSV, JSON или Python-процесс не требуются. |
| MQL5/Scripts/MIDAS_V5/MIDAS_EXPORT_HISTORY.mq5 | MQL5 Script | Экспортирует историю используемых валютных пар из MetaTrader 5 для воспроизведения эксперимента на данных собственного брокера. |
| MQL5/Files/MIDAS_V5/reproduce/reproduce_v5.py | Python | Главная точка запуска воспроизводимого конвейера. Проверяет входные данные и последовательно запускает стадии V2, V3, V4 и V5. |
| MQL5/Files/MIDAS_V5/reproduce/pipeline/*.py | Python | Стадии подготовки кандидатов, walk-forward обучения, triangle-моделей, cross-fitted stack, калибровки, audit и экспорта деревьев в MQL5. |
| MQL5/Files/MIDAS_V5/reproduce/requirements.txt | TXT | Зависимости Python, необходимые для повторного построения исследовательского pipeline. |
| MQL5/Files/MIDAS_V5/feature_contract_stack.json | JSON | Фиксированный порядок 39 stack-признаков и контракт входов второго уровня. |
| MQL5/Profiles/Tester/MIDAS_V5/*.set | SET | Готовые профили Strategy Tester для V4 Immutable и основных режимов V5, включая отдельный экспериментальный профиль Soft Grid. |
| MQL5/Files/MIDAS_V5/reproduce/README_REPRODUCE_RU.txt | TXT | Пошаговая инструкция по экспорту собственной истории и повторному запуску полного V2→V5 pipeline. |
| MQL5.zip | Архив проекта | Минимальный публикационный пакет. После распаковки в каталог данных терминала сохраняет стандартную структуру MQL5. |
Литература
- Wolpert D. H. Stacked Generalization. Neural Networks, 1992.
- Huber P. J. Robust Estimation of a Location Parameter. Annals of Mathematical Statistics, 1964.
- Niculescu-Mizil A., Caruana R. Predicting Good Probabilities with Supervised Learning. ICML, 2005.
- Prokhorenkova L. et al. CatBoost: Unbiased Boosting with Categorical Features. NeurIPS, 2018.
- Lopez de Prado M. Advances in Financial Machine Learning. Wiley, 2018.
- Bergmeir C., Benitez J. M. On the Use of Cross-Validation for Time Series Predictor Evaluation. Information Sciences, 2012.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Машинное обучение и Data Science (Часть 48): Насколько важны трансформеры для трейдинга?
База данных — это просто (Часть 1): Легковесный ORM-фреймворк для MQL5 на базе SQLite
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования