Ind_Aleks_ICT_Entry_V2_TS_I.../CEquilibrium.mqh
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

441 lines
17 KiB
MQL5

//+------------------------------------------------------------------+
//| CEquilibrium.mqh |
//| Модуль расчёта Premium/Discount зон (Equilibrium) |
//| для ICT Entry индикатора |
//+------------------------------------------------------------------+
#ifndef CEQUILIBRIUM_MQH
#define CEQUILIBRIUM_MQH
//--- Требует SStructureSwingState из CSwingDetector.mqh
//--- Подключать ПОСЛЕ CSwingDetector.mqh
//+------------------------------------------------------------------+
//| Класс CEquilibrium — расчёт Premium/Discount зон |
//+------------------------------------------------------------------+
class CEquilibrium
{
public:
CEquilibrium();
~CEquilibrium();
//--- Инициализация
bool Init(int pre_dis, int pre_dis2, bool eq_con);
//--- Расчёт на каждом баре (вызывается из цикла CSwingDetector)
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);
//--- Полный сброс при prev_calculated == 0
void FullReset(double &buf_equilibrium[],
double &buf_premium2[],
double &buf_discount2[],
int rates_total);
//--- Корректировка AS_SERIES индекса при появлении нового бара
void AdjustShift(int shift) { if(m_bar_eq_prev >= 0) m_bar_eq_prev += shift; if(m_break_bar >= 0) m_break_bar += shift; }
//--- Геттеры для Entry условий
double GetPremium() const { return m_premium; }
double GetDiscount() const { return m_discount; }
double GetPremium2() const { return m_premium2; }
double GetDiscount2() const { return m_discount2; }
//--- Фильтры для Entry условий
bool IsInDiscountZone(double ifvg_bottom) const;
bool IsInPremiumZone(double ifvg_top) const;
private:
//--- Параметры
int m_pre_dis; // % для первого уровня (default 50)
int m_pre_dis2; // % для второго уровня (default 80)
bool m_eq_con; // Включить фильтрацию по зонам
//--- Состояние Premium/Discount (динамические значения для геттеров)
double m_premium;
double m_discount;
double m_premium2;
double m_discount2;
int m_bar_eq_prev; // Предыдущий bar_eq для контроля backfill
double m_fixed_eq; // Зафиксированный уровень для буфера Equilibrium
double m_fixed_prem2; // Зафиксированный уровень для буфера Premium2
double m_fixed_disc2; // Зафиксированный уровень для буфера Discount2
//--- Динамический режим после пробоя 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 индекс бара начала трекинга
};
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CEquilibrium::CEquilibrium()
{
m_pre_dis = 50;
m_pre_dis2 = 80;
m_eq_con = false;
m_premium = EMPTY_VALUE;
m_discount = EMPTY_VALUE;
m_premium2 = EMPTY_VALUE;
m_discount2 = EMPTY_VALUE;
m_bar_eq_prev = -1;
m_fixed_eq = EMPTY_VALUE;
m_fixed_prem2 = EMPTY_VALUE;
m_fixed_disc2 = EMPTY_VALUE;
m_dynamic_mode = false;
m_break_dir = 0;
m_track_high = EMPTY_VALUE;
m_track_low = EMPTY_VALUE;
m_break_bar = -1;
}
//+------------------------------------------------------------------+
//| Деструктор |
//+------------------------------------------------------------------+
CEquilibrium::~CEquilibrium()
{
}
//+------------------------------------------------------------------+
//| Инициализация параметров |
//+------------------------------------------------------------------+
bool CEquilibrium::Init(int pre_dis, int pre_dis2, bool eq_con)
{
m_pre_dis = pre_dis;
m_pre_dis2 = pre_dis2;
m_eq_con = eq_con;
m_premium = EMPTY_VALUE;
m_discount = EMPTY_VALUE;
m_premium2 = EMPTY_VALUE;
m_discount2 = EMPTY_VALUE;
m_bar_eq_prev = -1;
m_fixed_eq = EMPTY_VALUE;
m_fixed_prem2 = EMPTY_VALUE;
m_fixed_disc2 = EMPTY_VALUE;
m_dynamic_mode = false;
m_break_dir = 0;
m_track_high = EMPTY_VALUE;
m_track_low = EMPTY_VALUE;
m_break_bar = -1;
return true;
}
//+------------------------------------------------------------------+
//| Полный сброс — вызывать при prev_calculated == 0 |
//+------------------------------------------------------------------+
void CEquilibrium::FullReset(double &buf_equilibrium[],
double &buf_premium2[],
double &buf_discount2[],
int rates_total)
{
ArrayInitialize(buf_equilibrium, EMPTY_VALUE);
ArrayInitialize(buf_premium2, EMPTY_VALUE);
ArrayInitialize(buf_discount2, EMPTY_VALUE);
m_premium = EMPTY_VALUE;
m_discount = EMPTY_VALUE;
m_premium2 = EMPTY_VALUE;
m_discount2 = EMPTY_VALUE;
m_bar_eq_prev = -1;
m_fixed_eq = EMPTY_VALUE;
m_fixed_prem2 = EMPTY_VALUE;
m_fixed_disc2 = EMPTY_VALUE;
m_dynamic_mode = false;
m_break_dir = 0;
m_track_high = EMPTY_VALUE;
m_track_low = EMPTY_VALUE;
m_break_bar = -1;
}
//+------------------------------------------------------------------+
//| Проверка: iFVG в Discount зоне (для Buy Entry) |
//| Discount2 <= ifvg_bottom <= Discount |
//+------------------------------------------------------------------+
bool CEquilibrium::IsInDiscountZone(double ifvg_bottom) const
{
if(!m_eq_con) return true;
if(m_discount == EMPTY_VALUE || m_discount2 == EMPTY_VALUE) return false;
return (ifvg_bottom <= m_discount && ifvg_bottom >= m_discount2);
}
//+------------------------------------------------------------------+
//| Проверка: iFVG в Premium зоне (для Sell Entry) |
//| Premium <= ifvg_top <= Premium2 |
//+------------------------------------------------------------------+
bool CEquilibrium::IsInPremiumZone(double ifvg_top) const
{
if(!m_eq_con) return true;
if(m_premium == EMPTY_VALUE || m_premium2 == EMPTY_VALUE) return false;
return (ifvg_top >= m_premium && ifvg_top <= m_premium2);
}
//+------------------------------------------------------------------+
//| Расчёт Premium/Discount и заполнение буферов |
//| Три случая по ARCHITECTURE_SWINGS_BOS_MSS.md секция 7.4 |
//+------------------------------------------------------------------+
void CEquilibrium::Calculate(int bar, const SStructureSwingState &state,
double price_high, double price_low,
double &buf_equilibrium[],
double &buf_premium2[],
double &buf_discount2[],
int rates_total)
{
double pre_dis_cal = m_pre_dis / 100.0;
double pre_dis_cal2 = m_pre_dis2 / 100.0;
//=== Динамические значения для геттеров (Entry сигналы) ===
//--- Case 1: CF_In_low=true, CF_In_High=false (ждём пробой вверх)
if(state.cf_in_low && !state.cf_in_high
&& state.in_low != EMPTY_VALUE && state.i_high != EMPTY_VALUE)
{
double range = state.i_high - state.in_low;
if(range > 0.0)
{
m_discount = state.i_high - (pre_dis_cal * range);
m_premium = EMPTY_VALUE;
if(m_eq_con)
{
m_discount2 = state.i_high - (pre_dis_cal2 * range);
m_premium2 = EMPTY_VALUE;
}
}
}
//--- Case 2: CF_In_High=true, CF_In_low=false (ждём пробой вниз)
else if(state.cf_in_high && !state.cf_in_low
&& state.in_high != EMPTY_VALUE && state.i_low != EMPTY_VALUE)
{
double range = state.in_high - state.i_low;
if(range > 0.0)
{
m_premium = state.i_low + (pre_dis_cal * range);
m_discount = EMPTY_VALUE;
if(m_eq_con)
{
m_premium2 = state.i_low + (pre_dis_cal2 * range);
m_discount2 = EMPTY_VALUE;
}
}
}
//--- Case 3: Оба подтверждены
else if(state.cf_in_high && state.cf_in_low
&& state.in_high != EMPTY_VALUE && state.in_low != EMPTY_VALUE)
{
double range = state.in_high - state.in_low;
m_premium = state.in_low + (pre_dis_cal * range);
m_discount = state.in_high - (pre_dis_cal * range);
if(m_eq_con)
{
m_premium2 = state.in_low + (pre_dis_cal2 * range);
m_discount2 = state.in_high - (pre_dis_cal2 * range);
}
}
//=== Динамический режим: трекинг после BOS/MSS ===
bool is_bos_mss = state.bos_up || state.bos_down || state.mss_up || state.mss_down;
if(is_bos_mss)
{
//--- Новый BOS/MSS → начать (или перезапустить) динамический режим
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;
}
}
else if(m_dynamic_mode)
{
//--- Обновление трекинга (без 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);
}
//=== Динамический пересчёт геттеров в dynamic mode ===
if(m_dynamic_mode)
{
if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
{
double dyn_range = m_track_high - state.in_low;
if(dyn_range > 0.0)
{
m_discount = m_track_high - (pre_dis_cal * dyn_range);
m_premium = EMPTY_VALUE;
if(m_eq_con)
{
m_discount2 = m_track_high - (pre_dis_cal2 * dyn_range);
m_premium2 = EMPTY_VALUE;
}
}
}
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
{
double dyn_range = state.in_high - m_track_low;
if(dyn_range > 0.0)
{
m_premium = m_track_low + (pre_dis_cal * dyn_range);
m_discount = EMPTY_VALUE;
if(m_eq_con)
{
m_premium2 = m_track_low + (pre_dis_cal2 * dyn_range);
m_discount2 = EMPTY_VALUE;
}
}
}
}
//=== Стабильные значения для буферов (ТОЛЬКО подтверждённые in_high/in_low) ===
double draw_eq = EMPTY_VALUE;
double draw_prem2 = EMPTY_VALUE;
double draw_disc2 = EMPTY_VALUE;
int bar_eq = -1;
if(state.in_high != EMPTY_VALUE && state.in_low != EMPTY_VALUE
&& (state.cf_in_high || state.cf_in_low))
{
double range = state.in_high - state.in_low;
if(range > 0.0)
{
double stable_premium = state.in_low + (pre_dis_cal * range);
double stable_discount = state.in_high - (pre_dis_cal * range);
if(state.cf_in_low && !state.cf_in_high)
draw_eq = stable_discount;
else if(state.cf_in_high && !state.cf_in_low)
draw_eq = stable_premium;
else
draw_eq = (state.in_dir_big == 1) ? stable_discount : stable_premium;
if(m_eq_con)
{
double stable_premium2 = state.in_low + (pre_dis_cal2 * range);
double stable_discount2 = state.in_high - (pre_dis_cal2 * range);
if(state.cf_in_low && !state.cf_in_high)
draw_disc2 = stable_discount2;
else if(state.cf_in_high && !state.cf_in_low)
draw_prem2 = stable_premium2;
else
{
draw_prem2 = stable_premium2;
draw_disc2 = stable_discount2;
}
}
bar_eq = MathMax(state.bar_in_high, state.bar_in_low);
}
}
//=== Отрисовка ===
if(bar >= 0 && bar < rates_total)
{
bool structural_event = state.ch_in_h || state.ch_in_l;
//--- При структурном событии — обновить уровень и backfill от нового свинга
if(draw_eq != EMPTY_VALUE && structural_event)
{
m_fixed_eq = draw_eq;
m_fixed_prem2 = draw_prem2;
m_fixed_disc2 = draw_disc2;
//--- Backfill от изменённого свинга
int fill_from = -1;
if(state.ch_in_h && state.bar_in_high >= 0)
fill_from = state.bar_in_high;
if(state.ch_in_l && state.bar_in_low >= 0)
fill_from = (fill_from >= 0) ? MathMax(fill_from, state.bar_in_low) : state.bar_in_low;
if(fill_from >= 0)
{
for(int i = fill_from; i > bar && i < rates_total; i--)
buf_equilibrium[i] = m_fixed_eq;
if(m_fixed_prem2 != EMPTY_VALUE)
{
for(int i = fill_from; i > bar && i < rates_total; i--)
buf_premium2[i] = m_fixed_prem2;
}
if(m_fixed_disc2 != EMPTY_VALUE)
{
for(int i = fill_from; i > bar && i < rates_total; i--)
buf_discount2[i] = m_fixed_disc2;
}
}
}
else if(draw_eq == EMPTY_VALUE)
{
m_fixed_eq = EMPTY_VALUE;
m_fixed_prem2 = EMPTY_VALUE;
m_fixed_disc2 = EMPTY_VALUE;
}
//--- Записать зафиксированный уровень в текущий бар
buf_equilibrium[bar] = m_fixed_eq;
buf_premium2[bar] = m_fixed_prem2;
buf_discount2[bar] = m_fixed_disc2;
//=== Динамическая отрисовка: перезаписать буфер, если dynamic mode активен ===
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 dyn_range = m_track_high - state.in_low;
if(dyn_range > 0.0)
{
dyn_eq = m_track_high - (pre_dis_cal * dyn_range);
if(m_eq_con)
dyn_disc2 = m_track_high - (pre_dis_cal2 * dyn_range);
}
}
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
{
double dyn_range = state.in_high - m_track_low;
if(dyn_range > 0.0)
{
dyn_eq = m_track_low + (pre_dis_cal * dyn_range);
if(m_eq_con)
dyn_prem2 = m_track_low + (pre_dis_cal2 * dyn_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;
//--- Всегда перезаписываем — чтобы очистить устаревшие стабильные уровни
buf_premium2[i] = dyn_prem2;
buf_discount2[i] = dyn_disc2;
}
}
}
}
m_bar_eq_prev = bar_eq;
}
#endif // CEQUILIBRIUM_MQH