Архитектура машинного обучения для MetaTrader 5 (Часть 15): Как калибровать уровни тейк-профита и стоп-лосса по синтетическим данным
Оглавление
- Введение
- Ловушка калибровки
- Процесс O-U: модель движения цены после входа
- Примечание о единицах: пипсы и доходности
- Направленность и возврат к среднему: единственное решение, которое меняет всё
- Пятишаговый алгоритм
- Реализация
- Результаты: как читать тепловые карты коэффициента Шарпа
- От оптимальных правил к практике
- Заключение
- Прикрепленные файлы
Введение
Вы используете механическую торговую стратегию (советник), которой в конечном итоге нужны два рабочих параметра: тейк-профит (PT) и стоп-лосс (SL). Распространенная практика — прогнать исторический поиск по сетке по PT/SL и выбрать пару с максимальным внутривыборочным коэффициентом Шарпа — быстрая, но статистически опасная: обычно она выбирает пару, которая лучше всего подстроилась под специфический шум данной исторической траектории, а не пару, оптимальную для базового процесса генерации доходностей. Чтобы сделать задачу конкретной, предположим, что у вас есть: (a) серия P&L по сделкам с учетом издержек, (b) ограничения по счету и исполнению сделок (например, дневной потолок убытка проп-фирмы, размер лота, максимум одновременных позиций) и (c) решение использовать в советнике выходы по методу тройного барьера.
В статье реализуется альтернатива: оценить дискретный процесс Орнштейна–Уленбека (O‑U) по торговому P&L, зафиксировать прогноз (единственный модельный выбор, определяющий, является ли стратегия стратегией возврата к среднему или направленной стратегией), а затем вывести PT и SL, применив процедуру OTR на 100 000 синтетических траекториях. Диапазон SL ограничен вашим лимитом риска: размером счета, лотом и числом одновременных позиций. PT определяется выбранным прогнозом: прогноз = 0 для стратегии возврата к среднему, прогноз = средний P&L для консервативной направленной стратегии, прогноз = средняя прибыльная сделка для оптимистичных направленных сигналов. Прикрепленный код реализует весь конвейер и использует Numba, чтобы сделать крупные расчеты по 100 000 траекторий практичными на одной машине.
Это вторая статья из двухчастной серии. Предыдущая статья построила модель транзакционных издержек, которая выводит скорректированную на издержки серию P&L по сделкам и порог разметки (min_ret) из измеренных брокерских издержек. Если вы еще не читали ту статью, начните с нее — серия P&L, с которой работает текущая статья, должна поступать из того конвейера, а не из жестко заданной константы спреда. Качество оценок параметров O‑U, а значит и качество выводимых здесь PT и SL, напрямую зависит от точности вычета издержек из каждого результата сделки. Надежность значений E[P&L] и средней прибыльной сделки, которые используются как прогнозы для случаев B и C, напрямую зависит от корректности вычета спреда, проскальзывания и комиссии.
Перед продолжением важно установить одно различие. Порог min_ret из предыдущей статьи — это параметр разметки: он определяет, какие исторические результаты сделок считаются настоящим сигналом, а какие — трением исполнения при построении обучающего набора. Оптимальный PT, выводимый в этой статье, — это параметр исполнения сделки: он указывает советнику, где размещать ордер фиксации прибыли. Эти величины связаны — PT как параметр исполнения сделки должен превышать min_ret, иначе вы применяете правило, которое в обучающих данных было бы размечено как ноль, — но они выводятся разными процедурами и не должны смешиваться. Эта статья отвечает за сторону исполнения; предыдущая — за сторону разметки.
Ловушка калибровки
Чтобы понять, почему калибровка стоп-лосса и тейк-профита на исторических данных рискованна, рассмотрим, что на самом деле оптимизирует поиск по сетке. У вас есть выборка из I исторических возможностей. Для каждой кандидатной пары уровней (стоп-лосс, тейк-профит) в сетке из N комбинаций вы вычисляете коэффициент Шарпа стратегии, примененной к этим I возможностям, и выбираете пару с наивысшей оценкой. Выбранная пара — это та, которая лучше всего подстроилась под конкретную последовательность цен в вашей выборке. Она не обязательно оптимальна для будущих последовательностей, сгенерированных тем же процессом.
Лопес де Прадо формализует это как определение переобучения: выбранное правило R* переобучено, если ожидается, что вне выборки оно покажет результат хуже медианной альтернативы. Это не теоретическая проблема — это типичный исход, когда два свободных параметра калибруются на конечной выборке. И тейк-профит, и стоп-лосс могут подстроиться под отдельные выбросы, которые больше не повторятся. Чем больше комбинаций вы тестируете, тем выше вероятность найти ту, которая подогнана под шум.
Альтернатива, предлагаемая книгой, — полностью отделить калибровку от истории. Вместо вопроса «какая пара (PT, SL) лучше всего сработала внутри выборки?» нужно спросить: «какая пара (PT, SL) оптимальна для оцененного процесса?». На этот вопрос можно ответить без исторической симуляции, и полученное правило не переобучается, потому что определяется параметрами процесса, а не выборочными исходами.
Процесс O-U: модель движения цены после входа
Математическая основа этого фреймворка — дискретный процесс Орнштейна–Уленбека (O‑U). Название звучит технически, но идея проста. Представьте резиновую ленту: один конец закреплен в фиксированной точке-якоре, другой прикреплен к шару. На каждом шаге случайный шум может увести шар от якоря, но резиновая лента всегда тянет его обратно. Сила этой ленты управляет тем, насколько быстро шар возвращается.
В процессе O‑U «шар» — это текущая цена (или P&L), а «якорь» — прогноз, то есть уровень, к которому процесс, как ожидается, будет сходиться. Уравнение каждого шага:
Pt = (1 − φ) · прогноз + φ · Pt−1 + σ · εt
где εt — случайный шок из стандартного нормального распределения, σ управляет размером каждого шока, а φ — силой притяжения к прогнозу. Когда φ близка к 1, «резинка» слабая: цена медленно дрейфует к прогнозу на протяжении многих шагов. Когда φ близка к 0, «резинка» сильная: цена почти сразу возвращается к прогнозу после каждого отклонения.
Величина φ напрямую связана с более интуитивной мерой — периодом полураспада: количеством шагов, за которое отклонение от прогноза сокращается вдвое. Период полураспада 25 шагов означает, что цене требуется 25 баров, чтобы закрыть половину расстояния до цели. Период 2 шага означает, что нужно 2 бара. Связь такова: φ = 2−1/hl.

Рисунок 1. Иллюстрация из 4 панелей: траектории O‑U при разных значениях периода полураспада и прогноза
- Панель (a): возврат к среднему с медленным периодом полураспада (25 шагов). Траектории широко блуждают вокруг нуля без устойчивого направления.
- Панель (b): возврат к среднему с быстрым периодом полураспада (2 шага). Траектории возвращаются к нулю в течение нескольких шагов после отклонения.
- Панель (c): направленный процесс с медленным периодом полураспада (25 шагов). Средняя траектория постепенно дрейфует к прогнозу 30 пипсов, а отдельные траектории имеют широкий разброс.
- Панель (d): направленный процесс с быстрым периодом полураспада (2 шага). Средняя траектория быстро сходится к 30 пипсам, но отдельные траектории колеблются вокруг нее с высоким шумом.
На рисунке 1 стоит отметить две вещи. Во-первых, прогноз полностью определяет, куда идет средняя траектория. Без прогноза (панели a и b) средняя траектория остается около нуля. С положительным прогнозом (панели c и d) она движется вверх. Во-вторых, период полураспада управляет тем, насколько плотно отдельные траектории группируются вокруг среднего. Короткий период полураспада не делает стратегию более спокойной; он создает стратегию, которая быстро осциллирует вокруг прогноза, а не плавно дрейфует к нему.
Один параметр требует внимательного чтения результата оценки. σ, показанная в результатах — 12,4 пипса в примерном прогоне, — это остаточное стандартное отклонение OLS-регрессии, а не сырое стандартное отклонение серии P&L. Оценка регрессирует каждый P&L на предыдущий, вычитает предсказуемую AR(1)-компоненту и вычисляет стандартное отклонение оставшейся части. Если в P&L есть значимая автокорреляция (то есть φ заметно выше нуля), остаток будет меньше сырой серии. Поэтому читатель, проверивший pnl_series.std(), получит число больше, чем σ, напечатанная оценщиком. Это корректно: сетка строится на остаточной волатильности подогнанного процесса, а не на безусловной волатильности сырой серии. Но это стоит подчеркнуть, чтобы избежать путаницы при проверке промежуточных результатов.
Примечание о единицах: пипсы и доходности
Описанный фреймворк использует пипсы как единицу P&L, потому что итоговые уровни — тейк-профит и стоп-лосс — советник может напрямую выставить в терминале. Если ваша серия P&L выражена в доходностях (fractional returns), например получена из книжного конвейера getEvents / getBins или построена через выход min_ret_for_symbol(), механизм O‑U остается тем же. Два представления отличаются только линейным масштабированием. Это масштабирование сокращается, когда оптимальная пара (PT, SL) переводится обратно в ценовые расстояния.
Преобразование результата сделки
Пусть PIP — размер пипса (0.0001 для большинства котировок EURUSD), а S — цена входа. Для длинной сделки два направления преобразования:
доходность = (pnlpips × PIP) / S
pnlpips = (доходность × S) / PIP
Как масштабируются параметры O‑U
Передача в оценщик O‑U P&L в доходностях вместо P&L в пипсах влияет на две из трех модельных величин. Третья — скорость возврата к среднему — не меняется.
- φ (коэффициент возврата к среднему) — без изменений. OLS-регрессия Pt на Pt−1 безразмерна: деление обеих сторон на одну и ту же константу оставляет наклон коэффициента регрессии, а значит и период полураспада, неизменными.
- σ (стандартное отклонение шока) — масштабируется пропорционально единице измерения. При входе S = 1,0800 σ = 12,4 пипса становится σ = 0,115% в доходностях. Преобразование следует той же пропорции, что и сам P&L:
σдоходности = σpips × (PIP / S)
σpips = σдоходности × (S / PIP)
- Прогноз — также масштабируется линейно. Прогноз +30 пипсов при входе 1,0800 — это прогноз в доходности: (30 × 0.0001) / 1,0800 ≈ 0,278%. Та же пропорциональность действует для трех случаев из следующего раздела.
Почему оптимальное правило инвариантно к выбору единиц
Поскольку вся сетка (PT, SL) строится как кратные σ, а барьеры и симулированный P&L всегда выражены в одной и той же единице, тепловая карта коэффициента Шарпа будет одинаковой с точностью до численного округления независимо от выбранной единицы. Оптимальные PT и SL, выраженные в доходностях, после умножения обратно на цену входа дадут те же значения в пипсах — и, следовательно, те же параметры тройного барьера для EA. Таблица ниже подтверждает это для пяти ключевых величин из примерного прогона.
| Величина | В пипсах (S = 1,0800) | В доходностях (S = 1,0800) |
|---|---|---|
| Прогноз (случай A) | 0 пипсов | 0,000% |
| Прогноз (случай C) | +35,6 пипса | +0,330% |
| σ (остаточная, OLS) | 12,4 пипса | 0,115% |
| Оптимальный PT (случай A) | 51 пипс | 0,472% |
| Потолок SL (проп-фирма) | 125 пипсов | 1,157% |
Таблица 1. Ключевые величины в пипсах и доходностях при средней цене входа 1,0800; σ — остаточное стандартное отклонение OLS, а Оптимальный PT — из примерного прогона прикрепленного кода.
Выбор между пипсами и доходностями поэтому является вопросом удобства конвейера. Если у вас уже есть процесс разметки по методу тройного барьера, который производит метки доходности, включая процесс на основе min_ret_for_symbol() из предыдущей статьи, подайте эти доходности напрямую в оценку O‑U и перебор сетки, затем умножьте полученные (PT*, SL*) на цену входа, чтобы восстановить расстояния в пипсах. Выбор не влияет на статистическую корректность OTR или ранжирование вариантов правил; он лишь определяет, выражены ли промежуточные результаты в процентах или пипсах.
Направленность и возврат к среднему: единственное решение, которое меняет всё
Процесс O‑U — общая модель. Он может описывать маркет-мейкинговую стратегию с возвратом к среднему, направленную импульсную стратегию или стратегию, которая ожидаемо теряет деньги. Именно параметр прогноза отличает эти случаи — и правильный выбор прогноза является самым важным модельным решением во всем фреймворке. В книге это занимает два абзаца. На практике это заслуживает значительно большего внимания.
Что представляет параметр прогноза
Прогноз, записываемый как E0[PT] в обозначениях книги, — это ценовой уровень, которого, согласно сигналу стратегии, рынок достигнет до закрытия позиции. Это не свободный параметр для подбора. Это утверждение о вашем преимуществе, выраженное в ценовых терминах. Прежде чем подгонять процесс O‑U к чему-либо, нужно ответить на один вопрос: что сигнал предсказывает для цены после входа? Ответ на этот вопрос и есть прогноз.
Все три значения прогноза ниже вычисляются из серии после учета издержек P&L — то есть после вычета спреда, проскальзывания и комиссии из каждого результата сделки с помощью модели издержек из предыдущей статьи. Прогноз, полученный из P&L без учета издержек, будет более оптимистичным, чем позволяет реализованное распределение, и итоговый PT окажется слишком широким.
Случай A — возврат к среднему: прогноз = 0
Задавайте прогноз равным нулю, когда сигнал стратегии прогнозирует, что цена вернется примерно туда, откуда начала. Это корректная спецификация для любой стратегии возврата к среднему: маркет-мейкинговой стратегии, покупающей bid в ожидании отскока к mid, статистического арбитража, покупающего спред в ожидании схождения, или разворотной стратегии, покупающей перепроданность в ожидании восстановления к середине недавнего диапазона.
При прогнозе = 0 процесс O‑U привязан к цене входа. Любое движение в любую сторону моделируется как шум вокруг нулевой ожидаемой прибыли. Далее модель позволяет ответить на вопрос: если ожидаемый результат равен нулю, какая пара из тейк-профита и стоп-лосса оптимизирует риск-скорректированное распределение P&L на выходе? Ответ полностью зависит от периода полураспада и волатильности — шумовой структуры, а не направленного смещения.
Распространенная ошибка — ставить прогноз = 0 для разворотной RSI-стратегии только потому, что RSI срабатывает на экстремумах. Это смешивает логику входа сигнала с внутрисделочной динамикой цены. RSI срабатывает, когда рынок перепродан; он не предсказывает, что цена просто вернется туда, где была. Если у сигнала есть реальное преимущество, цена должна сместиться в направлении сигнала дальше уровня входа, — и это ожидаемое движение и является прогнозом.
Случай B — реалистичная направленная стратегия: прогноз = E[P&L]
Задавайте прогноз равным историческому среднему P&L на сделку после учета издержек, когда считаете, что у сигнала есть скромное или неопределенное направленное преимущество. Это консервативная спецификация направленной стратегии. Для RSI-стратегии на EURUSD H1, которая дала −3,3 пипса на сделку в среднем по выборке — после вычета p95-спреда 1,2 пипса и 0,4 пипса проскальзывания на каждой сделке, — это означает прогноз = −3,3 пипса. Тогда процесс O‑U моделирует стратегию, которая после издержек слегка работает против вас, а модель позволяет оценить, какая пара (PT, SL) наиболее эффективно снижает ущерб при встречном ветре 3,3 пипса на сделку?
Этот случай подходит, когда вы не уверены, есть ли у сигнала настоящее преимущество, когда историческая выборка покрывает смешанные рыночные режимы или когда нужна консервативная нижняя граница оптимальных параметров правила.
Случай C — оптимистичная направленная стратегия: прогноз = E[win]
Задавайте прогноз равным средней выигрышной сделке после учета издержек, когда считаете, что сигнал срабатывает правильно в моменты, после которых следует значимое ценовое движение. Для RSI-стратегии выигрышные сделки в среднем дали +35,6 пипса после издержек. Установка прогноз = +35,6 пипса моделирует позицию, которая при корректном сигнале сходится к такой прибыли. Затем модель оптимизирует пару (PT, SL) для этого процесса схождения и, как правило, приводит к более широкому тейк-профиту.
Этот случай подходит, когда классификатор метаразметки уже отфильтровал сигнал до событий высокой уверенности — сделок, которые фактически достигают порога исполнения советника MetaLabeling Enhancer. Для таких отфильтрованных сделок условная ожидаемая после учета издержек прибыль значительно выше сырого среднего по всем сигналам. Обратите внимание: если модель издержек меняется — например, вы повторно запускаете MQL5-скрипт после смены брокера, и p95-спред расширился, — значение E[win] изменится, и прогноз для случая C нужно пересчитать перед повторным запуском поиска OTR.
Прогноз как априорное убеждение
Эти три случая не являются взаимозаменяемыми вариантами одной и той же задачи. Это три разные постановки задачи для трех разных вариантов стратегии. При выборе между ними нужно ясно указать, для какого варианта стратегии калибруется торговое правило. Правило, откалиброванное на прогноз = 0, задаст меньший уровень тейк-профита, чем правило, откалиброванное на прогноз = +35,6, и будет по-разному вести себя в разных рыночных условиях. Фреймворк не делает этот выбор за вас — он фиксирует последствия выбранной спецификации.
| Случай | Значение прогноза | Подходит, когда… | Оптимальный PT (пипсы) |
|---|---|---|---|
| A — Контртрендовый | 0 | Сигнал предсказывает возврат к входу; направленное преимущество не предполагается | 51 |
| B — Направленный, реалистичный | −3,3 | У сигнала неопределенное или слабое преимущество; консервативная спецификация | 45 |
| C — Направленный, оптимистичный | +35,6 | Отфильтрованные сигналы высокой уверенности, где средний выигрыш после учета издержек можно считать репрезентативным | 85 |
Во всех трех случаях стоп-лосс был ограничен 125 пипсами дневным лимитом убытка проп-фирмы для счета $25 000, торговли 0,5 лота и максимум двух одновременных позиций. Диапазон значений SL ограничен правилом проп-фирмы, а не процессом O‑U. Диапазон значений PT определяется прогнозом. Это практическое различие важно: SL задается вашими правилами управления риском, PT — вашим убеждением о преимуществе сигнала.
Пятишаговый алгоритм

Рисунок 2. Иллюстрация из 5 панелей: алгоритм оптимального торгового правила из главы 13 AFML
- Шаг 1 — Оценка параметров: OLS-регрессия по линеаризованному уравнению O‑U оценивает φ и σ из исторической после учета издержек серии P&L по сделкам. Полученная σ — остаточное стандартное отклонение регрессии, а не безусловное стандартное отклонение сырого P&L; они различаются, когда в серии есть значимая автокорреляция.
- Шаг 2 — Построение сетки: декартово произведение кандидатных уровней тейк-профита и стоп-лосса, каждое выражено как кратное σ и сверху ограничено дневным лимитом убытка проп-фирмы. Оси PT и SL имеют один и тот же верхний предел.
- Шаг 3 — Симуляция: генерируются 100 000 траекторий O‑U с подогнанными параметрами и выбранным прогнозом. Все шоки заранее генерируются как единая матрица и используются совместно всеми узлами сетки — это схема общих случайных чисел (CRN), благодаря которой сравнения значений коэффициента Шарпа между узлами отражают правила, а не шум выборки. Каждая траектория идет максимум max_hp шагов; траектории, не коснувшиеся ни одного барьера к шагу max_hp, оцениваются по терминальной цене. Этот выход по времени — третий барьер в симуляции, и его нужно согласовать с параметром max_hold в предшествующей разметке по методу тройного барьера. Несоответствие — в коде по умолчанию max_hold=48 для разметки и max_hp=100 для симуляции — означает, что OTR оптимизирует горизонт удержания длиннее реального времени работы стратегии, что смещает оценки PT вверх. Перед продукционным запуском задайте оба параметра одинаковыми.
- Шаг 4 — Оценка: каждый узел (PT, SL) применяется ко всем симулированным траекториям. P&L на выходе записывается по каждой траектории, а коэффициент Шарпа вычисляется по всем траекториям этого узла с помощью онлайн-алгоритма Welford за один проход.
- Шаг 5 — Выбор: пара (PT*, SL*) с максимальным коэффициентом Шарпа является оптимальным торговым правилом. Эти значения становятся параметрами тройного барьера для EA.
Шаги 1 и 2 дешевы. Вся вычислительная стоимость находится в шагах 3 и 4: 100 000 траекторий × 400 узлов сетки × до 100 шагов на траекторию = 4 миллиарда операций. Выбор реализации для шагов 3 и 4 определяет практическую применимость подхода.
Перед переходом к коду стоит сделать важное замечание об итерационной инициализации. Серия P&L, используемая на шаге 1, генерируется запуском triple_barrier_pnl() с начальными ATR-барьерами (pt_mult=2.0, sl_mult=1.0). Эти начальные барьеры формируют распределение P&L: стратегия, часто достигающая барьера тейк-профита, покажет другое распределение — а значит и другие оценки φ и σ, — чем те же сигналы, оцененные с более широкими барьерами. На практике запустите алгоритм один раз с разумными ATR-множителями, получите начальные (PT*, SL*), затем повторно запустите triple_barrier_pnl(), используя эти выведенные значения в пипсах как барьеры, и переоцените процесс. После одной итерации распределение P&L становится согласованным с барьерами, которые его породили. Для RSI-примера в статье начальная ATR-разметка оказалась достаточно близка к результату OTR, поэтому вторая итерация существенно не изменила результаты, но для любой новой стратегии это нужно проверять, а не предполагать.
Реализация
Шаг 1: P&L с учетом издержек и оценка параметров O‑U
Первое изменение относительно автономной версии кода: вычет спреда в triple_barrier_pnl() теперь берется из модели транзакционных издержек, а не задан жестко. То же значение model.spread_pips, которое использовалось для вывода min_ret на этапе разметки, должно использоваться здесь; иначе распределение P&L будет отражать другое предположение об издержках, чем то, на котором построены обучающие метки. Проскальзывание также добавлено в вычет на сделку; исходный код его пропускал.
from pathlib import Path
from afml.transaction_costs import load_cost_model
# Загружаем ту же модель издержек, которая использовалась для разметки
# на предыдущем этапе конвейера.
# spread_pips и slippage_pips должны совпадать со значениями,
# переданными в get_events().
cost_model = load_cost_model(
csv_path = Path("data/EURUSD_costs.csv"),
spread_percentile = "p95_pips",
slippage_pips = 0.4,
commission_per_lot = 7.0,
lot_size = 0.5,
)
bars = download_bars("EURUSD=X", period="730d", interval="1h")
rsi = compute_rsi(bars["close"], period=14)
at = compute_atr(bars["high"], bars["low"], bars["close"], period=14)
sigs = generate_rsi_signals(rsi, overbought=70, oversold=30)
# max_hold=48 баров H1 ≈ 2 календарных дня; значение должно совпадать
# с holding_days=2.0, переданным в min_ret_for_symbol() на этапе разметки.
# max_hp в симуляции OTR (шаг 3) следует задать тем же значением.
pnl_series = triple_barrier_pnl(
df = bars,
signals = sigs,
atr = at,
pt_mult = 2.0,
sl_mult = 1.0,
max_hold = 48,
spread_pips = cost_model.spread_pips, # ← из модели издержек, а не жестко заданное значение
slippage_pips = cost_model.slippage_pips, # ← учитываем проскальзывание
pip = 0.0001,
)
pnl_nz = pnl_series.dropna().values.astype(float)
pnl_nz = pnl_nz[pnl_nz != 0.0]
avg_win = float(pnl_nz[pnl_nz > 0].mean())
expected = float(pnl_nz.mean())
Обратите внимание, что triple_barrier_pnl() в прикрепленном коде обновлена и теперь принимает параметр slippage_pips наряду с spread_pips. Вычет на сделку теперь равен spread_pips + slippage_pips. Комиссия не включается в этот вычет, потому что при размерах 0,01–0,5 лота на EURUSD она дает менее 0.1 пипса на сделку и уже встроена в E[P&L] через конвейер разметки; добавление ее еще раз здесь привело бы к двойному учету. Для больших лотов или инструментов с более высоким отношением комиссии к спреду ее следует включать явно.
Книга линеаризует уравнение (13.2), определяя Xt = Pt−1 − прогноз и Yt = Pt, а затем применяет OLS для оценки φ̂ = Cov(Y, X) / Var(X). Остаточное стандартное отклонение этой регрессии — это σ̂, то есть размер шока на шаг, используемый для построения сетки. Как отмечалось в разделе 3, оно меньше безусловного стандартного отклонения сырой серии P&L, когда φ заметно выше нуля.
def estimate_ou_params(pnl: np.ndarray, forecast: float = 0.0) -> OUParams:
y = pnl[1:]
x = pnl[:-1] - forecast
phi = np.cov(y, x)[0, 1] / np.var(x) # наклон OLS-регрессии
phi = np.clip(phi, -0.9999, 0.9999)
residuals = y - forecast - phi * x # остатки AR(1)
sigma = np.std(residuals) # остаточная σ, не сырое std
hl = -np.log(2) / np.log(phi) if 0 < phi < 1 else float("inf")
return OUParams(phi=phi, sigma=sigma, hl=hl, forecast=forecast)
Реализация из книги (фрагмент 13.2)
Реализация из книги использует вложенный цикл: внешний цикл по каждому узлу (PT, SL) в сетке и внутренний цикл, который симулирует n_iter траекторий, вызывая Python-функцию random.gauss() один раз на шаг каждой траектории. Для сетки 20×20 и 100 000 траекторий по 100 шагов это означает 4 миллиарда отдельных вызовов Python-функций внутри чистого цикла. Книга признает, что это медленно, и предлагает распараллелить узлы с помощью схемы multiprocessing из главы 20.
def batch_book(ou, n_iter=100_000, max_hp=100, rPT=..., rSLm=...):
for pt, sl in product(rPT * ou.sigma, rSLm * ou.sigma):
exits = []
for _ in range(n_iter):
p = seed_val
for _ in range(max_hp):
p = (1-ou.phi)*ou.forecast + ou.phi*p + ou.sigma*gauss(0,1)
if p - seed_val >= pt or p - seed_val <= -sl:
break
exits.append(p - seed_val)
Почему multiprocessing здесь не дает выигрыша
Параллелизация через Python multiprocessing.Pool — естественное расширение схемы из главы 20. На практике она не дает измеримого ускорения для этой нагрузки. Измеренное ускорение на 2-ядерной машине составило 1.0×: реальное время выполнения совпало с однопоточной книжной версией. Причина архитектурная. Каждый рабочий процесс должен быть форкнут из родительского интерпретатора (50–200 мс накладных расходов), получить аргументы через pickle по каналу межпроцессного взаимодействия, выполнить внутренний цикл в отдельном интерпретаторе Python и вернуть результаты тем же способом. Для нагрузки, где каждая задача — примерно 50 мс внутреннего вычисления, коммуникационные накладные расходы съедают весь выигрыш. Урок не в том, что multiprocessing плох, а в том, что правильная примитивная форма параллелизма зависит от баланса между вычислением и коммуникацией. Когда задачи крупные и независимые, multiprocessing превосходен. Когда работа легкая и плотно зацикленная, он не подходит.
Реализация на Numba
Декоратор Numba @njit компилирует цикл симуляции в машинный код перед первым запуском. В скомпилированном ядре нет интерпретатора Python, сборщика мусора и объектных накладных расходов. Четыре структурных изменения относительно книжной версии дают Numba преимущество; первое из них является методологическим улучшением, влияющим не только на скорость, но и на качество результата.
Во-первых, все случайные шоки заранее генерируются как единая NumPy-матрица формы (n_paths × max_hp) до основного цикла, и эта матрица используется совместно всеми узлами сетки. Это схема общих случайных чисел (CRN): поскольку все 400 узлов (PT, SL) оцениваются на одних и тех же 100 000 сценариев, различия значений коэффициента Шарпа по сетке отражают правила, а не удачу независимых выборок. Книжная версия регенерирует шоки независимо для каждого узла, добавляя шумовой фон к каждому попарному сравнению; он почти незаметен в отдельной ячейке, но проявляется как сглаживание поверхности тепловой карты. При 100 000 траекторий эффект мал, но не равен нулю; при масштабе сравнительного теста 3 000 траекторий он заметен как зернистость поверхности коэффициента Шарпа.
Во-вторых, векторизованная NumPy-генерация матрицы шоков заменяет 4 миллиарда отдельных вызовов gauss() одной операцией, полностью устраняя доминирующую стоимость книжной реализации.
В-третьих, внешний цикл по значениям PT использует Numba prange вместо Python range. Numba переводит prange в параллельные циклы OpenMP, распределяя итерации PT по всем доступным ядрам CPU без накладных расходов сериализации — потоки разделяют одно адресное пространство, поэтому нечего сериализовать через pickle или передавать.
В-четвертых, среднее и стандартное отклонение вычисляются онлайн-алгоритмом Welford внутри скомпилированного ядра, устраняя второй проход по массиву выходов для каждого узла.
@njit(cache=True)
def _sim_paths(phi, sigma, forecast, seed_val, shocks):
# shocks: матрица (n_paths × max_hp), заранее сгенерированная один раз
# и общая для всех узлов сетки; схема общих случайных чисел (CRN).
n, T = shocks.shape
out = np.empty((n, T))
for i in range(n):
p = seed_val
for t in range(T):
p = (1 - phi) * forecast + phi * p + sigma * shocks[i, t]
out[i, t] = p - seed_val
return out
@njit(parallel=True, cache=True)
def _eval_mesh(paths, PT_pips, SLm_pips):
n_pt, n_sl = len(PT_pips), len(SLm_pips)
n, T = paths.shape
out = np.empty((n_pt, n_sl, 2))
for pi in prange(n_pt): # параллелим внешний цикл
pt = PT_pips[pi]
for si in range(n_sl):
sl = SLm_pips[si]
mu, M2 = 0.0, 0.0
for i in range(n):
exit_pnl = paths[i, T - 1] # выход по времени (вертикальный барьер)
for t in range(T):
if paths[i,t] >= pt or paths[i,t] <= -sl:
exit_pnl = paths[i, t]; break
delta = exit_pnl - mu
mu += delta / (i + 1) # обновление среднего по Welford
M2 += delta * (exit_pnl - mu) # обновление дисперсии по Welford
out[pi, si] = [mu, np.sqrt(M2 / n)]
return out

Рисунок 3. Иллюстрация из 2 панелей: сравнение производительности трех подходов
- Панель (a): измеренное реальное время выполнения при 3 000 траекторий на 2-ядерной машине. Книжная версия и multiprocessing находятся в пределах 3% друг от друга; Numba в 242 раза быстрее.
- Панель (b): экстраполяция на 100 000 траекторий и сетку 20×20. Книжная версия заняла бы 161 секунду; версия Numba занимает 0,67 секунды.
На результат multiprocessing стоит обратить внимание. Паттерн главы 20 — распараллеливание pandas-операций по CPU-кластеру — принципиально корректен, но не подходит для этой нагрузки. Здесь каждая задача слишком мала и эффективна с точки зрения кэша, чтобы оправдать стоимость межпроцессного взаимодействия. Коротко: multiprocessing выигрывает, когда доминирует вычисление; Numba выигрывает, когда коммуникационные накладные расходы иначе стерли бы весь выигрыш.
Результаты: как читать тепловые карты коэффициента Шарпа

Рисунок 4. Иллюстрация из 3 панелей: тепловые карты коэффициента Шарпа для спецификаций прогноза с возвратом к среднему и направленным движением
- Случай A (прогноз = 0): область максимальных значений коэффициента Шарпа проходит вдоль верхнего края тепловой карты — широкие стоп-лоссы — при умеренных уровнях тейк-профита около 51 пипса. Красная область при низком SL и высоком PT представляет правила, которые выбиваются из позиции до того, как успевают захватить колебания, вызванные шумом процесса.
- Случай B (прогноз = −3,3 пипса): небольшой отрицательный прогноз смещает оптимальный PT влево, к 45 пипсам. Поверхность коэффициента Шарпа качественно похожа на Случай A, но с более низкими абсолютными значениями по всей сетке, отражая встречный ветер издержек.
- Случай C (прогноз = +35,6 пипса): положительный прогноз создает принципиально другую тепловую карту. Зеленая область расширяется к более широким PT (85 пипсов), а вся поверхность коэффициента Шарпа выше, потому что процесс моделируется как сходящийся к уровню тейк-профита. Звезда отмечает PT=85, SL=125, коэффициент Шарпа = 7,70.
Во всех трех тепловых картах появляются две структурные особенности. Во-первых, оптимальный SL всегда находится на потолке сетки — 125 пипсов в этом случае, — подтверждая, что дневной лимит убытка проп-фирмы является связывающим ограничением, а не отражением предпочтений процесса O‑U. Процесс рекомендует еще более широкий стоп; правила проп-фирмы его ограничивают. Во-вторых, нижний левый угол каждой тепловой карты (узкие PT, узкие SL) всегда желтый: это правила, которые почти сразу выходят в любую сторону и имеют коэффициенты Шарпа около нуля или отрицательные независимо от прогноза.
Практическая интерпретация тепловой карты такова: цвет в любой координате (PT, SL) — это ожидаемый коэффициент Шарпа торгового правила с такими параметрами, усредненный по 100 000 синтетических рыночных сценариев, согласованных с оцененным процессом O‑U. Зеленая координата означает, что правило ожидаемо даст благоприятное распределение выходов. Красная — что нет. Звезда показывает, где задавать параметры тройного барьера.
От оптимальных правил к практике
На выходе процедура дает два числа: оптимальный тейк-профит и оптимальный стоп-лосс в пипсах. Чтобы перенести их в параметры советника, нужно также учесть размер счета, размер позиции и максимальное число одновременно открытых позиций.
Дневной лимит убытка проп-фирмы применяется к счету, а не к отдельным сделкам. Если счет $25 000 имеет дневной лимит убытка 5%, максимальный дневной убыток составляет $1 250. При 0,5 лота на EURUSD — где каждый пипс стоит $5 — этот лимит соответствует 250 пипсам суммарно по всем открытым позициям за день. Если стратегия удерживает максимум две позиции одновременно, стоп-лосс каждой позиции не должен превышать 125 пипсов, чтобы одновременный убыток по обеим не нарушил дневной лимит.
Именно это число задает верхнюю границу по оси SL. Оно не выбирается произвольно, а выводится из конкретной конфигурации счета. Например, для счета $100 000, лота 1,0 и трех одновременных позиций потолок составит 167 пипсов. Для счета $10 000, лота 0,1 и одной позиции — 500 пипсов. Оптимальный SL в модели O‑U будет равен минимуму между предпочтением неограниченной модели и этим потолком. Во всех трех протестированных случаях потолок оказался связывающим ограничением: неограниченная модель давала более широкий стоп, чем разрешают правила проп-фирмы.
Перед применением выведенного PT проверьте, что он превышает порог min_ret, рассчитанный моделью транзакционных издержек в предыдущей статье. Для внутридневной конфигурации EURUSD min_ret составляет примерно 2,5 пипса: это результат применения множителя издержек 1,5× к полной стоимости сделки в обе стороны, равной 1,67 пипса. Это значение значительно ниже оптимального PT в 51 пипс для случая A, поэтому в данном примере конфликта нет. Но для стратегий с очень малым прогнозом, жесткими лимитами проп-фирмы или необычно высокими издержками PT, выведенный через OTR, может оказаться близким к min_ret или даже ниже него. В такой ситуации стратегия должна достигать тейк-профита, который на этапе обучения был бы размечен как ноль. Правильная реакция — пересмотреть спецификацию прогноза или предположения об издержках, а не применять правило как есть.
Для любого советника, который использует выходы по методу тройного барьера, результаты напрямую превращаются в два входных параметра. Тейк-профит задается спецификацией прогноза, соответствующей варианту стратегии: 51 пипс для RSI-стратегии без фильтрации (случай A) и 85 пипсов для сигналов, прошедших фильтр метаразметки (случай C). Стоп-лосс задается потолком конфигурации счета, рассчитанным выше. Оба значения нужно пересматривать при изменении размера счета, лота или максимального числа одновременных позиций, а также при каждом повторном запуске модели транзакционных издержек: изменение модели издержек сдвинет E[P&L] и E[win], а значит и прогнозы для случаев B и C.
Заключение
Калибровка PT и SL через выбор лучшего внутривыборочного узла сетки легко переобучается на исторической траектории. Более устойчивый подход состоит из пяти шагов: (1) получить скорректированную на издержки серию P&L, используя измеренные брокерские издержки из модели транзакционных издержек предыдущей статьи; (2) заранее сформулировать, что сигнал предсказывает после входа, то есть задать прогноз; (3) оценить параметры O‑U по внутрисделочному P&L после учета издержек; (4) сгенерировать множество синтетических траекторий из оцененного процесса с использованием общих случайных чисел; (5) выбрать пару (PT, SL), максимизирующую ожидаемую риск-скорректированную результативность на этих траекториях, с учетом потолка SL, выведенного из размера счета, лота и максимального числа одновременных позиций.
Выбор прогноза — ключевое модельное решение. Прогноз = 0 дает правило возврата к среднему с более узкими PT. Прогноз, равный среднему P&L после учета издержек, или прогноз, равный средней прибыльной сделке после учета издержек, постепенно расширяет PT и подходит соответственно для консервативных направленных вариантов и вариантов с высокой уверенностью. Все три прогноза должны рассчитываться по серии P&L после учета издержек. Прогнозы, полученные из P&L без учета издержек, будут систематически завышены, а итоговый PT окажется шире, чем позволяет реальная среда исполнения.
На практике фрагмент Python-кода из книги слишком медленный для 100 000 траекторий. Скомпилированное параллельное ядро Numba с общей памятью выполняет тот же перебор в сотни раз быстрее и делает метод применимым. Общая матрица шоков — это не просто ускорение, а схема общих случайных чисел, которая убирает выборочный шум отдельных узлов из сравнений коэффициента Шарпа по сетке. Задавайте max_hp равным max_hold на предшествующем этапе разметки и согласуйте holding_days в модели издержек с тем же горизонтом. Пересчитывайте сетку и потолок SL при каждом изменении размера счета, лота, числа одновременных позиций или брокерских издержек. Включенный код дает воспроизводимый и быстрый конвейер: от скорректированной на издержки серии торгового P&L и риск-ограничений — к готовым к применению параметрам тройного барьера.
Прикрепленные файлы
| Имя файла | Описание |
|---|---|
| synthetic_backtest.py | Полная реализация конвейера: интеграция модели издержек, оценка параметров O‑U, реализация фрагмента 13.2 из книги, многопроцессная версия, версия с оптимизацией через Numba (матрица шоков CRN, параллельный цикл prange и расчет дисперсии по Уэлфорду), сравнительный тест трех реализаций и экспорт результата OTR. Код обновлен: triple_barrier_pnl() принимает slippage_pips, а spread_pips берется из TransactionCostModel. |
| transaction_costs.py | Зависимость из предыдущей статьи: TransactionCostModel и load_cost_model(). Модуль предоставляет model.spread_pips и model.slippage_pips для конвейера расчета P&L. Перед запуском synthetic_backtest.py поместите файл в afml/transaction_costs.py. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22275
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Адаптивный индикатор Malaysian Engulfing (Часть 1): Обнаружение паттернов и валидация ретеста
Торговые инструменты MQL5 (Часть 24): Улучшение восприятия глубины с помощью 3D-кривых, режима панорамирования и навигации через виджет ViewCube
Торговые инструменты MQL5 (Часть 25): Расширяем поддержку нескольких распределений с интерактивным переключением
Нейросети в трейдинге: от рыночного шума к устойчивому торговому плану (MomAD)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо за эту информацию
Рад помочь.