Ind_Aleks_ICT_Entry_V2_TS_I.../docs/plans/2026-03-04-dynamic-equilibrium-design.md
Alexandr Nikolaev d32645030d feat(CEquilibrium): динамический Equilibrium после пробоя BOS/MSS
Добавлен динамический режим в CEquilibrium: после пробоя BOS/MSS
линия Equilibrium автоматически обновляется по новым экстремумам цены
(running max/min), пока не появится следующий BOS/MSS.

Изменения:
- Новые поля: m_dynamic_mode, m_break_dir, m_track_high/low, m_break_bar
- Calculate() принимает price_high/price_low для трекинга
- Backfill от бара пробоя до текущего при каждом обновлении экстремума
- AdjustShift корректирует m_break_bar при новом баре (AS_SERIES)
- Геттеры также обновляются в dynamic mode
- CSwingDetector передаёт high[bar]/low[bar] в Calculate()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:51:14 +03:00

6.2 KiB

Dynamic Equilibrium After BOS/MSS Breakout

Дата: 2026-03-04 Модуль: CEquilibrium.mqh Статус: Утверждён

Проблема

После пробоя BOS/MSS уровня цена продолжает двигаться, но до формирования нового BOS/MSS линия Equilibrium остаётся на старых подтверждённых уровнях (in_high/in_low). Equilibrium становится неактуальным на время между пробоями.

Решение

Добавить динамический режим в CEquilibrium: после BOS/MSS пробоя отслеживать экстремум цены по направлению пробоя и пересчитывать Equilibrium в реальном времени.

Подход

Вся логика внутри CEquilibrium (Подход 1). Добавляются 2 параметра price_high, price_low в Calculate().

Новые поля CEquilibrium

//--- Динамический режим после пробоя BOS/MSS
bool   m_dynamic_mode;     // Активен ли динамический режим
int    m_break_dir;        // Направление пробоя: 1=вверх, -1=вниз
double m_track_high;       // Running max(high[]) с момента пробоя
double m_track_low;        // Running min(low[]) с момента пробоя
int    m_break_bar;        // AS_SERIES индекс бара начала трекинга

Изменение сигнатуры Calculate()

void Calculate(int bar, const SStructureSwingState &state,
               double price_high, double price_low,  // НОВЫЕ параметры
               double &buf_equilibrium[],
               double &buf_premium2[],
               double &buf_discount2[],
               int rates_total);

Логика динамического режима

Активация (при BOS/MSS)

bool is_bos_mss = state.bos_up || state.bos_down || state.mss_up || state.mss_down;

if(is_bos_mss)
{
   //--- Предыдущий dynamic mode автоматически завершается
   m_dynamic_mode = true;
   m_break_bar = bar;

   if(state.bos_up || state.mss_up)
   {
      m_break_dir = 1;
      m_track_high = price_high;
      m_track_low = EMPTY_VALUE;
   }
   else
   {
      m_break_dir = -1;
      m_track_low = price_low;
      m_track_high = EMPTY_VALUE;
   }
}

Обновление трекинга (каждый бар в dynamic mode)

if(m_dynamic_mode && !is_bos_mss)
{
   if(m_break_dir == 1)
      m_track_high = MathMax(m_track_high, price_high);
   else
      m_track_low = MathMin(m_track_low, price_low);
}

Расчёт динамического Equilibrium (буферы)

if(m_dynamic_mode)
{
   double dyn_eq = EMPTY_VALUE;
   double dyn_prem2 = EMPTY_VALUE;
   double dyn_disc2 = EMPTY_VALUE;

   if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
   {
      double range = m_track_high - state.in_low;
      if(range > 0.0)
      {
         dyn_eq = m_track_high - (pre_dis_cal * range);   // Discount
         if(m_eq_con)
            dyn_disc2 = m_track_high - (pre_dis_cal2 * range);
      }
   }
   else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
   {
      double range = state.in_high - m_track_low;
      if(range > 0.0)
      {
         dyn_eq = m_track_low + (pre_dis_cal * range);    // Premium
         if(m_eq_con)
            dyn_prem2 = m_track_low + (pre_dis_cal2 * range);
      }
   }

   //--- Backfill от бара пробоя до текущего
   if(dyn_eq != EMPTY_VALUE)
   {
      int fill_start = MathMin(m_break_bar, rates_total - 1);
      for(int i = fill_start; i >= bar && i >= 0; i--)
      {
         buf_equilibrium[i] = dyn_eq;
         if(dyn_prem2 != EMPTY_VALUE) buf_premium2[i] = dyn_prem2;
         if(dyn_disc2 != EMPTY_VALUE) buf_discount2[i] = dyn_disc2;
      }
   }
}

AdjustShift — корректировка при новом баре

void AdjustShift(int shift)
{
   if(m_bar_eq_prev >= 0) m_bar_eq_prev += shift;
   if(m_break_bar >= 0)   m_break_bar += shift;
}

Деактивация

Dynamic mode завершается только при следующем BOS/MSS. На том же баре:

  1. Старый dynamic mode выключается
  2. Обычная логика (structural_event = ch_in_h || ch_in_l) обновляет m_fixed_eq
  3. Новый dynamic mode включается (если это BOS/MSS)

Простое подтверждение свинга (ch_in_h/ch_in_l без BOS/MSS) НЕ завершает dynamic mode.

Геттеры (для Entry сигналов)

В dynamic mode геттеры тоже обновляются:

if(m_dynamic_mode)
{
   if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
   {
      double range = m_track_high - state.in_low;
      if(range > 0.0)
      {
         m_discount = m_track_high - (pre_dis_cal * range);
         if(m_eq_con) m_discount2 = m_track_high - (pre_dis_cal2 * range);
      }
   }
   else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
   {
      double range = state.in_high - m_track_low;
      if(range > 0.0)
      {
         m_premium = m_track_low + (pre_dis_cal * range);
         if(m_eq_con) m_premium2 = m_track_low + (pre_dis_cal2 * range);
      }
   }
}

Что НЕ меняется

  • Обычная логика structural_event (ch_in_h/ch_in_l) — продолжает работать
  • m_fixed_eq — обновляется как раньше, но в dynamic mode буфер перезаписывается
  • FullReset() — сбрасывает dynamic state
  • Логика IsInDiscountZone() / IsInPremiumZone() — без изменений

Затронутые файлы

Файл Изменения
CEquilibrium.mqh Новые поля, изменённый Calculate(), AdjustShift(), FullReset(), Init()
CSwingDetector.mqh Обновить вызов equilibrium->Calculate() — добавить high[bar], low[bar]