English
preview
Когда закрывать позицию: Анализ выживаемости и модель конкурирующих рисков в дискретном времени на MQL5

Когда закрывать позицию: Анализ выживаемости и модель конкурирующих рисков в дискретном времени на MQL5

MetaTrader 5 — Статистика и анализ |
26 0
Adewumi Babatunde Gbadebo
Adewumi Babatunde Gbadebo

Введение

Большая часть усилий при разработке торговой системы уходит на определение момента входа в рынок. Мы оптимизируем фильтр, настраиваем глубину истории, спорим о подтверждении сигнала. Затем добавляем тейк-профит с отношением прибыли к риску два к одному и стоп-лосс на расстоянии одного ATR — и больше об этом не думаем. Такой выход по фиксированным правилам отбрасывает всю информацию, которую рынок даёт нам после входа в сделку.

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

В области медицинской статистики подобная задача была решена ещё несколько десятилетий назад. Открытая позиция — это пациент под наблюдением. Каждый бар, в течение которого позиция остаётся открытой, — это ещё один период, когда она находится под наблюдением и с возможностью наступления события. Позиция выбывает из исследования одним из двух взаимоисключающих способов: срабатывает тейк-профит или стоп-лосс. Это конкурирующие риски — возникновение одного из них окончательно исключает возникновение другого. Если раньше происходит закрытие по времени, наблюдение цензурируется.

Такая постановка вопроса устраняет две ошибки, допускаемые примитивным классификатором типа «принесет ли эта сделка прибыль?». Сделка, которая остаётся открытой к моменту закрытия по времени, представляет собой цензурированное наблюдение, а не ошибочный прогноз. При этом состояние сделки не является статичным — показатели MFE, MAE, расстояние до каждого барьера и режим волатильности меняются на протяжении всего срока действия позиции. Модель условной вероятности наступления события в дискретном времени естественным образом учитывает изменяющиеся во времени ковариаты; классификатор, выдающий прогноз лишь один раз при входе, этого не может.

Ниже приведена полная реализация на языке MQL5. В ней не используются ни DLL, ни среда выполнения ONNX, ни ALGLIB. Мы формируем таблицу «сделка — период» и оцениваем две условные вероятности событий определённого типа методом максимального правдоподобия с регуляризацией. Затем на основе накопленной вероятности события мы на каждом баре принимаем решение: удерживать позицию или закрыть её. Полученный подход сравнивается с выходами по фиксированным правилам на четырёх инструментах при одинаковых значениях всех параметров; в отчёте представлены в том числе и неудачные результаты.


Содержание

  1. Почему выход из позиции — задача анализа выживаемости
  2. Условные вероятности событий определённого типа и накопленная вероятность события
  3. Формирование таблицы «сделка — период»
  4. Обучение модели непосредственно средствами MQL5
  5. От накопленной вероятности события к решению о выходе из позиции
  6. Пограничные случаи и подводные камни
  7. Тестирование в Тестере стратегий
  8. Заключение


Почему выход из позиции — задача анализа выживаемости

Начните с величины, которую вы действительно хотите оценить. На баре t с момента открытия позиции: при условии, что она всё ещё открыта, и с учётом текущей картины на графике, какова вероятность достижения тейк-профита раньше стоп-лосса? Не «хорошая ли это сделка» — этот вопрос был решен при входе в рынок. Вопрос ставится при условии, что позиция до сих пор остаётся открытой.

Именно эту структуру — вероятность события при условии, что позиция до сих пор остаётся открытой, — и описывает функция риска. Условная вероятность события определённого типа j в дискретном времени — это вероятность того, что сделка будет закрыта по причине j в течение бара t, при условии, что она была открыта в начале бара t:

h j (t | x t ) = P( T = t, J = j | T ≥ t, x t )

где T — индекс бара, на котором произошел выход, J — причина (1 = тейк-профит, 2 = стоп-лосс), а x t — состояние сделки и рынка, зафиксированное в начале бара t.

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

Легко неверно понять, почему это задача о конкурирующих рисках, а не две независимые задачи анализа выживаемости. Цензурирование означает: «Мы прекратили наблюдение» — событие всё ещё могло произойти. Когда срабатывает стоп-лосс, срабатывание тейк-профита становится невозможным, а не просто ненаблюдаемым. Если считать достижение одного барьера цензурированием для другого, получится модель мира, в котором стоп-лоссов не существует, а оценки окажутся слишком оптимистичными.

Границы рассмотрения. В этой статье моделируются только выходы из торговых позиций. Код и параметры правила входа — пробой 20-барного канала Дончиана при условии повышенного ATR — остаются побайтово идентичными для всех инструментов и обоих вариантов тестирования. Это выбор, обусловленный особенностями эксперимента, а не рекомендация: как показывают результаты, сам по себе этот фильтр довольно слабый. Это правило используется здесь, чтобы изолировать влияние правил выхода — единственного аспекта, относительно которого в статье делаются выводы.


Условные вероятности событий определённого типа и накопленная вероятность события

Поскольку на каждом баре, пока позиция остаётся под наблюдением и с возможностью наступления события, реализуется ровно один из трёх исходов — срабатывание тейк-профита, срабатывание стоп-лосса или сохранение позиции открытой, — две условные вероятности событий определённого типа непосредственно задаются мультиномиальной логистической регрессией с «сохранением позиции открытой» в качестве опорной категории:

ηj(t) = βj' z(t, xt)

hj(t) = exp(ηj) / ( 1 + exp(η1) + exp(η2) )

Строка матрицы признаков имеет вид z(t, x t ) = [ 1, log t, t/T max , x 1 … x p ]. Эти два временных члена задают базовую функцию риска. Вероятность сохранения позиции открытой до конца бара t равна произведению вероятностей сохранения позиции открытой на каждом баре, а накопленная вероятность события типа j получается суммированием условных вероятностей наступления этого события, взвешенных по вероятности сохранения позиции открытой к началу соответствующего бара:

S(t) = ∏s=1..t [ 1 − h1(s) − h2(s) ]

Fj(t) = ∑s=1..t hj(s) · S(s−1)

Обратите внимание, что F j (t) не равно 1 − S(t). Ключевую роль играет множитель S(s−1): высокая условная вероятность наступления события на 60-м баре даёт почти нулевой вклад, если к этому моменту открытыми остаются лишь три процента сделок. Сумма F 1 + F 2 + S(T max ) равна 1. Это проверено на обученной модели для XAUUSD: равенство выполняется с точностью до 12 знаков после запятой.

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

Рис. 1. Структура состояний. Позиция движется по цепочке состояний «под наблюдением и с возможностью наступления события» по одному бару за раз; на каждом баре она либо поглощается одним из двух состояний выхода, либо остаётся открытой до следующего бара. Сделка, которая к моменту закрытия по времени всё ещё находится под наблюдением и с возможностью наступления события, считается цензурированной, а не убыточной; каждый бар, в течение которого она оставалась открытой, даёт одну строку таблицы для оценивания модели.


Построение таблицы «сделка — период»

В основе этого подхода лежит таблица «сделка — период»: по одной строке на каждую сделку и каждый бар, на котором она находится под наблюдением и с возможностью наступления события. Сделка, в которой стоп-лосс срабатывает через девять баров, даёт девять строк: восемь с исходом «позиция остаётся открытой» и одну с исходом «стоп-лосс». Отдельного члена для цензурирования нет: цензурированная сделка просто перестаёт добавлять строки.

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

Ковариата
Определение
Что отражает показатель
mfe_R
максимальное благоприятное отклонение (MFE) / риск
насколько далеко цена уже уходила в пользу сделки
mae_R
максимальное неблагоприятное отклонение (MAE) / риск
какую просадку перенесла позиция
cur_R
нереализованный результат в R
в каком состоянии позиция находится сейчас
dist_tp_atr
(тейк-профит − цена)/ATR(14)
оставшееся расстояние до тейк-профита в единицах ATR
dist_sl_atr
(цена − стоп-лосс)/ATR(14)
запас до достижения стоп-лосса
vol_ratio
ATR(14)/ATR(50)
рост волатильности с момента входа в позицию
range_pos
положение цены в диапазоне за 20 баров с учётом знака
продолжается ли прорыв
trend_align
(цена − EMA50)/ATR(14), со знаком
соответствие среднесрочной тенденции
park_ratio
Волатильность по Паркинсону за 20 баров / ATR(14)
фактический диапазон по сравнению с типовым диапазоном

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

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

//--- Returns the number of covariates actually written. The caller
//--- compares that with SURV_NUM_FEATURES - this is the contract test.
int CSurvivalFeatures::Build(const STradeState &st, const SMarketSnapshot &mk, double &x[])
  {
   ArrayResize(x, SURV_NUM_FEATURES);
   if(st.risk <= 0.0 || mk.atr14 <= 0.0)
      return(0);

   double cur_R = st.dir * (mk.close - st.entry) / st.risk;
   double rng   = MathMax(mk.hi20 - mk.lo20, _Point);
   int n = 0;
   x[n++] = st.mfe_R;
   x[n++] = st.mae_R;
   x[n++] = cur_R;
   x[n++] = st.dir * (st.tp - mk.close) / mk.atr14;
   x[n++] = st.dir * (mk.close - st.sl) / mk.atr14;
   x[n++] = mk.atr14 / mk.atr50;
   x[n++] = st.dir * (2.0 * (mk.close - mk.lo20) / rng - 1.0);
   x[n++] = st.dir * (mk.close - mk.ema50) / mk.atr14;
   x[n++] = mk.park20 / mk.atr14;

   for(int k = 0; k < n; k++)
      x[k] = MathMax(-SURV_CLIP, MathMin(SURV_CLIP, x[k]));
   return(n);
  }

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

И сборщик данных, и советник, работающий в реальном времени, считывают ковариаты через один общий объект CSeriesCache — это решение принято ради корректности, а не производительности. Раздельные реализации расчёта признаков при обучении и в рабочем режиме часто приводят к незаметным ошибкам в работе внедрённых моделей.

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

for(int t = 1; t <= m_tmax; t++)
  {
   int idx_obs = i - (t - 1);  // bar that closed before period t
   int idx_evt = i - t;        // bar in which period t plays out

   SMarketSnapshot mk;
   c.Snapshot(idx_obs, mk);
   st.bars_held = t - 1;
   CSurvivalFeatures::Build(st, mk, xs);

   //--- Resolve period t on the event bar.
   double hi = c.High(idx_evt), lo = c.Low(idx_evt);
   bool hit_tp = (dir > 0 ? hi >= st.tp : lo <= st.tp);
   bool hit_sl = (dir > 0 ? lo <= st.sl : hi >= st.sl);

   int y = SURV_EV_SURVIVE;
   if(hit_sl)
      y = SURV_EV_SL;
   else if(hit_tp)
      y = SURV_EV_TP;

В ходе фактического прогона на четырёх инструментах на таймфрейме M30 за период 01.01.2023–30.06.2025 было получено от 6 422 до 7 923 строк таблицы «сделка — период» на каждый инструмент на основе от 997 до 1 055 виртуальных сделок. Одновременно открыта только одна виртуальная сделка, поэтому перекрывающиеся по времени сделки не завышают эффективный размер выборки.


Оценивание параметров модели непосредственно средствами MQL5

Объедините блоки коэффициентов в θ = [β 1 ; β 2 ]. При девяти ковариатах размерность вектора признаков равна двенадцати, поэтому θ содержит 24 элемента, а информационная матрица имеет размер 24 × 24 — встроенный матричный тип позволяет обратить её за микросекунды:

gj = ∑i zi ( yij − pij ) − λ βj

A(j,k) = ∑i zi zi' ( pij δjk − pij pik ) + λ I

θ ← θ + A−1 g

Блок весов размером 2 × 2 для каждой строки представляет собой ковариационную матрицу мультиномиального распределения: p1(1-p1) и p2(1-p2) на диагонали, -p1*p2 — вне диагонали. Внедиагональные элементы отрицательны, поскольку события разных типов конкурируют за одну и ту же вероятностную массу.

double r1 = y1 - p1;
double r2 = y2 - p2;
double w11 = p1 * (1.0 - p1);
double w22 = p2 * (1.0 - p2);
double w12 = -p1 * p2;

for(int a = 0; a < d; a++)
  {
   g[a]     += z[a] * r1;
   g[d + a] += z[a] * r2;
  }

for(int a = 0; a < d; a++)
  {
   double za = z[a];
   if(za == 0.0)
      continue;
   for(int b = a; b < d; b++)
     {
      double v = za * z[b];
      A[(a) * P + (b)]         += v * w11;
      A[(a) * P + (d + b)]     += v * w12;
      A[(d + a) * P + (b)]     += v * w12;
      A[(d + a) * P + (d + b)] += v * w22;
      if(b > a)   // same four writes, a and b swapped
         MirrorBlock(A, a, b, d, P, v, w11, w12, w22);
     }
  }

Две детали обеспечивают численную устойчивость этого расчёта. При вычислении softmax вычитается текущее максимальное значение, поэтому большое значение линейного предиктора не приводит к переполнению. При этом каждый шаг проверяется по значению логарифма правдоподобия со штрафом; прежде чем принять шаг, его длину при необходимости уменьшают вдвое до десяти раз.

Свободные члены не включаются в L2-штраф: их стягивание к нулю сместило бы базовую функцию риска к значению одной трети на бар, что бессмысленно при частоте выходов около пяти процентов на бар. В приведённых здесь реальных запусках обучения сходимость каждый раз достигалась за семь–восемь итераций и менее чем за 100 мс:

Инструмент
Сделки
Строки
Соотношение TP / SL
Среднее число периодов под наблюдением и с возможностью наступления события
Строки обучающей выборки (60 %)
Логарифмическая функция потерь на отложенной выборке
Базовая модель только с временными признаками
Улучшение
EURUSD
1 055
7 777
30.7 % / 69.2 %
7.4
4 971
0.3792
0.4889
22.4 %
GBPUSD
1 055
7 923
31.4 % / 68.5 %
7.5
5 077
0.3888
0.4876
20.3 %
USDJPY
997
6 422
33.2 % / 66.7 %
6.4
3 979
0.4180
0.5372
22.2 %
XAUUSD
1 015
6 664
33.0 % / 67.0 %
6.6
4 210
0.4195
0.5304
20.9 %

На всех четырёх инструментах — трёх валютных парах и одном металле — модель с ковариатами превзошла базовую модель только с временными признаками на 20–22 % вне обучающей выборки, причём разбиение данных было строго хронологическим. Эта стабильность является наиболее убедительным эмпирическим результатом данной статьи. В разделах 6 и 7 честно рассматривается, что происходит, когда на основе модели формулируется торговое правило.

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

Кривые условных вероятностей наступления событий по модели, обученной на реальных данных XAUUSD: вероятности срабатывания тейк-профита и стоп-лосса резко снижаются уже с 1-го бара, а не достигают пика в середине окна.

Рис. 2. Условные вероятности событий определённого типа по модели, обученной на реальных данных XAUUSD, рассчитанные при средних значениях ковариат. Обе условные вероятности наступления событий резко снижаются уже с 1-го бара, а не достигают пика в середине окна: большинство сделок, в которых цена достигает одного из барьеров, завершаются быстро, а в сделках, которые остаются открытыми дольше, цена всё чаще оказывается ограничена диапазоном. Условная вероятность срабатывания тейк-профита становится выше условной вероятности срабатывания стоп-лосса примерно на 34-м баре — просто потому, что условная вероятность срабатывания стоп-лосса снижается быстрее с более высокого начального уровня, а не потому, что первая растёт.

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


От накопленной вероятности события к решению о выходе

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

void CCompetingRisks::PredictCIF(const int t_start, const double &x[],
                                 double &cifTP, double &cifSL, double &survOpen) const
  {
   cifTP = 0.0;
   cifSL = 0.0;
   double S = 1.0;
   for(int s = MathMax(t_start, 1); s <= m_tmax; s++)
     {
      double h1, h2;
      Hazards(s, x, h1, h2);
      cifTP += h1 * S;
      cifSL += h2 * S;
      S     *= MathMax(0.0, 1.0 - h1 - h2);
     }
   survOpen = S;
  }

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

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

Рис. 3. Накопленные вероятности событий по модели, обученной на реальных данных XAUUSD, для двух контрастных иллюстративных профилей ковариат при одинаковом времени с момента открытия сделки. Согласно обученной модели, успешно развивающаяся сделка (большое MFE, малое MAE, текущая прибыль) закрывается по тейк-профиту в 86 % случаев; сделка с затяжным боковым движением цены и небольшим MFE закрывается по тейк-профиту лишь в 26 % случаев, а в 22 % случаев всё ещё остаётся открытой к моменту закрытия по времени.

Теперь решение сводится к простой арифметике. Достижение цели приносит R TP , срабатывание стоп-лосса по определению даёт убыток ровно в один R, а сделка, всё ещё открытая к моменту закрытия по времени, завершается с результатом, примерно равным её текущему результату в R:

E[hold] = π TP · R TP − π SL · 1.0 + π open · R now

E[close] = R now

Закрывайте позицию досрочно, если E[hold] < E[close] − δ. Без порога преимущества правило срабатывает всякий раз, когда два математических ожидания пересекаются из-за шума, и за это приходится платить спред.

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

double e_hold  = cifTP * g_rtp - cifSL * 1.0 + sOpen * cur_R;
double e_close = cur_R;

if(g_state.bars_held < InpMinHold)
   return;

//--- Rule 1: holding is no longer worth what closing is worth.
bool close_now = (e_hold < e_close - InpEdgeDelta);

//--- Rule 2: the trade has turned while still in profit.
if(!close_now && cur_R > 0.0 && h1 > 1e-9)
   close_now = ((h2 / h1) > InpHazardRatio);

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

Два пороговых параметра, InpEdgeDelta и InpHazardRatio, не подбирались заново для каждого инструмента — ради воспроизводимости их значения намеренно оставляли равными 0.02 и 2.5 во всех случаях. Этот выбор оказался важнее, чем ожидалось; мы обсудим его после того, как представим фактические результаты.

Текущее решение отображается на графике с помощью SurvivalPanel.mqh — небольшой растровой метки на базе CCanvas, которая представляет четыре числа, только что вычисленные функцией PredictCIF(), в виде, понятном с первого взгляда:

void CSurvivalPanel::Update(const bool has_pos, const int bars_held, const int tmax,
                            const double cur_R, const double cifTP, const double cifSL,
                            const double sOpen, const double e_hold, const double e_close,
                            const double h1, const double h2)
  {
   if(!m_ok)
      return;
   m_c.Erase(Argb(clrBlack, 190));

   //--- Stacked incidence bar: green width proportional to cifTP, red to cifSL.
   int bx = 10, by = 56, bw = m_w - 20, bh = 22;
   int wTP = (int)MathRound(bw * MathMax(cifTP, 0.0));
   int wSL = (int)MathRound(bw * MathMax(cifSL, 0.0));
   m_c.FillRectangle(bx, by, bx + wTP, by + bh, Argb(clrMediumSeaGreen));
   m_c.FillRectangle(bx + wTP, by, bx + wTP + wSL, by + bh, Argb(clrIndianRed));

   color ec = (e_hold >= e_close ? clrMediumSeaGreen : clrOrange);
   m_c.TextOut(10, 166, StringFormat("E[hold] %+.3f   E[close] %+.3f", e_hold, e_close), Argb(ec));
   m_c.Update();
  }

Особенно полезен цвет строки математического ожидания: зелёный, пока удержание позиции выгоднее закрытия, и оранжевый, как только соотношение меняется на противоположное. Панель создается только в том случае, если значения параметров InpShowPanel и MQL_VISUAL_MODE равны true. В противном случае Тестер стратегий не отобразит растровое изображение.


Пограничные случаи и подводные камни

Значение SYMBOL_TRADE_TICK_VALUE может быть неверным для пар с иеной в качестве валюты котировки. Изначально расчёт объёма позиции выполнялся непосредственно на основе отношения SYMBOL_TRADE_TICK_VALUE / SYMBOL_TRADE_TICK_SIZE. Для USDJPY у использованного здесь брокера это свойство возвращало стоимость тика 100.00 — примерно в 160 раз выше правильного значения. Объём позиции без каких-либо предупреждений округлялся до минимального разрешённого брокером значения 0.01 лота, и риск каждой сделки составлял лишь около 1/40 от запланированного 1 %. Ошибку обнаружили только потому, что средний убыток (2.41 доллара) был неправдоподобно мал по сравнению с остальными тремя инструментами (44–84 доллара). Проблему решает функция OrderCalcProfit(): она запрашивает у терминала прибыль или убыток гипотетической сделки, причём её расчёт гарантированно согласуется с расчётом механизма исполнения сделок:

double loss_for_1_lot = 0.0;
ENUM_ORDER_TYPE ot = (dir > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
if(!OrderCalcProfit(ot, _Symbol, 1.0, price, sl, loss_for_1_lot) || loss_for_1_lot >= 0.0)
  {
   PrintFormat("LotsForRisk: OrderCalcProfit failed - falling back to InpLots.");
   return(InpLots);
  }
double lots = (AccountInfoDouble(ACCOUNT_BALANCE) * InpRiskPercent / 100.0) / MathAbs(loss_for_1_lot);

Если вы рассчитываете объём позиции по заданному проценту риска для нескольких инструментов, один раз проверьте результат вручную: убедитесь, что отношение среднего убытка на сделку в отчёте Тестера стратегий к запланированному риску имеет разумную величину. Даже при незаметном занижении объёма позиции в 40 раз тест выполняется и формирует на первый взгляд правдоподобный отчёт.

Чтение CSV-файлов по полям оказалось ненадёжным, а построчное чтение — надёжным. В более ранней версии модуля чтения файлов SurvivalModelTrainer.mq5 для разбора CSV-файла поле за полем использовались функции FileReadNumber() и FileIsLineEnding(). При тестировании это приводило к отбрасыванию 60–86 % строк в зависимости от конкретного варианта реализации, а каждая попытка исправления лишь меняла характер повреждения данных, не устраняя проблему. Окончательное исправление внесено как в модуль записи, так и в модуль чтения. Модуль записи явно форматирует каждое поле перед вызовом FileWrite(). Модуль чтения разбирает строки целиком с помощью StringSplit(), а не полагается на режим чтения отдельных полей CSV. При таком подходе все наборы данных, использованные для таблицы результатов в разделе 7, загрузились без единой отклонённой строки.

//--- Each field is written as an explicitly self-formatted string, passed
//--- to FileWrite as its OWN argument. Separate arguments let FILE_CSV
//--- mode place the delimiter correctly - a single hand-joined string
//--- does not round-trip reliably. And pre-formatting with
//--- IntegerToString/DoubleToString - both always period-decimal
//--- regardless of the terminal's Windows region - denies MQL5 any
//--- chance to render 0.78 as "0,78" on a comma-decimal locale, which
//--- would silently split into two fields against the comma delimiter.
for(int i = 0; i < nrows; i++)
  {
   FileWrite(fh,
            IntegerToString(rows[i].trade_id),
            IntegerToString((long)rows[i].entry_time),
            IntegerToString(rows[i].t),
            IntegerToString(rows[i].y),
            DoubleToString(rows[i].x[0], 8), DoubleToString(rows[i].x[1], 8),
            DoubleToString(rows[i].x[2], 8), DoubleToString(rows[i].x[3], 8),
            DoubleToString(rows[i].x[4], 8), DoubleToString(rows[i].x[5], 8),
            DoubleToString(rows[i].x[6], 8), DoubleToString(rows[i].x[7], 8),
            DoubleToString(rows[i].x[8], 8));
  }

Количество столбцов в строке заголовка определяется на основе контракта признаков, а не задаётся числовой константой 13, поэтому изменение SURV_NUM_FEATURES отражается в файле уже при следующем запуске, вместо того чтобы незаметно записывать неверное число столбцов:

//--- Header. Column count is derived from the contract, never typed out.
string hdr = "trade_id,entry_time,t,y";
for(int k = 0; k < SURV_NUM_FEATURES; k++)
   hdr += "," + CSurvivalFeatures::Name(k);
FileWrite(fh, hdr);

Модель не переносится между символами. Коэффициенты задают зависимость вероятности достижения барьера от состояния конкретного рынка, а константы стандартизации — это средние значения и стандартные отклонения для соответствующего символа. Для каждого инструмента требуется собственный цикл сборки и обучения. Прежде чем доверять правилу выхода, проверьте для каждого инструмента улучшение на отложенной выборке по сравнению с базовой моделью только с временными признаками — здесь у всех четырёх инструментов оно превысило 20 %.

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

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

Изолированная среда Тестера стратегий. У каждого агента тестирования есть собственный каталог MQL5\Files, поэтому манифест модели, записанный локально, недоступен при тестировании на исторических данных. Оба файла нужно записывать в общую папку с помощью FILE_COMMON, иначе советник (EA) не сможет инициализироваться в Тестере стратегий, хотя на рабочем графике будет работать нормально.

Разделение в файле SurvivalModelTrainer.mq5 должно производиться по сделкам, а не по строкам. При случайном разбиении по строкам периоды одной и той же сделки окажутся в обеих выборках, а поскольку соседние периоды имеют почти одинаковые ковариаты и один и тот же конечный исход сделки, оценка на отложенной выборке фактически станет проверкой на уже знакомых данных. Вместо этого разбиение выполняется по идентификатору сделки: граница определяется как доля InpTrainShare от максимального обнаруженного идентификатора, а для обучения берётся начальная часть диапазона:

//--- Chronological split by trade, never by row. Splitting rows at
//--- random would put periods of the same trade on both sides and
//--- leak the outcome straight into the holdout.
int max_id = 0;
for(int i = 0; i < g_rows; i++)
   max_id = MathMax(max_id, g_ID[i]);
int cut_id = (int)MathRound(max_id * InpTrainShare);

int ntr = 0, nho = 0;
for(int i = 0; i < g_rows; i++)
  {
   if(g_ID[i] <= cut_id)
      ntr++;
   else
      nho++;
  }

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

Сам модуль чтения — вторая часть описанного выше исправления обработки CSV: строки считываются как обычный текст и разбиваются с помощью StringSplit(), а не читаются по полям в режиме CSV; при этом проверяются столбцы заголовка и ведётся счётчик отклонённых строк, чтобы некорректный файл вызывал явную ошибку, а не приводил к незаметному обучению на меньшем числе строк, чем положено:

while(!FileIsEnding(fh))
  {
   string line = FileReadString(fh);
   if(StringLen(line) == 0)
      continue;                            // blank line (e.g. trailing EOF line)

   string parts[];
   int n = StringSplit(line, ',', parts);

   if(n != ncol)
     {
      rej_total++;
      continue;
     }

   int    id = (int)StringToInteger(parts[0]);
   int    t  = (int)StringToInteger(parts[2]);
   int    y  = (int)StringToInteger(parts[3]);

   bool bad = (t <= 0 || y < 0 || y > 2);
   for(int k = 0; k < SURV_NUM_FEATURES; k++)
     {
      double v = StringToDouble(parts[4 + k]);
      if(!MathIsValidNumber(v))
         bad = true;
      g_X[g_rows * SURV_NUM_FEATURES + k] = v;
     }
  }

Все наборы данных, использованные для таблицы в разделе 7, были загружены через этот модуль чтения без единой отклонённой строки; это подтверждено сверкой с числом строк, о котором SurvivalDatasetBuilder.mq5 сообщал при записи.


Тестирование в Тестере стратегий

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

Один инструмент ничего не доказывает, поэтому тестирование по этому протоколу проводится на четырёх инструментах, представляющих два типа рынков: трёх основных валютных парах и одном металле; все приведённые ниже входные параметры при этом одинаковы.

настройка
Значение
Инструменты
EURUSD, GBPUSD, USDJPY, XAUUSD — все на таймфрейме M30
Вход (оба варианта тестирования)
Пробой канала Дончиана за 20 баров, ATR(14) выше своей медианы за 100 баров
Стоп-лосс / тейк-профит / закрытие по времени
1.0 × ATR(14) / 2.0 R / 96 баров
Период данных (модуль формирования набора данных)
01.01.2023 – 30.06.2025, параметры InpFrom и InpTo одинаковы для всех символов
Разделение на обучающую и отложенную выборки
InpTrainShare = 0.60, в хронологическом порядке по идентификатору сделки
Коэффициент L2-штрафа λ / число итераций / допуск
1.0 / 25 / 1e-7
Диапазон дат в «Тестере стратегий»
01.07.2024 – 30.06.2025 (только период отложенной выборки)
Моделирование
OHLC на M1
Расчёт объёма позиции
Риск 1% от баланса на сделку с расчётом через OrderCalcProfit, одинаковый для всех инструментов
Порог преимущества δ / отношение условных вероятностей событий / минимальный срок удержания
0.02 / 2.5 / 2 бара, одинаковые для всех инструментов
Депозит
10 000 долларов США

Диапазон дат в Тестере стратегий намеренно ограничен только периодом отложенной выборки — если включить в бэктест и обучающие данные, это уже не будет проверкой вне обучающей выборки. Временное окно формирования данных, разбиение на выборки при обучении и диапазон дат тестирования должны быть согласованы между собой. На рис. 4 показаны две проверки калибровки вероятностей по фактическим запускам, результаты которых приведены в таблице ниже:

Калибровка вероятностей по децилям на реальной отложенной выборке (тейк-профит по EURUSD, стоп-лосс по USDJPY): прогнозируемые вероятности близки к наблюдаемым частотам событий.

Рис. 4. Калибровка вероятностей по децилям на отложенной выборке для двух из четырёх фактически обученных моделей, с 95%-ными доверительными интервалами. Точки на диагонали означают, что прогнозируемым вероятностям можно доверять. На обеих диаграммах кривые проходят близко к диагонали вплоть до верхних децилей — так с точки зрения калибровки вероятностей выглядит снижение логарифмической функции потерь на 20–22 %, показанное в таблице выше.

Инструмент
Выходы
Сделки
Чистая прибыль
Профит-фактор
Матожидание выигрыша
Максимальная просадка
Процент выигрышных сделок
Коэффициент Шарпа
EURUSD
выходы по фиксированным правилам
464
-6 841.94
0.64
-14.75
70.28 %
25.22 %
-5.00
EURUSD
выходы под управлением модели выживаемости
508
-6 212.69
0.59
-12.23
62.49 %
32.87 %
-5.00
GBPUSD
выходы по фиксированным правилам
467
-3 995.86
0.85
-8.56
46.51 %
29.98 %
-3.68
GBPUSD
выходы под управлением модели выживаемости
515
-3 401.17
0.84
-6.60
40.16 %
35.53 %
-5.00
USDJPY
выходы по фиксированным правилам
452
-3 614.99
0.86
-8.00
41.67 %
30.31 %
-3.20
USDJPY
выходы под управлением модели выживаемости
507
-4 293.61
0.77
-8.47
46.74 %
32.94 %
-5.00
XAUUSD
выходы по фиксированным правилам
469
-1 379.40
0.95
-2.94
30.23 %
32.62 %
-1.09
XAUUSD
выходы под управлением модели выживаемости
507
-3 012.58
0.83
-5.94
35.33 %
33.33 %
-4.56
Все четыре инструмента, выходы по фиксированным правилам
—
1 852
-15 832.19
0.84
—
47.2 % в среднем
29.5 %
-3.24 в среднем
Все четыре инструмента, выходы под управлением модели выживаемости
—
2 037
-16 920.05
0.77
—
46.2 % в среднем
33.7 %
-4.89 в среднем

Столбчатая диаграмма, сравнивающая фактическую чистую прибыль, просадку и профит-фактор по всем четырём инструментам при выходах по фиксированным правилам и выходах под управлением модели выживаемости.

Рис. 5. Чистая прибыль, максимальная просадка и профит-фактор: сравнение выходов по фиксированным правилам с выходами под управлением модели выживаемости по всем четырём инструментам на основе фактических результатов Тестера стратегий. Ни один показатель не улучшается на всех инструментах; при выходах под управлением модели выживаемости профит-фактор хуже на всех четырёх.

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

Вывод касается сравнения двух правил выхода из позиций и не подтверждает простой тезис «модель помогает». При выходах под управлением модели выживаемости профит-фактор хуже на всех четырёх инструментах, а коэффициент Шарпа — на трёх из четырёх. Доля прибыльных сделок растёт на всех инструментах: выходы под управлением модели выживаемости позволяют досрочно закрыть часть сделок, которые иначе завершились бы убытком; при этом сделки становятся короче и совершаются чаще. Рост доли прибыльных сделок ценой ухудшения профит-фактора — не очевидно выгодный компромисс.

По изменению чистой прибыли и просадки инструменты чётко делятся на две группы в зависимости от того, насколько плохи были исходные результаты варианта с выходами по фиксированным правилам. На EURUSD и GBPUSD, где профит-факторы варианта тестирования с выходами по фиксированным правилам были худшими — 0.64 и 0.85, — выходы под управлением модели выживаемости сократили убыток в долларах и просадку. На USDJPY и XAUUSD, где профит-факторы варианта тестирования с выходами по фиксированным правилам были лучшими — 0.86 и 0.95, — выходы под управлением модели выживаемости увеличили и убыток, и просадку, а на XAUUSD убыток вырос более чем вдвое. В совокупности выходы под управлением модели выживаемости дали больший денежный убыток (16 920 долларов против 15 832) при худшем профит-факторе (0.77 против 0.84).

Корректность модели условных вероятностей наступления событий и эффективность правила принятия решений — два отдельных утверждения. Этот тест подтверждает корректность только модели. Все диагностические показатели в разделе 4 свидетельствуют о надёжной калибровке вероятностей модели: значения логарифмической функции потерь ниже на 20–22 % на каждом инструменте, а точки для децилей на рис. 4 располагаются вдоль диагонали. Между инструментами не переносятся одни и те же фиксированные значения параметров InpEdgeDelta и InpHazardRatio: порог преимущества, защищающий систему с большими убытками, может просто урезать потенциальную прибыль системы, уже близкой к безубыточности. В этом тесте пороговые значения оставались фиксированными ради воспроизводимости, и воспроизводимый результат таков: фиксированные пороги сами по себе не гарантируют улучшения результатов.

Именно так выглядит полезный реальный результат: не «всегда работает» и не «никогда не работает», а опровержимое утверждение — подход к вероятностному моделированию переносим между инструментами, а фиксированное правило принятия решений на его основе — нет; следующий тест должен показать, позволяет ли настройка порогов отдельно для каждого инструмента вернуть то, что было потеряно при использовании единого общего порога.


Заключение

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

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

Именно благодаря межинструментальному тестированию в данной статье выводы не являются просто предположениями, а подкрепляются фактическими данными. Четыре инструмента, одинаковая логика входа, расчёт объёма позиции по риску и пороги выхода — меняется только политика выхода. Модель условных вероятностей наступления событий показала стабильно хорошую калибровку вероятностей на всех инструментах — это реальный, воспроизводимый результат. А вот торговые результаты фиксированного правила принятия решений на основе этой модели не были столь же стабильными: оно улучшило результаты двух самых слабых стратегий с выходами по фиксированным правилам, ухудшило результаты двух самых сильных и в среднем по всем четырём инструментам принесло больше убытков, чем отсутствие дополнительных вмешательств.

Наиболее перспективный следующий шаг прямо следует из этого расхождения: подобрать InpEdgeDelta и InpHazardRatio отдельно для каждого инструмента, а не для всех сразу, используя уже реализованное в модуле обучения разбиение с выделением отложенной выборки, и проверить, позволит ли настройка вернуть то, что было потеряно из-за общего порога на USDJPY и XAUUSD. Второй подход заключается в замене фиксированного тейк-профита на тейк-профит, определяемый моделью.

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

Файл
Тип
Описание
TradeSurvivalExitEA.mq5
Советник (EA)
Входы по стратегии Дончиана с выбором между выходами по фиксированным правилам и выходами под управлением модели выживаемости. Загружает манифест модели для своего символа и таймфрейма, рассчитывает объём позиций с помощью OrderCalcProfit и на каждом новом баре оценивает каждую открытую позицию по обоим правилам принятия решений.
SurvivalFeatures.mqh
Включаемый файл
Контракт признаков: девять изменяющихся во времени ковариат, общий кэш временных рядов, из которого читают данные и сборщик данных, и советник в рабочем режиме, а также самопроверка, которую каждый компонент выполняет до начала работы с рынком.
CompetingRisks.mqh
Включаемый файл
Оценивание мультиномиальной логистической регрессии методом Ньютона — Рафсона, условные вероятности событий определённого типа, накопленные вероятности событий, а также чтение и запись манифеста модели с привязкой к символу и таймфрейму.
SurvivalHarvest.mqh
Включаемый файл
Общее правило входа, воспроизведение виртуальных сделок и развёртывание данных в формат «сделка — период», преобразующее исторические бары в таблицу для оценивания модели.
SurvivalPanel.mqh
Включаемый файл
Растровая панель на основе CCanvas, отображающая на графике текущие накопленные вероятности событий и сравнение удержания позиции с её закрытием; доступна только в режиме визуализации.
SurvivalDatasetBuilder.mq5
Скрипт
Воспроизводит сделки на интервале выборки и записывает файл survival_dataset_<SYMBOL>_<PERIOD>.csv в общую папку: каждому полю соответствует отдельный аргумент FileWrite, а десятичный разделитель при форматировании явно задаётся точкой независимо от региональных настроек Windows, используемых терминалом. Запускать один раз для каждого инструмента.
SurvivalModelTrainer.mq5
Скрипт
Считывает CSV как обычный текст с помощью StringSplit, обучает модель с хронологическим разбиением данных на уровне сделок, выводит значения логарифмической функции потерь и результаты калибровки вероятностей по децилям на отложенной выборке в сравнении с базовой моделью только с временными признаками и записывает файл survival_model_<SYMBOL>_<PERIOD>.mft. Запускать один раз для каждого инструмента.

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

Прикрепленные файлы |
MQL5.zip (28.53 KB)
Создание инструмента воспроизведения баров на MQL5 Создание инструмента воспроизведения баров на MQL5
В этой статье рассказывается, как создать на MQL5 для MetaTrader 5 интерактивный инструмент воспроизведения баров, который отображает исторические свечи по одной, не раскрывая будущих данных. Вы реализуете пользовательскую отрисовку свечей с помощью DRAW_COLOR_CANDLES, событийно-ориентированный механизм воспроизведения с обработчиками OnChartEvent и OnTimer, панель управления с кнопкой «Воспроизведение/Пауза», перетаскиваемый якорь воспроизведения и имитационную торговлю с кнопками «Buy» и «Sell» и линиями стоп-лосса (SL) и тейк-профита (TP). При этом активный бар будет оставаться в видимой области графика, чтобы вы могли отрабатывать навыки дискреционного анализа и исполнения сделок.
Преодоление проблем доступности в торговых инструментах на MQL5 (Часть V): Торговля с помощью жестов на основе компьютерного зрения Преодоление проблем доступности в торговых инструментах на MQL5 (Часть V): Торговля с помощью жестов на основе компьютерного зрения
В этой статье рассказывается, как организовать торговлю в MetaTrader 5 без использования мыши и клавиатуры, преобразуя жесты рук, отслеживаемые с помощью веб-камеры, в торговые команды MQL5. Мы рассмотрим архитектуру (MediaPipe/OpenCV на Python плюс советник MQL5), сопоставление жестов и действий, а также межпроцессное взаимодействие с помощью глобальных переменных или HTTP-опроса. Вы реализуете советник, выполните действия BUY/SELL/CLOSE и проверите задержку и надежность в режиме реального времени.
Создание объектно-ориентированного расчетного модуля сессионного VWAP на MQL5 Создание объектно-ориентированного расчетного модуля сессионного VWAP на MQL5
В этой статье показано, как реализовать сессионный VWAP на MQL5 в виде пригодного для повторного использования подключаемого класса, который строго сбрасывает накопленные значения в полночь по времени брокера. Расчетный модуль вычисляет VWAP и взвешенные по объему полосы отклонения только по закрытым барам и задает временную привязку накопления с помощью MqlDateTime, чтобы избежать искажений из-за отсутствующих свечей. Вспомогательный индикатор отображает базовую линию и полосы, а советник считывает сигналы один раз на бар, что обеспечивает согласованное выполнение, экономное использование ресурсов процессора и надежное тестирование.
Паттерн "Строитель" в MQL5: Формирование ордеров с помощью цепочек вызовов методов Паттерн "Строитель" в MQL5: Формирование ордеров с помощью цепочек вызовов методов
При ручном заполнении структуры MqlTradeRequest правила согласованности полей остаются непроверенными, что приводит к скрытым ошибкам конфигурации, проявляющимся во время выполнения. COrderBuilder с плавным интерфейсом (Fluent Interface) для MQL5 поддерживает цепочку вызовов методов с использованием указателей, проверку каждого поля и проверку расположения SL/TP с учетом направления сделки и ограничений брокера по уровню стопов. Метод Send() выполняет четыре этапа: проверку установки всех необходимых флагов, проверку согласованности полей, OrderCheck(), а затем OrderSend(), благодаря чему ошибки конфигурации выявляются на ранней стадии, а код отправки ордеров остается понятным и пригодным для повторного использования.