English
preview
Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5

Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5

MetaTrader 5 — Тестер |
39 1
Duy Van Nguy
Duy Van Nguy

Введение

Бэктест, показавший 42 % чистой прибыли и профит-фактор 1,8, на бумаге выглядит просто великолепно. Но вот неприятная правда: этот результат зависит от траектории. Именно конкретная последовательность прибыльных и убыточных сделок в ваших исторических данных сформировала эту конкретную кривую эквити — и если бы те же сделки произошли в другом порядке, результат мог бы оказаться совершенно иным.

Мы наблюдали это воочию на примере советника, торгующего по тренду, на EURUSD M30. У стратегии было положительное математическое ожидание, приличная доля прибыльных сделок и профит-фактор чуть выше 1,6. Выглядело неплохо. Однако при проведении стресс-теста последовательности сделок мы обнаружили, что примерно в 12 % случаев альтернативный порядок сделок вызвал бы маржин-колл ещё до 80-й сделки — при истории в 200 сделок. Те же сделки. То же статистическое преимущество. Просто по-разному повезло с порядком сделок. Вот в чём заключается проблема, о которой никто не говорит на этапе бэктеста.

И всё хуже, чем кажется. Трейдер, анализирующий одну-единственную кривую эквити, не может с уверенностью сказать, является ли стратегия действительно робастной или ей просто повезло с последовательностью сделок. Честно говоря, большинство трейдеров на этом и останавливаются: один бэктест, результаты выглядят неплохо — и переходят к реальной торговле. Это ошибка, которая, как мы не раз убеждались, приводит к плачевным последствиям.

Специалисты по количественному управлению рисками занялись этой проблемой ещё несколько десятилетий назад с помощью моделирования методом Монте-Карло. Они генерируют тысячи сценариев типа «что, если» на основе одних и тех же данных, чтобы выявить распределение возможных исходов, а не только одну историческую траекторию. С точки зрения торговли: если есть N сделок и их фактический P&L, что было бы, если бы эти же сделки произошли в другом порядке? Критически важно: как часто стратегия нарушала бы порог катастрофической просадки до окончания выборки?

Скрипт MonteCarlo_RiskAssessor.mq5 считывает P&L по сделкам из CSV-файла и выполняет 1000 бутстрап-моделирований. Он отображает веерную диаграмму с несколькими процентилями непосредственно в MetaTrader с помощью CCanvas и экспортирует процентильные кривые в машиночитаемый выходной файл. Комиссии и проскальзывание можно добавить, чтобы провести стресс-тестирование результатов в реалистичных условиях исполнения. Внешние библиотеки не требуются.



Метод

Вот основная идея: бутстрап-передискретизация — самая простая и практически не требующая допущений форма метода Монте-Карло.

Имея набор исторических результатов сделок (значений P&L), мы рассматриваем каждую сделку как независимое наблюдение, взятое из некоторого неизвестного распределения. Для каждого прогона моделирования мы случайным образом выбираем N сделок с возвращением из исторической выборки, а затем накапливаем их в синтетическую кривую эквити, начиная с начального баланса. Повторяем это 1000 раз — и получаем 1000 правдоподобных траекторий эквити. Это не прогнозы будущего. Это реалистичный анализ того, насколько изменчивой могла бы быть траектория даже при том же базовом статистическом преимуществе.

Почему используется метод бутстрапа, а не параметрическая модель (например, подбор нормального распределения)? Потому что распределения фактического P&L не являются нормальными. Они имеют тяжёлые хвосты, часто асимметричны, а иногда и бимодальны. Стратегия возврата к среднему, применяемая на GOLD H1, может иметь распределение доходности, совершенно не похожее на колоколообразную кривую: в нём будет кластер небольших прибылей, несколько крупных убытков и, возможно, тяжёлый правый хвост из-за редких дней с сильным трендом, которые удалось идеально поймать. Подгонка нормального распределения к таким данным и выборка из него сглаживают именно те выбросы, которые наиболее важны для оценки просадки. Бутстрап не делает никаких предположений о форме распределения. Он выбирает наблюдения из того, что действительно произошло.

Пять процентильных кривых — 5-я, 25-я, 50-я, 75-я и 95-я — образуют веерную диаграмму, которая служит основой выходных данных. 50-й процентиль — это типичный результат. 5-й процентиль представляет собой сценарий «неблагоприятного развития событий»: 95 % симуляций завершились выше этого уровня. Ширина веера на любом конкретном шаге сделок является прямым визуальным показателем неопределённости результата. Широкий веер в начале серии означает, что стратегия чувствительна к эффектам последовательности сделок. Об этом стоит знать перед запуском в реальную торговлю.

На основе полного набора симуляций рассчитываются четыре основных показателя риска. Медианная максимальная просадка — типичная худшая просадка от пика до минимума. Стрессовая просадка (95-й процентиль) — просадка, превышаемая лишь в 5 % прогонов: это показатель стресс-теста, а не нижний предел. Value at Risk (5 %) (VaR) — сумма в долларах, находящаяся под риском на уровне 5-го процентиля конечной эквити, рассчитываемая как начальный баланс − P5_Final_Equity. А также вероятность разорения — доля симуляций, в которых максимальная просадка превышала заданный порог разорения (в нашем тестовом прогоне он был установлен на уровне 20 %).

Моделирование проскальзывания (функция № 3) добавляет к каждой сделке издержку, выбранную из диапазона [0, InpSlippageMax], плюс фиксированную комиссию; эта сумма вычитается из каждой выбранной сделки независимо от направления. Везде предполагается исполнение по худшей стороне цены — подход консервативный, но в этом-то и суть.



Реализация

Входные параметры и глобальные переменные

Прежде чем приступить к рассмотрению, стоит отметить: это скрипт, а не советник. Он запускается один раз по запросу — без потикового цикла и без сохранения состояния между запусками. Все вычисления выполняются за один вызов функции OnStart().

Блок входных параметров предоставляет доступ ко всем настраиваемым параметрам без необходимости вносить изменения в код. Ниже приведены объявления, а также глобальные массивы, которые переносят данные между вызовами функций:

//--- Core simulation inputs
input string   InpCSVFile         = "trades.csv";    // CSV file in MQL5\Files\
input int      InpSimulations     = 1000;             // Number of MC simulations
input double   InpInitialBalance  = 10000.0;          // Starting account balance ($)
input double   InpRuinThreshold   = 0.20;             // Ruin = 20% drawdown
//--- Feature #3: Commission & slippage stress-test
input bool     InpSlippageEnabled = false;
input double   InpCommission      = 2;                // Fixed commission per trade ($)
input double   InpSlippageMax     = 3;                // Max random slippage per trade ($)
//--- Feature #2: CSV export
input bool     InpExportCSV       = true;
input string   InpExportFile      = "mc_results.csv";
//--- Global data arrays
double g_Profits[];         // Trade P&L values from CSV
double g_FinalEquities[];   // Final equity per simulation
double g_MaxDrawdowns[];    // Max drawdown (%) per simulation
double g_AllCurves[];       // Flattened [sim × (tradeCount+1)] equity matrix
int    g_TradeCount = 0;
//--- Shared summary metrics
double g_p05Eq, g_p50Eq, g_p95Eq;
double g_p50DD, g_p95DD;
double g_varAmt, g_probRuin;

Массив g_AllCurves[] является ключевой структурой данных. Это развёрнутая в одномерное представление 2D-матрица, в которой хранится полная траектория эквити для каждой симуляции. Плоское хранение — в отличие от рваного массива — обеспечивает быстрый доступ к строкам: sim * curveLen + step. Когда проход отрисовки проходит по сотням шагов сделок, рисуя пять процентильных линий, такая индексная арифметика позволяет заметно сэкономить время на больших наборах данных.

Загрузка данных о сделках

Функция LoadTradesFromCSV() выполняет загрузку данных из CSV с поддержкой как формата с одним столбцом (только P&L), так и формата с двумя столбцами (дата, P&L). Вот полный код функции:

//+------------------------------------------------------------------+
//| Load trade P&L from CSV into g_Profits[]                         |
//+------------------------------------------------------------------+
bool LoadTradesFromCSV(const string fileName)
  {
   int handle = FileOpen(fileName, FILE_READ | FILE_CSV | FILE_ANSI, ',');
   if(handle == INVALID_HANDLE)
     {
      PrintFormat("ERROR: Cannot open '%s'. Verify file is in MQL5\\Files\\. Code: %d",
                  fileName, GetLastError());
      return false;
     }
   if(!FileIsEnding(handle))
      FileReadString(handle);  // skip header row
   double buffer[];
   int    count = 0;
   while(!FileIsEnding(handle))
     {
      string row = FileReadString(handle);
      StringTrimRight(row);
      StringTrimLeft(row);
      if(StringLen(row) == 0) continue;
      string cols[];
      int nCols = StringSplit(row, ',', cols);
      double profit = 0.0;
      if(nCols >= 2)
         profit = StringToDouble(cols[1]);
      else if(nCols == 1)
         profit = StringToDouble(cols[0]);
      ArrayResize(buffer, count + 1, 1000);
      buffer[count++] = profit;
     }
   FileClose(handle);
   if(count == 0) { Print("ERROR: No valid trade data found."); return false; }
   ArrayResize(g_Profits, count);
   ArrayCopy(g_Profits, buffer, 0, 0, count);
   g_TradeCount = count;
   return true;
  }

Вызов ArrayResize(..., 1000) заранее выделяет память блоками по 1000 элементов в ходе цикла чтения — это позволяет избежать затрат, связанных с перераспределением памяти для каждого элемента при работе с большими наборами данных. Практическое замечание: при экспорте CSV из Тестера стратегий столбец «Прибыль» часто находится под индексом 12, а не под индексом 1. Достаточно всего лишь одной строки кода, чтобы указать целевой столбец cols[12]. Нам приходилось делать это не раз при переходе с одного формата экспорта на другой.

Модуль бутстрап-моделирования

Именно здесь и происходит настоящая работа. Функция RunMonteCarloSimulation() запускает N независимых симуляций, каждая из которых выполняет случайную выборку из пула сделок с возвращением и накапливает траекторию эквити. Если режим включён, из каждой сделки вычитаются комиссия и проскальзывание:

//+------------------------------------------------------------------+
//| Bootstrap Monte Carlo engine                                     |
//| Feature #3: optionally subtract commission + random slippage     |
//+------------------------------------------------------------------+
void RunMonteCarloSimulation()
  {
   int curveLen = g_TradeCount + 1;
   ArrayResize(g_FinalEquities, InpSimulations);
   ArrayResize(g_MaxDrawdowns,  InpSimulations);
   ArrayResize(g_AllCurves,     InpSimulations * curveLen);
   double equity[];
   ArrayResize(equity, curveLen);
   MathSrand((int)TimeLocal());
   for(int sim = 0; sim < InpSimulations; sim++)
     {
      equity[0] = InpInitialBalance;
      for(int t = 0; t < g_TradeCount; t++)
        {
         int    rIdx = MathRand() % g_TradeCount;
         double pnl  = g_Profits[rIdx];
         //--- Feature #3: apply fixed commission + uniform-random slippage
         if(InpSlippageEnabled)
           {
            double slip = ((double)MathRand() / 32767.0) * InpSlippageMax;
            pnl -= (InpCommission + slip);
           }
         equity[t+1] = equity[t] + pnl;
        }
      g_FinalEquities[sim] = equity[g_TradeCount];
      g_MaxDrawdowns[sim]  = CalcMaxDrawdown(equity, curveLen);
      //--- Store full curve in flattened matrix
      ArrayCopy(g_AllCurves, equity, sim * curveLen, 0, curveLen);
     }
  }

Модель проскальзывания намеренно консервативна: из каждой сделки, независимо от её направления, вычитаются фиксированная комиссия и случайная величина из диапазона [0, InpSlippageMax]. На протяжении всего расчета принимается, что исполнение осуществляется по худшей стороне цены. Для GOLD в стратегиях, удерживающих позиции менее 4 часов, мы обнаружили, что такая консервативная постановка довольно хорошо соответствует реальным издержкам исполнения — по крайней мере, в наших тестах на ECN-счетах с типичными спредами 1,5–2 пипса.

Функция CalcMaxDrawdown() последовательно проходит по каждой траектории эквити, отслеживая текущий максимум:

//+------------------------------------------------------------------+
//| Calculate peak-to-trough max drawdown (returns %)                |
//+------------------------------------------------------------------+
double CalcMaxDrawdown(const double &eq[], int len)
  {
   double peak  = eq[0];
   double maxDD = 0.0;
   for(int i = 1; i < len; i++)
     {
      if(eq[i] > peak) peak = eq[i];
      if(peak > 0.0)
        {
         double dd = (peak - eq[i]) / peak;
         if(dd > maxDD) maxDD = dd;
        }
     }
   return maxDD * 100.0;  // return as percentage
  }

Расчёт метрик

Функция ComputeMetrics() сводит полный набор данных моделирования к семи числам. Вспомогательная функция Percentile() использует линейную интерполяцию на отсортированной копии — тот же метод, что и в функции percentile из NumPy и функции PERCENTILE.INC в Excel, поэтому результаты можно напрямую сравнивать при экспорте в Python или электронную таблицу:

//+------------------------------------------------------------------+
//| Percentile helper (linear interpolation on sorted copy)          |
//+------------------------------------------------------------------+
double Percentile(double &arr[], double pct)
  {
   int n = ArraySize(arr);
   if(n == 0) return 0.0;
   double tmp[];
   ArrayCopy(tmp, arr);
   ArraySort(tmp);
   double pos = pct * (n - 1);
   int    lo  = (int)MathFloor(pos);
   int    hi  = (int)MathCeil(pos);
   if(lo == hi) return tmp[lo];
   return tmp[lo] + (tmp[hi] - tmp[lo]) * (pos - lo);
  }
//+------------------------------------------------------------------+
//| Compute and cache all summary metrics into globals               |
//+------------------------------------------------------------------+
void ComputeMetrics()
  {
   g_p05Eq   = Percentile(g_FinalEquities, 0.05);
   g_p50Eq   = Percentile(g_FinalEquities, 0.50);
   g_p95Eq   = Percentile(g_FinalEquities, 0.95);
   g_p50DD   = Percentile(g_MaxDrawdowns,  0.50);
   g_p95DD   = Percentile(g_MaxDrawdowns,  0.95);
   g_varAmt  = InpInitialBalance - g_p05Eq;
   int ruinCount = 0;
   for(int i = 0; i < InpSimulations; i++)
      if(g_MaxDrawdowns[i] >= InpRuinThreshold * 100.0)
         ruinCount++;
   g_probRuin = (double)ruinCount / InpSimulations * 100.0;
  }

Результаты сохраняются в глобальных переменных, поэтому как функция DrawResultsOnChart(), так и ExportResultsToCSV() могут считывать их без повторного вычисления. PctAtStep() — это вспомогательная функция, которая извлекает процентиль по поперечному сечению на заданном шаге последовательности сделок: она собирает по одному значению из сохранённой кривой каждой симуляции, а затем вызывает Percentile() для этого среза. Это механизм, лежащий в основе веерной диаграммы.

Отрисовка графика

Функция DrawResultsOnChart() формирует визуальный вывод с помощью CCanvas — встроенного растрового холста платформы MetaTrader. Отрисовка выполняется последовательно в семь слоёв: заполненные процентильные полосы, линии границ процентилей, встроенные метки справа, опорная линия и сетка, заголовок, легенда и панель статистики.

Шкала оси Y определяется коридором 5–95-го процентилей по всем шагам сделок. Это гарантирует, что веерная диаграмма всегда полностью заполняет область графика, даже при большом разбросе возможных результатов:

//--- Y-scale: 5th–95th envelope across all steps
double yMin = DBL_MAX, yMax = -DBL_MAX;
for(int s = 0; s <= g_TradeCount; s++)
  {
   double lo = PctAtStep(s, 0.05);
   double hi = PctAtStep(s, 0.95);
   if(lo < yMin) yMin = lo;
   if(hi > yMax) yMax = hi;
  }
double yRange = (yMax > yMin) ? yMax - yMin : 1.0;

Преобразованием данных в пиксели занимаются два макроса:

#define XP(step) (cL + (int)((double)(step) / g_TradeCount * cW))
#define YP(val)  (cB - (int)(((val) - yMin) / yRange * cH))

Для внутреннего и внешнего диапазонов используется функция FillTriangle() с цветами с альфа-смешением: альфа равна 45 для широкого диапазона от 5-го до 95-го процентиля и 110 для более узкого межквартильного диапазона от 25-го до 75-го процентиля. Медианная кривая имеет толщину 3 пикселя. Деталь реализации: для встроенных подписей справа используется двухпроходный алгоритм предотвращения наложений. На первом проходе подписи сдвигаются вперёд, чтобы выдержать минимальный зазор; на втором — назад, чтобы уменьшить скученность. Без него подписи будут накладываться друг на друга всякий раз, когда кривые процентилей сходятся в конечной точке ряда.

Экспорт в CSV

Функция ExportResultsToCSV() создаёт выходной файл из трёх разделов: заголовка с метаданными, пошаговой таблицы процентилей (по одной строке на сделку, пять столбцов с процентилями) и блока сводных метрик. Файл записывается в режиме FILE_ANSI для обеспечения широкой совместимости с программами для работы с электронными таблицами.

//--- Section 2: percentile curves—one row per trade step
FileWriteString(h, "Step,P5_Equity,P25_Equity,P50_Equity,P75_Equity,P95_Equity\n");
for(int s = 0; s <= g_TradeCount; s++)
  {
   FileWriteString(h, StringFormat("%d,%.2f,%.2f,%.2f,%.2f,%.2f\n",
                   s,
                   PctAtStep(s, 0.05),
                   PctAtStep(s, 0.25),
                   PctAtStep(s, 0.50),
                   PctAtStep(s, 0.75),
                   PctAtStep(s, 0.95)));
  }

Экспортированный файл можно использовать в Excel или Python. К типичным сценариям применения относятся построение кривых процентилей (matplotlib), подгонка распределений к конечной эквити и расчёт скользящих коэффициентов Шарпа по моделируемым траекториям. На практике мы загружаем файл mc_results.csv в датафрейм pandas и быстро строим линейный график seaborn по столбцам с процентилями — на это уходит около трёх строк кода на Python, и в результате получается готовый к публикации график, не обязательный для работы в MQL5, но полезный для отчётности.



Как использовать скрипт

Прежде чем приступить к анализу результатов, полезно точно знать, как запустить скрипт и откуда берутся входные данные. В этом разделе рассматриваются оба вопроса.

Подготовка файла trades.csv

Скрипт считывает значения P&L по сделкам из CSV-файла, расположенного в каталоге MQL5\Files\. Существует два способа создать этот файл.

Вариант 1 — экспорт из тестера стратегий MetaTrader (рекомендуется). Это самый распространённый сценарий работы. После проведения бэктеста откройте панель «Тестер стратегий», перейдите на вкладку «Результаты», щелкните правой кнопкой мыши в любом месте списка сделок и выберите «Сохранить как отчет». Экспортированный файл будет содержать по одной строке на каждую закрытую сделку с несколькими столбцами. В полном экспорте из тестера стратегий столбец прибыли обычно находится под индексом 12 — в этом случае откройте файл и либо удалите остальные столбцы, оставив только значения прибыли, либо настройте функцию LoadTradesFromCSV() так, чтобы она напрямую обращалась к cols[12].

Скрипту нужен только столбец «Прибыль» — ни даты, ни номера тикета, ничего другого. Требуемый формат — одностолбцовый CSV-файл:

Profit
125.40
-87.20
203.75
...

Строка заголовка не является обязательной. Если она присутствует, загрузчик автоматически пропускает её.

Вариант 2 — создайте вручную на основе любого журнала сделок. Если ваша история торговли взята с реального счёта, сторонней платформы или из электронной таблицы, скопируйте столбец прибыли по закрытым сделкам в простой текстовый файл — по одному значению в строке: положительные значения для прибыльных сделок, отрицательные — для убыточных. Сохраните файл под именем trades.csv (или под любым другим именем — его можно настроить во входных параметрах скрипта). Вот и всё.

Запуск скрипта

Как только CSV-файл будет готов, рабочий процесс состоит из трёх шагов.

Шаг 1 — поместите файл в правильную папку. Скопируйте файл trades.csv в папку MQL5\Files\ на своём компьютере. В MetaTrader 5 эта папка доступна через меню «Файл» → «Открыть каталог данных» → «MQL5» → «Files». Скрипт может считывать файлы только из этого каталога.

Шаг 2 — прикрепите скрипт к любому графику. Перетащите MonteCarlo_RiskAssessor с панели «Навигатор» на любой открытый график — инструмент и таймфрейм не имеют значения, поскольку скрипт считывает данные из файла, а не из потока рыночных данных. Появится диалоговое окно входных параметров.

Шаг 3 — настройте параметры и нажмите «ОК». Настройки по умолчанию — разумная отправная точка для большинства стратегий.

Рис. 1. Диалоговое окно входных параметров скрипта в MetaTrader 5 — настройки по умолчанию

Нажмите «ОК». На типичной истории сделок скрипт выполняет 1000 симуляций за несколько секунд. На графике появляется панель с веерной диаграммой, и если включена опция InpExportCSV, файл mc_results.csv автоматически сохраняется в папке MQL5\Files\.

Стоит знать одну вещь: скрипт отображает панель в виде растрового объекта-метки. Если график закрыть и снова открыть, панель не сохранится — просто запустите скрипт заново. Это займет менее 10 секунд.



Результаты

Скрипт был протестирован на истории сделок, экспортированной из Тестера стратегий MetaTrader: трендовый советник на GOLD H1, 182 закрытые сделки за период с января по апрель 2026 года. Начальный баланс установлен на уровне 10 000 долларов, количество симуляций — 1000, порог разорения — просадка 20 %. В данном прогоне моделирование проскальзывания было отключено.

Рис. 2. Полная веерная диаграмма Монте-Карло: 1000 симуляций, GOLD H1

На панели статистики были представлены следующие данные:

Рис. 3. Вывод журнала эксперта MetaTrader 5



Интерпретация

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

Ширина веера на каждом конкретном шаге по сделкам — основной сигнал. Узкий «веер» означает, что независимо от последовательности сделок результаты сходятся: статистическое преимущество достаточно стабильно, поэтому зависимость от траектории имеет меньшее значение. Широкий «веер», особенно если он быстро расходится в первые 30–40 сделок, — это предупреждение: результат стратегии сильно зависит от порядка сделок. Это может означать, что серия ранних убытков опасна даже в том случае, если общее математическое ожидание остается положительным. Мы видели, как счета разорялись в течение первых 60 сделок системы из 200 сделок, хотя в историческом бэктесте она показывала вполне приемлемую итоговую эквити. Веерная диаграмма показала бы это.

Наклон медианной кривой по отношению к линии начального баланса дает представление об ожидаемом дрейфе. В ходе нашего тестового прогона медианная кривая поднимается с 10 000 до 13 654,00 доллара за 182 сделки — это прирост на 36,5 % в типичном сценарии. Это явный положительный наклон, подтверждающий, что статистическое преимущество сохраняется в большинстве смоделированных траекторий. Если медиана едва превышает начальный баланс, статистическое преимущество невелико — возможно, слишком невелико, чтобы покрыть реальные издержки исполнения, которые не были учтены в модели.

Означает ли вероятность разорения на уровне 25,4 %, что стратегия небезопасна? Это зависит от того, как это интерпретировать. Это означает, что почти в каждой четвертой смоделированной последовательности сделок просадка в какой-то момент достигала порога разорения в 20 %. Инструмент не зря выделяет это красным — и, честно говоря, показатель в 25,4 % стоит принять всерьез перед переходом к реальной торговле. В рамках допущений данного моделирования — независимые и одинаково распределенные сделки, фиксированные размеры лотов, отсутствие смены режимов — этот показатель говорит о том, что стратегия существенно чувствительна к порядку сделок, особенно в первых сделках серии. Это полезный сигнал, а не окончательный вердикт.

Value at Risk (VaR), или стоимостная мера риска, переводит результат 5-го процентиля в денежное выражение. Здесь эта сумма составляет 648,65 доллара — то есть в худших 5 % смоделированных траекторий счет теряет не менее 648,65 доллара относительно начального баланса в 10 000 долларов. При балансе счета в 10 000 долларов с этим можно справиться. Но если увеличить размер лота в 3 раза, тот же VaR вырастет до 1946 долларов — эта цифра уже начинает казаться менее комфортной. Это напрямую влияет на принятие решений о размере позиции. Если показатель VaR вызывает опасения с учетом размера счета, решением проблемы является пропорциональное уменьшение размера лота, а не замена стратегии.

Стрессовая просадка (95-й процентиль) — это показатель, на котором следует основывать планирование рисков. Здесь этот показатель составляет 30,6 % — это вдвое больше медианной максимальной просадки, равной 15,1 %. В ходе наших тестов нескольких систем следования за трендом — на GOLD, EURUSD и NAS100 с таймфреймом H1 — просадка Монте-Карло на 95-м процентиле, как правило, в 1,5–2,5 раза превышает просадку, зафиксированную в исходной последовательности бэктеста. Этот показатель, равный 30,6 %, полностью укладывается в этот диапазон. Если реальный счёт не способен выдержать такую просадку, значит, стратегия использует чрезмерное кредитное плечо для данного размера счёта. И именно это — ключевой вывод, которого один бэктест никогда не даст.


Ограничения

Бутстрап-моделирование Монте-Карло — не хрустальный шар. Существуют реальные структурные ограничения, и мы их не скрываем.

Самая большая ловушка: независимость сделок. При бутстрап-передискретизации каждая сделка рассматривается как независимое наблюдение. В модели не учитывается серийная корреляция между последовательными результатами. На самом деле многие стратегии дают коррелированные результаты: выигрышные и проигрышные серии возникают кластерами, поскольку отражают базовые рыночные условия. Трендовая стратегия, испытывающая трудности в рваном боковом диапазоне, даст серию убыточных сделок, которые вовсе не независимы друг от друга. Метод бутстрапа случайным образом распределяет эти убытки по траекториям моделирования. Что в итоге? Веерная диаграмма может занижать риск краткосрочной просадки в неблагоприятных рыночных режимах. Насколько сильно? Это зависит от стратегии. Мы видели, что это имеет большое значение для систем возврата к среднему и меньшее — для многодневных свинг-стратегий.

Кроме того, этот инструмент не моделирует смену рыночных режимов. История сделок, охватывающая трендовый рынок 2022 года и рваный рынок 2023 года, смешивает эти режимы в единый пул. При бутстрап-выборке из такого пула любая комбинация двух типов режимов считается одинаково правдоподобной в любой момент времени, но рынки так не работают. В результате кривые эквити могут выглядеть более гладкими, чем были бы в реальности, если следующие 12 месяцев окажутся полностью в одном из этих режимов.

На протяжении всего расчёта предполагается стационарность размера лота. Каждая сделка вносит одинаковый вклад в пул P&L независимо от того, когда она была совершена. Стратегии, которые со временем наращивают размер позиции за счёт компаундинга или масштабирования, будут смешивать ранние сделки небольшим лотом с более поздними сделками крупным лотом. Модель не будет учитывать эту разницу — и итоговые кривые окажутся искажёнными.

Наконец, качество входных данных имеет большее значение, чем может показаться. Выборка из 50 сделок статистически слишком мала для стабильной оценки процентилей. Полосы будут заметно смещаться от прогона к прогону. 200 и более закрытых сделок — практический минимум для результатов, на которых имеет смысл основывать решения.



Расширения

Блочный бутстрап — первое улучшение, которое мы бы внедрили. Вместо отбора отдельных сделок блочный бутстрап выбирает непрерывные последовательности — например, группы из 5–10 сделок подряд, — что сохраняет краткосрочную серийную корреляцию. Это не решает проблему рыночных режимов полностью, но даёт распределения просадок, которые выглядят более реалистично для стратегий, где последовательные сделки обусловлены одними и теми же рыночными условиями. Для его реализации достаточно изменить один внутренний цикл в RunMonteCarloSimulation(). Остальной конвейер обработки остаётся без изменений.

Расширение для портфеля из нескольких стратегий было бы действительно полезным. Объедините истории сделок нескольких советников (EA), выполните коррелированную передискретизацию — одновременно выбирая из каждой истории сделку с одинаковым временным индексом — и выведите VaR и вероятность разорения на уровне портфеля. Сейчас вам придётся запускать скрипт отдельно для каждой стратегии и объединять результаты вручную. Выполнимо, но неудобно.

Walk-forward Monte Carlo — ещё одно направление: вместо объединения всех сделок в одну совокупность историю делят на скользящие окна и запускают отдельные симуляции для каждого окна. Полученный временной ряд риск-метрик показывает, оставался ли профиль риска стратегии стабильным с течением времени или дрейфовал. Такой сигнал дрейфа, на наш взгляд, — один из лучших опережающих индикаторов провала на данных вне выборки и полезнее, чем один только профит-фактор.

Для интеграции в рабочие фреймворки советников (EA) g_p95DD и g_probRuin можно записывать в общий файл или именованную глобальную переменную, которую советник считывает при запуске, что позволит ему самостоятельно корректировать размер лота, когда профиль риска по методу Монте-Карло превышает заданный порог. Механизм моделирования и слой рендеринга скрипта полностью разделены: можно убрать canvas и встроить вычисление метрик в процесс OnInit() любого советника (EA). Структура уже есть.

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

Полный исходный код приведен ниже. Перед запуском скрипта поместите файл trades.csv в каталог MQL5\Files\.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22291

Прикрепленные файлы |
trades.csv (1 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Antoine MERCIER
Antoine MERCIER | 6 мая 2026 в 12:38
Метод Монте-Карло — это ключевой инструмент. Без него не обойтись.
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейронная сеть на практике: График функции ReLU Нейронная сеть на практике: График функции ReLU
Мы создадим индикатор, полностью написанный на MQL5, для отображения на графике функции активации и её производной, взяв в качестве примера ReLU. Мы объясним влияние производных на выбор функции активации и необходимые меры предосторожности в недифференцируемых точках. Читатель сможет интерактивно визуализировать кривые и получит практическую основу, чтобы решать, когда стоит использовать те или иные функции активации, а когда — нет.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От начального к среднему уровню: Подокна (III) От начального к среднему уровню: Подокна (III)
В этом тексте подробно рассматривается использование подокон в индикаторах MQL5: их базовое создание, обнаружение экземпляров и предотвращение дублирования. В нём также рассматриваются INDICATOR_SHORTNAME, запрос сведений об окнах и индикаторах графика, а также различие между отображением на основном графике и в отдельном окне. Здесь также показано, как задать высоту и границы подокна, чтобы стандартизировать интерфейс и упростить размещение объектов.