//+------------------------------------------------------------------+ //| 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