Управление позициями: Пирамидальное наращивание прибыльных позиций при снижающемся риске
Содержание
- Введение
- Обзор архитектуры
- Пять точек интеграции
- Дополнения к модулю
- Класс «Мост»
- Подключение полноценного советника
- Заключение
- Список файлов приложения
Введение
В Части 13 этой серии на язык MQL5 переведены четыре метода определения размера ставки AFML. BetSizeProbability формирует скорректированный для параллельной обработки дискретизированный сигнал позиции в диапазоне [−1, 1]. BetSizeDynamic преобразует расхождение между текущей ценой и прогнозной ценой в размер ставки с помощью калиброванной сигмоидальной или степенной кривой. BetSizeBudget нормализует дисбаланс длинных и коротких позиций активных направленных сигналов, преобразуя его в коэффициент загрузки доступной емкости. BetSizeReserve использует эмпирическое распределение одновременного дисбаланса через функцию CDF смеси EF3M. Каждый метод возвращает структуру BetSizeResult, а советник в Части 13 устанавливает соответствие подписанного поля bet_size с единственной позицией, размер лота которой равен InpMaxLots × |bet_size|.
В этой схеме есть один структурный пробел: она связывает величину ставки с одноуровневой позицией. Размеры ставки bet_size 0.90 и bet_size 0.55 открывают сделки различного размера, но обе исполняются как единый рыночный ордер по фиксированному стоп-лоссу. Информация об объеме сделки определяет, какой капитал будет вложен; она не влияет на то, как формируется сделка, как изменяется риск по мере изменения позиции в прибыльном направлении или когда следует увеличивать прибыльную сделку. Эти вопросы относятся к управлению позициями, а не к определению размеров.
В статье автора Tola Moses Hector Управление позициями: Безопасный пирамидинг с единым стопом в MQL5 представлен класс CPyramidEngine. Это самодостаточный класс MQL5, который позволяет совершать сделки с использованием строго уменьшающихся размеров лотов и применяет единый унифицированный стоп-лосс, который продвигается вперед после каждого добавления. Математическая структура системы такова, что общий риск по счету снижается с каждым добавлением. Она разработана таким образом, чтобы ее можно было интегрировать в любой советник при внесении шести изменений в существующий код.
Эти две системы дополняют друг друга. Модуль определения размера ставки отвечает на вопрос, насколько велик риск. Пирамидный движок отвечает на вопрос о том, как распределить этот риск по слоям. В этой статье описывается создание адаптерного слоя, который обеспечивает их соединение. В результате получаем CPyramidBridge это класс-обертка, который находится между стеком определения размера ставки и пирамидным движком и связывает их через пять точек интеграции. Каждая точка заменяет жестко заданный параметр в CPyramidEngine актуальным выходным значением модуля расчета размера позиции.
Прежде чем начать, обратите внимание на необходимые предварительные условия. Код в этой статье зависит от всех пяти файлов из Части 13 (BetSizingUtils.mqh, EF3M.mqh, Ch10Snippets.mqh, BetSizing.mqh, BetSizingEA.mq5) и по трем файлам из статьи о пирамидировании (PyramidUtils.mqh,PyramidEngine.mqh,PyramidEA.mq5). В эту статью добавлены два новых файла: PyramidEngine_additions.mqh, который расширяет возможности движка семью новыми публичными методами, и BetSizingPyramidBridge.mqh, который реализует CPyramidBridge. Демонстрационный советник с полностью настроенной связкой BetSizingPyramidEA.mq5 одновременно отображает все пять активных точек интегрирования. Все три новых файла находятся в прикрепленном архиве.
На рисунке 1 показано пирамидирование. Уменьшающиеся объемы лотов, добавляемые на более высоких ценовых уровнях, образуют треугольный контур. Поскольку единый стоп-лосс продвигается вперед после каждого добавления, общий долларовый риск снижается на каждом этапе даже при увеличении общего размера позиции.

Рисунок 1. 2-панельная иллюстрация идеи пирамидирования
- Панель (а). Три горизонтальных бара, построенные на уровнях цен входа: Начальный (1.00 лот по цене E1), Добавление 1 (0.60 лота по цене E2, +60 пунктов), Добавление 2 (0.30 лота по цене E3, +120 пунктов). Пунктирная линия, соединяющая вершины баров, описывает контур пирамиды: широкое основание возле самой низкой цены, сужающееся к вершине около самой высокой. Единый уровень стоп-лосса для каждого этапа показан пунктирной горизонтальной линией; заштрихованная красным зона иллюстрирует диапазон риска 1-го этапа.
- Панель (b): общий риск пипсах на лот на каждом этапе (синие/зеленые/оранжевые бары, левая ось) наряду с общим количеством открытых лотов (серая пунктирная линия, правая ось). Риск снижается с 50 → 40 → 15 пипс-лотов, в то время как позиция увеличивается с 1.00 → 1.60 → 1.90 лота. Снижение происходит потому, что продвигающийся вперед единый стоп-лосс перемещает позиции, ранее открытые для достижения точки безубыточности или фиксированной прибыли, поэтому только самое новое, минимальное добавление несет незакрытый риск.
Обзор архитектуры
Стек размеров ставок из пяти файлов из Части 13 формирует четкую иерархию зависимостей. Файл BetSizingUtils.mqh является основой, предоставляя обычную функцию распределения вероятностей (CDF), ее обратную функцию, счетчик параллельной активности сигналов методом сканирующей линии, а также структуру BetSizeResult. Каждый из файлов EF3M.mqh и Ch10Snippets.mqh зависит только от этого слоя. Файл BetSizing.mqh зависит от обоих, и советник находится на верхнем уровне этой иерархии. CPyramidEngine представляет собой независимый стек из двух файлов: PyramidUtils.mqh обеспечивает вычисление стоимости пункта независимо от используемого инструмента и проверку стоп-лосса на уровне брокера; PyramidEngine.mqh реализует класс управления пирамидой.
Файл BetSizingPyramidBridge.mqh не вносит изменений ни в один из существующих стеков. Он включает в себя оба стека, владеет экземпляром CPyramidEngine в качестве закрытого члена и предоставляет открытый интерфейс, который заменяет прямые вызовы движка в советнике. Советник взаимодействует исключительно с мостом; мост же взаимодействует как с функциями расчета размеров, так и с движком. Точки интеграции подключены внутри моста таким образом, чтобы в советнике отсутствовала логика расчета размеров, и для функций расчета размеров не требуется никаких знаний от движка.
На рисунке 2 показана полная диаграмма зависимостей.

Рисунок 2. 3-уровневый граф зависимостей, отображающий стек BetSizing, CPyramidEngine и адаптер CPyramidBridge.
- Левая колонка: стек BetSizing в Части 13 из файлов от BetSizingUtils.mqh до BetSizing.mqh включительно.
- Правая колонка: файл PyramidUtils.mqh, оригинал файла PyramidEngine.mqh, а также семь новых общедоступных методов, добавленных файлом PyramidEngine_additions.mqh.
- В центре: файл BetSizingPyramidBridge.mqh, который соединяет эти два стека. Советник вызывает только мост. Пять пронумерованных меток в выносном блоке соответствуют пяти точкам интеграции, разработанным в следующем разделе.
| Файл | Размещение | Роль в этой статье |
|---|---|---|
| BetSizingUtils.mqh | MQL5\Include\BetSizing\ | Без изменений из Части 13 |
| EF3M.mqh | MQL5\Include\BetSizing\ | Без изменений из Части 13 |
| Ch10Snippets.mqh | MQL5\Include\BetSizing\ | Без изменений из Части 13 |
| BetSizing.mqh | MQL5\Include\BetSizing\ | Без изменений из Части 13 |
| PyramidUtils.mqh | MQL5\Include\Pyramid\ | Без изменений из статьи о пирамидинге |
| PyramidEngine.mqh | MQL5\Include\Pyramid\ | Без изменений; добавлено семь новых общедоступных методов |
| PyramidEngine_additions.mqh | MQL5\Include\Pyramid\ | Новый. Вставьте содержимое в общедоступный раздел CPyramidEngine |
| BetSizingPyramidBridge.mqh | MQL5\Include\Pyramid\ | Новый. Класс адаптеров моста |
| BetSizingPyramidEA.mq5 | MQL5\Experts\ | Новый. Демонстрация подключения советника ко всем пяти точкам |
Пять точек интеграции
Пять точек интеграции позволяют решить пять отдельных проектных задач, предусмотренных в первоначальной версии CPyramidEngine, которые были жестко запрограммированы при инициализации и не имели механизма, позволяющего модулю определения размеров влиять на них. Каждая точка сопоставляет конкретный результат работы стека определения размеров с определенным параметром или решением в движке.
Пункт 1 — Калибровка размера лота по вероятности
В советнике из Части 13 lot_initial является фиксированным входным параметром. Сигналы с уровнем достоверности 0.55 и 0.85 открывают начальную позицию одинакового размера. Решение состоит в том, чтобы вычислить значение lot_initial на момент входа из выходных данных вероятностного метода:
//--- Integration Point 1: base lot from BetSizeProbability double base_lot = m_cfg.max_lots * MathAbs(prob_bet_size);
Добавления к лотам должны соответствовать строго убывающему ограничению, которое проверяет CPyramidEngine::Init(). Выразив их в виде фиксированных соотношений base-lot сохраняет ограничение во всем диапазоне выходных данных prob_bet_size:
//--- Compute proportional add-on lots, floor to broker step double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); out_initial = NormalizeDouble(MathFloor(base_lot / step) * step, 2); out_addon1 = NormalizeDouble(MathFloor(base_lot * m_cfg.addon1_ratio / step) * step, 2); out_addon2 = NormalizeDouble(MathFloor(base_lot * m_cfg.addon2_ratio / step) * step, 2);
Округление вниз, а не обычное округление, выбрано намеренно. Округление может значительно продвинуть выполнение операции к следующему шагу брокера, что, в свою очередь, может привести к нарушению ограничения после обрезки по SYMBOL_VOLUME_MIN. Округление вниз гарантирует, что полученные лоты всегда будут меньше неокругленного значения; это ограничение сохраняется и после обрезки. Если вычисленные объемы лотов после обрезки сводятся к одному и тому же минимуму, — что происходит, когда размер prob_bet_size небольшой, а у инструмента крупный шаг изменения лота, — ComputeProportionalLots() возвращает false, и вход в позицию пропускается. Это правильное поведение: сигнал с низкой степенью достоверности, который нельзя выразить в виде корректной пирамидальной структуры, вообще не должен приводить к открытию сделки.
Поля лотов в движке не фиксируются на этапе Init() для каждой сделки; они обновляются непосредственно перед вызовом OpenInitial() с помощью нового метода UpdateLots(). Это безопасно, потому что UpdateLots() отказывается записывать данные, когда пирамида уже активна.
Пункт 2 — Бюджетный шлюз на входе
Единственный входной шлюз в оригинальной версии CPyramidEngine — это IsActive(): если пирамида не запущена, то любой сигнал, прошедший проверку логики входа, может открыть ее. При этом не учитывается одновременное использование пула сигналов. BetSizeBudget вычисляет нормированную долю дисбаланса длинных и коротких позиций. Его дополнение —1 − |c_t| — представляет собой доступный запас. Мост проверяет это перед вызовом TryOpenInitial(). Требуется SeedBudgetMaxima(). Без этого запущенные максимумы начинаются с 1, поэтому самые ранние бары дают завышенные доли бюджета:
//--- Integration Point 2: budget gate BetSizeResult r = BetSizeBudget(open_t, close_t, sides, now); double headroom = 1.0 - MathAbs(r.c_t); if(headroom < m_cfg.budget_min_bet) { PrintFormat("Budget gate blocked | c_t=%.3f headroom=%.3f", r.c_t, headroom); return(false); }
Пороговое значение budget_min_bet является параметром конфигурации в SBridgeConfig. Значение 0.10 блокирует вход в позицию, если пул сигналов заполнен на 90%. Значение 0.00 полностью отключает шлюз. Правильное значение зависит от среднего периода удержания и ожидаемого максимального количества одновременных сигналов для данной стратегии.
Пункт 3 — Динамический триггер добавлений
Оригинальный движок запускает добавления, когда позиция проходит фиксированное число пунктов в прибыльную сторону. Триггеры с фиксированным количеством пунктов не учитывают прогноз модели. BetSizeDynamic отображает расхождение между текущей ценой и прогнозируемой ценой модели с помощью калиброванной сигмоидной или степенной кривой, возвращая значение в диапазоне [−1, 1]. По мере того, как сделка перемещается в благоприятном для трейдера направлении, а цена приближается к прогнозируемому значению, размер динамической ставки увеличивается. Мост отслеживает размер динамической ставки на баре входа и сравнивает его с двумя пороговыми значениями на каждом тике. После преодоления первого порогового уровня он сдвигает внутренний триггер добавления 1 до 0.1 пипса — значение настолько малое, что текущие уровни P&L позиции всегда будут удовлетворять его условию, — и движок активирует добавление при следующем вызове Manage():
//--- Integration Point 3: dynamic add-on trigger bool a1_crossed = (!m_engine.IsAddon1Open()) && (current_dyn_bet >= m_cfg.dynamic_level_1); bool a2_crossed = (m_engine.IsAddon1Open()) && (!m_engine.IsAddon2Open()) && (current_dyn_bet >= m_cfg.dynamic_level_2); if(a1_crossed) m_engine.SetAddonTriggerPips(0.1, m_engine.GetAddon2TriggerPips()); if(a2_crossed) m_engine.SetAddonTriggerPips(m_engine.GetAddon1TriggerPips(), 0.1);
SetAddonTriggerPips() может лишь сократить триггеры. Это предотвращает отмену пересечения порогового значения, если после этого размер динамической ставки кратковременно снизится. Исходные триггеры на основе пипсов остаются в качестве резервных: еслиuse_dynamic_trigger является ложным (false) в SBridgeConfig, мост ничего не делает, а исходные триггеры в пипсах остаются резервным механизмом.
Пункт 4 — Выбор размера резерва как адаптивный множитель трейлинг-стопа
BetSizeReserve отображает исходный текущий дисбаланс посредством смешанной функции распределения EF3M, подобранной по историческим данным о дисбалансе. При уменьшении размера резервной ставки дисбаланс смещается обратно к центру исторического распределения; эмпирическая поддержка сохранения позиции ослабевает. Мост вычисляет текущий коэффициент распределения средств в пирамиде как общее количество открытых лотов, деленное на max_lots, и сравнивает его с размером резервной ставки на каждом новом баре. Когда резерв падает ниже коэффициента распределения средств, условия трейлинг-стопа ужесточаются посредством reserve_tighten_mul; при восстановлении резервов восстанавливается базовый уровень трейлинг-стопа.
//--- Integration Point 4: reserve-adaptive trail double total_lots = m_live_lot_initial + m_live_lot_addon1 + m_live_lot_addon2; double alloc_ratio = (m_cfg.max_lots > 0.0) ? total_lots / m_cfg.max_lots : 0.0; if(reserve_bet < alloc_ratio) { double tight_pips = m_base_trail_pips * m_cfg.reserve_tighten_mul; double tight_step = m_base_trail_step * m_cfg.reserve_tighten_mul; m_engine.SetTrailParams(tight_pips, tight_step); } else m_engine.SetTrailParams(m_base_trail_pips, m_base_trail_step);
Этот пункт требует выполнить подготовку EF3M, описанную в Части 13: FitM2N() необходимо вызвать в OnInit() из исторической серии c_t, прежде чем можно будет использовать резервный метод. Рекомендуемый минимальный объем истории составляет 500 ставок; при меньшем количестве наблюдений подгонка смеси становится нестабильной, и адаптивный трейлинг-стоп будет колебаться.
Пункт 5 — Синхронизация массива сигналов при закрытии пирамиды
Когда CPyramidEngine обнаруживает, что исходная позиция закрыта, она вызывает ResetState() и становится неактивной. Массивы сигналов модуля расчета размеров в советнике не знают о том, что это произошло. Самая последняя запись close_t открытого сигнала по-прежнему указывает на временную метку t1 с тройным барьером, поэтому сигнал остается зарегистрированным в счетчике параллельной активности сигналов как активный. Каждый последующий вызов BetSizeProbability или BetSizeBudget завышает число активных сигналов на единицу. Мост содержит единственную булеву проверку WasJustClosed(), которую советник вызывает после каждого прохода HandleTransaction():
//--- Integration Point 5: detect pyramid close and sync arrays bool was_active = g_bridge.IsActive(); g_bridge.HandleTransaction(trans); if(g_bridge.WasJustClosed(was_active)) SyncSignalArraysOnClose();
SyncSignalArraysOnClose() проходит через g_close_t[] в обратном направлении и устанавливает временную метку самого последнего все еще открытого сигнала на TimeCurrent(). Для счетчика параллельной активности сигналов, использующего время событий с точностью до бара, аппроксимация является точной.
Дополнения к модулю
Для этих пяти точек интеграции требуется семь новых публичных методов на CPyramidEngine. Ни один из них не изменяет существующую логику; они добавляют общий доступ на чтение и запись к переменным-членам, которые уже существовали, но не были доступны извне. Вставьте содержимое файла PyramidEngine_additions.mqh в общедоступный раздел CPyramidEngine в файле PyramidEngine.mqh непосредственно после существующего объявления GetUnifiedStop().
//+------------------------------------------------------------------+ //| UpdateLots: update lot sizes before the next TryOpenInitial call | //| Returns false when the pyramid is active; no writes occur | //+------------------------------------------------------------------+ bool UpdateLots(double lot_initial, double lot_addon1, double lot_addon2) { if(m_state.active) { Print("UpdateLots called while pyramid active — ignored."); return(false); } if(lot_addon1 >= lot_initial || lot_addon2 >= lot_addon1) { Print("UpdateLots: decreasing constraint violated."); return(false); } m_lot_initial = lot_initial; m_lot_addon1 = lot_addon1; m_lot_addon2 = lot_addon2; return(true); } //+------------------------------------------------------------------+ //| SetAddonTriggerPips: advance add-on pip triggers; never retracts | //| A trigger can only decrease; passing a larger value is ignored | //+------------------------------------------------------------------+ void SetAddonTriggerPips(double trig1, double trig2) { if(trig1 < m_addon1_trigger_pips) m_addon1_trigger_pips = trig1; if(trig2 < m_addon2_trigger_pips) m_addon2_trigger_pips = trig2; } //+------------------------------------------------------------------+ //| Read-only accessors used by CPyramidBridge for dynamic triggers | //+------------------------------------------------------------------+ double GetAddon1TriggerPips(void) { return(m_addon1_trigger_pips); } double GetAddon2TriggerPips(void) { return(m_addon2_trigger_pips); } bool IsAddon1Open(void) { return(m_state.addon1_open); } bool IsAddon2Open(void) { return(m_state.addon2_open); } //+------------------------------------------------------------------+ //| SetTrailParams: update trailing stop parameters at runtime | //| Used by the reserve-adaptive trail (Integration Point 4) | //+------------------------------------------------------------------+ void SetTrailParams(double trail_pips, double trail_step) { m_trail_pips = trail_pips; m_trail_step_pips = trail_step; }
В SetAddonTriggerPips() используется строго невозрастающий алгоритм записи: он принимает новое значение триггера только в том случае, если это значение меньше текущего сохраненного. UpdateLots() сначала проверяет m_state.active и возвращает false без записи, если пирамида в данный момент активна; вызов этой функции привел бы к скрытому нарушению согласованности состояний.
Класс «Мост»
Полная версия файла BetSizingPyramidBridge.mqh находится в приложенном архиве. SBridgeConfig содержит всю конфигурацию моста в одной структуре, передаваемой в функцию Init(). Это позволяет сохранить чистоту раздела ввода советника: конфигурация моста собирается в структуру OnInit() и передается однократно, вместо того чтобы распределять десяток параметров по отдельным вызовам функций.
struct SBridgeConfig { //--- Point 1 — lot sizing double max_lots; // Maximum lots at full confidence double addon1_ratio; // lot_addon1 = lot_initial * ratio double addon2_ratio; // lot_addon2 = lot_initial * ratio //--- Point 2 — budget gate bool use_budget_gate; double budget_min_bet; // Skip entry when headroom < this value //--- Point 3 — dynamic add-on trigger bool use_dynamic_trigger; double dynamic_level_1; // |BetSizeDynamic| threshold for add-on 1 double dynamic_level_2; // |BetSizeDynamic| threshold for add-on 2 //--- Point 4 — reserve adaptive trail bool use_reserve_trail; double reserve_tighten_mul; // Trail multiplier when reserve < alloc_ratio double min_lot; // Hard floor for lot_initial };
CPyramidBridge владеет одним частным экземпляром CPyramidEngine. Кроме того, он отдельно сохраняет параметры трейлинг-стопа, чтобы его резервная логика всегда могла восстановить их после ужесточения условий. Актуальные значения объема лотов сохраняются после каждого вызова TryOpenInitial(), позволяющего выполнить расчет соотношения распределения в Пункте 4 без запроса открытых позиций в торговой системе через брокера. Открытый интерфейс предоставляет пять методов интеграции, а также сквозные каналы связи с движком для операций, которые советник должен вызывать напрямую:
bool Init(const SBridgeConfig &cfg, double trail_pips, double trail_step); bool InitEngine(int magic, int slip, double lot_i, double lot_a1, double lot_a2, double trig1, double trig2, double stop1, double stop2, bool trail, double trail_pips, double trail_step); //--- Points 1 and 3: open initial position with calibrated lots bool TryOpenInitial(long direction, double price, double sl, double prob_bet_size, double dynamic_bet_size, string comment = "Pyramid Entry"); //--- Point 2: budget gate check before entry bool IsBudgetClearForEntry(const datetime &open_t[], const datetime &close_t[], const int &sides[], datetime now); //--- Point 3: update dynamic trigger — call every tick when active void UpdateDynamicTrigger(double current_dyn_bet); //--- Point 4: reserve-adaptive trail — call once per bar when active void AdaptTrailToReserve(double reserve_bet); //--- Point 5: did the pyramid just close? bool WasJustClosed(bool was_active_before); //--- Engine pass-throughs void Manage(void); void HandleTransaction(const MqlTradeTransaction &trans); void RecoverState(void); bool IsActive(void); double GetUnifiedStop(void);
Разделение между Init() и InitEngine() сделано намеренно. Конфигурация моста не зависит от конфигурации движка: обе можно изменять в рамках отдельных циклов разработки, при этом они не будут затрагивать друг друга. Значения объемов лотов, передаваемые в InitEngine(), служат лишь заполнителями; их действие перезаписывается с помощью UpdateLots() при каждом вызове TryOpenInitial().
Подключение полноценного советника
На рисунке 3 показано, какой результат дают пять точек интеграции на синтетических данных за 30 баров: на панели (а) показан график результатов работы BetSizeProbability с линиями пороговых значений, отмечающими, когда оправдано использование добавления 1 (|bet_size| > 0.40) и добавления 2 (|bet_size| > 0.65); на панели (b) показаны соответствующие слои пирамиды лотов — та же структура, что и на рисунке 1, которая теперь динамически изменяется в зависимости от степени достоверности классификатора.

Рисунок 3. 2-панельная иллюстрация результатов работы BetSizeProbability, определяющих пропорциональное распределение лотов в пирамиде
- Панель (а): дискретизированное значение bet_size из BetSizeProbability после 30 синтетических баров. Оранжевая пунктирная линия обозначает пороговое значение для добавления 2 (0.65); зеленая пунктирная линия обозначает пороговое значение для добавления 1 (0.40).
- Панель (b): слои лотов в собранной в стек пирамиде, полученные из приведенного выше сигнала. Синие бары показывают lot_initial. Зеленый цвет показывает значение lot_addon1 (0.60 × lot_initial). Оранжевый цвет показывает значение lot_addon2 (0.30 × lot_initial), которое появляется только при |bet_size| > 0.65.
Советник полной демонстрации, BetSizingPyramidEA.mq5, связывает все пять точек интеграции между собой. Последовательность OnTick() такова:
//+------------------------------------------------------------------+ //| OnTick: entry point called on every market tick | //| Runs dynamic trigger and Manage() on every tick; reserve trail | //| and entry evaluation run on new bars only | //+------------------------------------------------------------------+ void OnTick(void) { datetime current_bar = iTime(_Symbol, PERIOD_H1, 0); bool is_new_bar = (current_bar != g_last_bar); if(is_new_bar) g_last_bar = current_bar; //--- Point 3: update dynamic trigger on every tick if(g_bridge.IsActive()) { double dyn_bet = MathAbs(BetSizeDynamic( g_current_pos, InpMaxLots * 100, SymbolInfoDouble(_Symbol, SYMBOL_BID), GetForecastPrice(), InpCalDiv * _Point, InpCalBetSize, InpDynFunc).bet_size); g_bridge.UpdateDynamicTrigger(dyn_bet); } //--- Manage pyramid on every tick bool was_active = g_bridge.IsActive(); g_bridge.Manage(); //--- Point 5: sync arrays on pyramid close if(g_bridge.WasJustClosed(was_active)) SyncSignalArraysOnClose(); if(is_new_bar) { //--- Point 4: adapt trail once per bar if(g_bridge.IsActive() && InpUseReserveTrail) { BetSizeResult rv = BetSizeReserve(g_open_t, g_close_t, g_sides, TimeCurrent(), g_reserve_params); g_bridge.AdaptTrailToReserve(MathAbs(rv.bet_size)); } if(!g_bridge.IsActive()) CheckForEntry(); } }
Порядок вызовов отражает различные временные масштабы пяти точек. Динамическая проверка триггера и запуск функции Manage() выполняются на каждом тике, поскольку триггеры добавлений должны реагировать на движения цены внутри бара. Резервный уровень трейлинг-стопа и сигнал входа обрабатываются только на новых барах; трейлинг-стоп адаптируется с баровой дискретностью.
CheckForEntry() сначала вызывает IsBudgetClearForEntry() и незамедлительно возвращает, если шлюз заблокирован. Затем функция оценивает сигнал, вызывает BetSizeProbability для взвешенного по степени достоверности значения bet_size, отклоняет входы, в которых направление вероятностного метода не совпадает с направлением сигнала, и вызывает TryOpenInitial() с учетом как вероятности, так и динамических размеров ставок:
//+------------------------------------------------------------------+ //| CheckForEntry: evaluate signal and submit via the bridge | //| Applies the budget gate (Point 2), computes the probability and | //| dynamic bet sizes, then calls TryOpenInitial | //+------------------------------------------------------------------+ void CheckForEntry(void) { datetime now = TimeCurrent(); //--- Point 2: budget gate if(!g_bridge.IsBudgetClearForEntry(g_open_t, g_close_t, g_sides, now)) return; // ... evaluate signal, determine direction, price, sl ... //--- Point 1 source: probability bet size BetSizeResult prob_r = BetSizeProbability( g_open_t, g_close_t, g_prob, g_pred, 2, InpStepSize, InpAvgActive, now); if(direction == POSITION_TYPE_BUY && prob_r.bet_size <= 0.0) return; if(direction == POSITION_TYPE_SELL && prob_r.bet_size >= 0.0) return; //--- Point 3 source: dynamic bet size at entry bar double dyn_bet = MathAbs(BetSizeDynamic( 0, InpMaxLots * 100, price, GetForecastPrice(), InpCalDiv * _Point, InpCalBetSize, InpDynFunc).bet_size); if(g_bridge.TryOpenInitial(direction, price, sl, prob_r.bet_size, dyn_bet, "Pyramid Entry")) AppendNewSignal(now, direction, MathAbs(prob_r.bet_size)); }
Перед развертыванием необходимо принять два решения по калибровке. Во-первых, соотношения addon1_ratio и addon2_ratio должны соответствовать строгому ограничению: addon1_ratio должно быть меньше 1.0, а addon2_ratio должно быть меньше, чем addon1_ratio. Для инструментов с крупным шагом изменения объема лота перед началом реальной торговли необходимо убедиться, что вычисленные объема лота выдержат обрезку до SYMBOL_VOLUME_MIN. Во-вторых, dynamic_level_1 и dynamic_level_2 необходимо калибровать с учетом исторических результатов работы BetSizeDynamic выбранной стратегии. Запустите стек расчета размера ставки на данных прогрева и исследуйте распределение значений |bet_size| на барах, где позиция была прибыльной; медиана и 75-й процентиль этого распределения являются обоснованными точками отсчета для двух пороговых значений.
Заключение
Модуль определения размеров ставок из Части 13 и механизм пирамидирования из статьи Управление позициями: Безопасный пирамидинг с единым стопом в MQL5 решают разные части одной и той же задачи. Модуль определения размера ставки отвечает на вопрос о том, какого объема капитала заслуживает тот или иной сигнал, учитывая достоверность классификатора, параллельность меток и эмпирическое распределение прошлых позиций. Пирамидный движок отвечает на вопрос о том, как структурировать капитал по уровням, используя математически доказуемое свойство снижения риска — то же самое свойство, что показано на рисунке 1: каждое последующее добавление меньше предыдущего, единый стоп-лосс продвигается вперед, а общий долларовый риск снижается на каждом этапе. Эти две системы не были предназначены для совместной работы; для их соединения требуется тонкий адаптерный слой, который преобразует выходные значения модуля расчета размера позиции в параметры движка.
CPyramidBridge реализует этот адаптер в пяти точках интеграции. Вероятностный метод определяет первоначальный размер лота. Бюджетный метод ограничивает вход, когда пул сигналов заполнен до предела. Динамический метод продвигает триггеры добавлений вперед, когда их поддерживает расхождение цен. Метод резервирования ужесточает трейлинг-стоп, когда эмпирическое распределение дисбаланса сокращается ниже текущего уровня распределения средств. Проверка синхронизации обеспечивает согласованность массивов сигналов при закрытии пирамиды. Каждая точка поддается независимому управлению через структуру SBridgeConfig; отключение любого флага восстанавливает исходное поведение движка по данному аспекту.
В Части 16 серии «Архитектура системы машинного обучения в MetaTrader 5» слой определения размера связан с фреймворком тестирования CPCV на исторических данных в тестере стратегий MetaTrader 5. Вероятностные оценки, поступающие в BetSizeProbability, подвержены систематической ошибке, вызванной дисбалансом классов и ошибкой калибровки; их характеристика и коррекция описаны в Части 12.
Список файлов приложения
| Файл | Размещение | Зависит от | Описание | |
|---|---|---|---|---|
| 1 | BetSizingUtils.mqh | MQL5\Include\BetSizing\ | — | Без изменений из Части 13. NormCDF, NormICDF, NormPDF, RawMoments, SweepLineActiveCounts, BetSizeResult, Clamp, MathSign. |
| 2 | EF3M.mqh | MQL5\Include\BetSizing\ | BetSizingUtils.mqh | Без изменений из Части 13. M2NParams, DeriveComponentParams, FitM2N, MixtureCDF, ReserveBetSize. |
| 3 | Ch10Snippets.mqh | MQL5\Include\BetSizing\ | BetSizingUtils.mqh | Без изменений из Части 13. GetSignal, AvgActiveSignals, DiscreteSignal, SigmoidBetSize, PowerBetSize, GetW, LimitPrice. |
| 4 | BetSizing.mqh | MQL5\Include\BetSizing\ | Ch10Snippets.mqh, EF3M.mqh | Без изменений из Части 13. BetSizeProbability, BetSizeDynamic, BetSizeBudget, BetSizeReserve, SeedBudgetMaxima. |
| 5 | PyramidUtils.mqh | MQL5\Include\Pyramid\ | — | Без изменений из статьи о пирамидинге Вспомогательные функции для расчета стоимости пункта и проверка стоп-лосса на уровне брокера. |
| 6 | PyramidEngine.mqh | MQL5\Include\Pyramid\ | PyramidUtils.mqh | Оригинальная версия CPyramidEngine получает семь новых общедоступных методов: UpdateLots, SetAddonTriggerPips, GetAddon1TriggerPips, GetAddon2TriggerPips, IsAddon1Open, IsAddon2Open, SetTrailParams. Вставить в CPyramidEngine |
| 7 | BetSizingPyramidBridge.mqh | MQL5\Include\Pyramid\ | BetSizing.mqh, PyramidEngine.mqh | Структура SBridgeConfig и класс CPyramidBridge, реализующие все пять точек интеграции. |
| 8 | BetSizingPyramidEA.mq5 | MQL5\Experts\ | BetSizingPyramidBridge.mqh | Демонстрация советника, подключающего сигнал пересечения EMA к мосту. Все пять точек интеграции активны. Функцию CheckForEntry() можно заменить любыми выходными данными классификатора. |
Ссылки
- López de Prado, M. (2018). Advances in Financial Machine Learning. John Wiley & Sons. Глава 10.
- López de Prado, M. and Foreman, M. (2014). A mixture of two Gaussians approach to mathematical portfolio oversight: The EF3M algorithm. Quantitative Finance, 14(5), 913–930.
- Hector, T. M. (2026). Управление позициями: Безопасный пирамидинг с единым стопом в MQL5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22684
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Автоматизация торговых стратегий в MQL5 (Часть 46): Снятие ликвидности после пробоя структуры (BoS)
От начального до среднего уровня: Очереди, списки и деревья (V)
Как мы построили самую мощную торговую платформу с Machine Learning: хроника развития MQL и MetaTrader по архивам, форумам и релизам
Автоматизация торговых стратегий в MQL5 (Часть 45): Инверсный разрыв справедливой стоимости (IFVG)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования