//+------------------------------------------------------------------+ //| CSwingDetector.mqh | //| Модуль обнаружения свингов (Liquidity + Structure) | //| и логики BOS/MSS для ICT Entry индикатора | //+------------------------------------------------------------------+ #ifndef CSWING_DETECTOR_MQH #define CSWING_DETECTOR_MQH //--- Метод валидации pullback для Structure Swings enum ENUM_VALID_METHOD { VALID_CLOSE = 0, // Валидация по Close VALID_HIGHLOW = 1 // Валидация по High/Low }; //+------------------------------------------------------------------+ //| Структура состояния Liquidity Swings | //+------------------------------------------------------------------+ struct SLiquiditySwingState { int sw_dir; // Направление: 1=ждём High, -1=ждём Low double sw_high; // Текущий Swing High double sw_low; // Текущий Swing Low int bar_sw_high; // Бар Swing High (индекс AS_SERIES) int bar_sw_low; // Бар Swing Low (индекс AS_SERIES) double ss_high; // Промежуточный максимум (макс high с последнего свинга) double ss_low; // Промежуточный минимум (мин low с последнего свинга) int b_sw_high; // Бар промежуточного максимума int b_sw_low; // Бар промежуточного минимума bool ch_swh; // Флаг: Swing High изменился на этом баре bool ch_swl; // Флаг: Swing Low изменился на этом баре }; //+------------------------------------------------------------------+ //| Структура состояния Structure Swings + BOS/MSS | //+------------------------------------------------------------------+ struct SStructureSwingState { int in_dir_big; // Глобальный тренд: 0=не определён, 1=бычий, -1=медвежий int in_dir_small; // Локальное направление: 1=ждём High, -1=ждём Low double in_high; // Подтверждённый Structure High double in_low; // Подтверждённый Structure Low int bar_in_high; // Бар Structure High int bar_in_low; // Бар Structure Low double i_high; // Неподтверждённый экстремум High double i_low; // Неподтверждённый экстремум Low int bar_i_high; // Бар неподтверждённого High int bar_i_low; // Бар неподтверждённого Low bool cf_in_high; // Structure High подтверждён и активен bool cf_in_low; // Structure Low подтверждён и активен bool ch_in_h; // Structure High изменился на этом баре bool ch_in_l; // Structure Low изменился на этом баре //--- Флаги событий BOS/MSS bool bos_up; // Break of Structure вверх (продолжение тренда) bool bos_down; // Break of Structure вниз (продолжение тренда) bool mss_up; // Market Structure Shift вверх (смена тренда) bool mss_down; // Market Structure Shift вниз (смена тренда) }; //+------------------------------------------------------------------+ //| Класс CSwingDetector — обнаружение свингов и BOS/MSS | //+------------------------------------------------------------------+ class CSwingDetector { public: CSwingDetector(); ~CSwingDetector(); //--- Инициализация — вызывать из OnInit() bool Init(int len, int len_in, ENUM_VALID_METHOD valid_method, int calc_bars); //--- Главный метод расчёта — вызывать из OnCalculate() //--- Все массивы должны быть AS_SERIES = true void Calculate(const int rates_total, const int prev_calculated, const double &high[], const double &low[], const double &open[], const double &close[], double &buf_zigzag[], double &buf_liq_swing[], double &buf_str_swing[], double &buf_bos_up[], double &buf_bos_dn[], double &buf_mss_up[], double &buf_mss_dn[], double &calc_ss_high[], double &calc_ss_low[], double &calc_b_sw_high[], double &calc_b_sw_low[], double &calc_sw_high[], double &calc_sw_low[], double &calc_bar_sw_high[], double &calc_bar_sw_low[]); //--- Доступ к состоянию для других модулей SLiquiditySwingState GetLiquidityState() const { return m_liq; } SStructureSwingState GetStructureState() const { return m_str; } private: //--- Параметры int m_len; // Сила пивота для Liquidity Swings int m_len_in; // Сила пивота для Structure Swings ENUM_VALID_METHOD m_valid_method; // Метод валидации pullback int m_calc_bars; // Количество баров для расчёта //--- Состояние Liquidity Swings SLiquiditySwingState m_liq; //--- Состояние Structure Swings SStructureSwingState m_str; bool m_valid_cf_high; // Valid Pullback: откат подтверждён для High bool m_valid_cf_low; // Valid Pullback: откат подтверждён для Low bool m_valid_pivot_high; // Valid Pivot: пивот существует для High bool m_valid_pivot_low; // Valid Pivot: пивот существует для Low int m_con_first; // Счётчик инициализации (первые 2 свинга) //--- Предыдущие значения для Structure Swings (lookback [1]) double m_prev_i_high; double m_prev_i_low; int m_prev_bar_i_high; int m_prev_bar_i_low; bool m_prev_cf_in_high; bool m_prev_cf_in_low; int m_prev_bar_in_high; int m_prev_bar_in_low; double m_prev_in_high; double m_prev_in_low; //--- Внутренние методы void ResetState(); double PivotHigh(const double &high[], int bar, int strength, int rates_total); double PivotLow(const double &low[], int bar, int strength, int rates_total); void ProcessLiquiditySwing(int bar, const double &high[], const double &low[], const double &open[], const double &close[], double &buf_zigzag[], double &buf_liq_swing[], int rates_total, double &calc_ss_high[], double &calc_ss_low[], double &calc_b_sw_high[], double &calc_b_sw_low[], double &calc_sw_high[], double &calc_sw_low[], double &calc_bar_sw_high[], double &calc_bar_sw_low[]); void ProcessStructureSwing(int bar, const double &high[], const double &low[], const double &close[], double &buf_str_swing[], double &buf_bos_up[], double &buf_bos_dn[], double &buf_mss_up[], double &buf_mss_dn[], int rates_total); }; //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CSwingDetector::CSwingDetector() { m_len = 5; m_len_in = 10; m_valid_method = VALID_CLOSE; m_calc_bars = 1000; } //+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ CSwingDetector::~CSwingDetector() { } //+------------------------------------------------------------------+ //| Инициализация параметров | //+------------------------------------------------------------------+ bool CSwingDetector::Init(int len, int len_in, ENUM_VALID_METHOD valid_method, int calc_bars) { m_len = len; m_len_in = len_in; m_valid_method = valid_method; m_calc_bars = calc_bars; ResetState(); return true; } //+------------------------------------------------------------------+ //| Сброс всех состояний | //+------------------------------------------------------------------+ void CSwingDetector::ResetState() { //--- Liquidity Swings m_liq.sw_dir = 0; m_liq.sw_high = EMPTY_VALUE; m_liq.sw_low = EMPTY_VALUE; m_liq.bar_sw_high = 0; m_liq.bar_sw_low = 0; m_liq.ss_high = EMPTY_VALUE; m_liq.ss_low = EMPTY_VALUE; m_liq.b_sw_high = 0; m_liq.b_sw_low = 0; m_liq.ch_swh = false; m_liq.ch_swl = false; //--- Structure Swings m_str.in_dir_big = 0; m_str.in_dir_small = 0; m_str.in_high = EMPTY_VALUE; m_str.in_low = EMPTY_VALUE; m_str.bar_in_high = 0; m_str.bar_in_low = 0; m_str.i_high = EMPTY_VALUE; m_str.i_low = EMPTY_VALUE; m_str.bar_i_high = 0; m_str.bar_i_low = 0; m_str.cf_in_high = false; m_str.cf_in_low = false; m_str.ch_in_h = false; m_str.ch_in_l = false; m_str.bos_up = false; m_str.bos_down = false; m_str.mss_up = false; m_str.mss_down = false; //--- Valid Pullback m_valid_cf_high = false; m_valid_cf_low = false; m_valid_pivot_high = false; m_valid_pivot_low = false; m_con_first = 0; //--- Предыдущие значения Structure m_prev_i_high = EMPTY_VALUE; m_prev_i_low = EMPTY_VALUE; m_prev_bar_i_high = 0; m_prev_bar_i_low = 0; m_prev_cf_in_high = false; m_prev_cf_in_low = false; m_prev_bar_in_high = 0; m_prev_bar_in_low = 0; m_prev_in_high = EMPTY_VALUE; m_prev_in_low = EMPTY_VALUE; } //+------------------------------------------------------------------+ //| Обнаружение Pivot High | //| Проверяет: является ли high[bar+strength] максимумом | //| в окне [bar, bar+2*strength] (AS_SERIES индексация) | //| Возвращает значение пивота или EMPTY_VALUE если пивота нет | //+------------------------------------------------------------------+ double CSwingDetector::PivotHigh(const double &high[], int bar, int strength, int rates_total) { //--- Центр пивота — bar + strength баров назад от текущей позиции int pivot_bar = bar + strength; //--- Проверяем что хватает баров с обеих сторон if(pivot_bar + strength >= rates_total || pivot_bar < 0) return EMPTY_VALUE; double pivot_val = high[pivot_bar]; //--- Проверяем левую сторону (более старые бары, индексы > pivot_bar в AS_SERIES) for(int i = 1; i <= strength; i++) { if(high[pivot_bar + i] > pivot_val) return EMPTY_VALUE; } //--- Проверяем правую сторону (более новые бары, индексы < pivot_bar в AS_SERIES) for(int i = 1; i <= strength; i++) { if(high[pivot_bar - i] > pivot_val) return EMPTY_VALUE; } return pivot_val; } //+------------------------------------------------------------------+ //| Обнаружение Pivot Low | //| Аналогично PivotHigh, но ищем минимум | //+------------------------------------------------------------------+ double CSwingDetector::PivotLow(const double &low[], int bar, int strength, int rates_total) { int pivot_bar = bar + strength; if(pivot_bar + strength >= rates_total || pivot_bar < 0) return EMPTY_VALUE; double pivot_val = low[pivot_bar]; //--- Левая сторона (старые бары) for(int i = 1; i <= strength; i++) { if(low[pivot_bar + i] < pivot_val) return EMPTY_VALUE; } //--- Правая сторона (новые бары) for(int i = 1; i <= strength; i++) { if(low[pivot_bar - i] < pivot_val) return EMPTY_VALUE; } return pivot_val; } //+------------------------------------------------------------------+ //| Главный метод расчёта | //| Вызывается из OnCalculate(), все массивы AS_SERIES = true | //+------------------------------------------------------------------+ void CSwingDetector::Calculate(const int rates_total, const int prev_calculated, const double &high[], const double &low[], const double &open[], const double &close[], double &buf_zigzag[], double &buf_liq_swing[], double &buf_str_swing[], double &buf_bos_up[], double &buf_bos_dn[], double &buf_mss_up[], double &buf_mss_dn[], double &calc_ss_high[], double &calc_ss_low[], double &calc_b_sw_high[], double &calc_b_sw_low[], double &calc_sw_high[], double &calc_sw_low[], double &calc_bar_sw_high[], double &calc_bar_sw_low[]) { //--- Минимальное количество баров для расчёта int max_strength = MathMax(m_len, m_len_in); int min_bars = 2 * max_strength + 1; if(rates_total < min_bars) return; int limit; if(prev_calculated == 0) { //--- Первый расчёт: инициализируем все буферы ArrayInitialize(buf_zigzag, EMPTY_VALUE); ArrayInitialize(buf_liq_swing, EMPTY_VALUE); ArrayInitialize(buf_str_swing, EMPTY_VALUE); ArrayInitialize(buf_bos_up, EMPTY_VALUE); ArrayInitialize(buf_bos_dn, EMPTY_VALUE); ArrayInitialize(buf_mss_up, EMPTY_VALUE); ArrayInitialize(buf_mss_dn, EMPTY_VALUE); //--- Инициализация INDICATOR_CALCULATIONS буферов ArrayInitialize(calc_ss_high, EMPTY_VALUE); ArrayInitialize(calc_ss_low, EMPTY_VALUE); ArrayInitialize(calc_b_sw_high, 0); ArrayInitialize(calc_b_sw_low, 0); ArrayInitialize(calc_sw_high, EMPTY_VALUE); ArrayInitialize(calc_sw_low, EMPTY_VALUE); ArrayInitialize(calc_bar_sw_high, 0); ArrayInitialize(calc_bar_sw_low, 0); //--- Сброс состояния ResetState(); //--- Стартовый бар: нужно 2*max_strength баров истории для пивотов limit = rates_total - 1 - 2 * max_strength; //--- Ограничиваем количество баров для расчёта if(m_calc_bars > 0 && limit > m_calc_bars) limit = m_calc_bars; } else { //--- Инкрементальный расчёт: обрабатываем только новый подтверждённый бар if(rates_total == prev_calculated) return; // Нет нового бара — пропускаем //--- Новый бар: инициализируем bar 0 (неподтверждённый) как пустой buf_zigzag[0] = EMPTY_VALUE; buf_liq_swing[0] = EMPTY_VALUE; buf_str_swing[0] = EMPTY_VALUE; buf_bos_up[0] = EMPTY_VALUE; buf_bos_dn[0] = EMPTY_VALUE; buf_mss_up[0] = EMPTY_VALUE; buf_mss_dn[0] = EMPTY_VALUE; //--- Бар 1 — только что подтверждённый (был bar 0 до появления нового) limit = 1; } //--- Основной цикл: от старых баров к новым (limit → 1) //--- Пропускаем bar 0 (неподтверждённый), аналог barstate.isconfirmed for(int bar = limit; bar >= 1; bar--) { //--- Обработка Liquidity Swings ProcessLiquiditySwing(bar, high, low, open, close, buf_zigzag, buf_liq_swing, rates_total, calc_ss_high, calc_ss_low, calc_b_sw_high, calc_b_sw_low, calc_sw_high, calc_sw_low, calc_bar_sw_high, calc_bar_sw_low); //--- Сохраняем состояние в INDICATOR_CALCULATIONS буферы calc_ss_high[bar] = m_liq.ss_high; calc_ss_low[bar] = m_liq.ss_low; calc_b_sw_high[bar] = (double)m_liq.b_sw_high; calc_b_sw_low[bar] = (double)m_liq.b_sw_low; calc_sw_high[bar] = m_liq.sw_high; calc_sw_low[bar] = m_liq.sw_low; calc_bar_sw_high[bar] = (double)m_liq.bar_sw_high; calc_bar_sw_low[bar] = (double)m_liq.bar_sw_low; //--- Обработка Structure Swings + BOS/MSS ProcessStructureSwing(bar, high, low, close, buf_str_swing, buf_bos_up, buf_bos_dn, buf_mss_up, buf_mss_dn, rates_total); } } //+------------------------------------------------------------------+ //| Машина состояний Liquidity Swings | //| Перевод логики из tz.md строки 414-576 | //+------------------------------------------------------------------+ void CSwingDetector::ProcessLiquiditySwing(int bar, const double &high[], const double &low[], const double &open[], const double &close[], double &buf_zigzag[], double &buf_liq_swing[], int rates_total, double &calc_ss_high[], double &calc_ss_low[], double &calc_b_sw_high[], double &calc_b_sw_low[], double &calc_sw_high[], double &calc_sw_low[], double &calc_bar_sw_high[], double &calc_bar_sw_low[]) { //--- Сброс per-bar флагов m_liq.ch_swh = false; m_liq.ch_swl = false; bool sw_skip = false; bool swh_check = false; bool swl_check = false; //--- Обнаружение пивотов на текущем баре //--- Пивот находится на bar+m_len (подтверждён m_len барами справа) double sw_H = PivotHigh(high, bar, m_len, rates_total); double sw_L = PivotLow(low, bar, m_len, rates_total); bool has_H = (sw_H != EMPTY_VALUE); bool has_L = (sw_L != EMPTY_VALUE); int pivot_bar = bar + m_len; // Бар где находится пивот int b_len = m_len + 1; // Смещение для lookback (Pine: b_len = Len+1) //--- Безопасный lookback: вычисляем значения b_len баров назад один раз int lb_idx = bar + b_len; bool lb_valid = (lb_idx >= 0 && lb_idx < rates_total); double lb_ss_high = lb_valid ? calc_ss_high[lb_idx] : EMPTY_VALUE; double lb_ss_low = lb_valid ? calc_ss_low[lb_idx] : EMPTY_VALUE; double lb_b_sw_high = lb_valid ? calc_b_sw_high[lb_idx] : 0; double lb_b_sw_low = lb_valid ? calc_b_sw_low[lb_idx] : 0; double lb_sw_high = lb_valid ? calc_sw_high[lb_idx] : EMPTY_VALUE; double lb_sw_low = lb_valid ? calc_sw_low[lb_idx] : EMPTY_VALUE; double lb_bar_sw_high = lb_valid ? calc_bar_sw_high[lb_idx] : 0; double lb_bar_sw_low = lb_valid ? calc_bar_sw_low[lb_idx] : 0; //=== Инициализация: первый найденный пивот === if(m_liq.sw_high == EMPTY_VALUE && has_H) { m_liq.sw_high = sw_H; m_liq.bar_sw_high = pivot_bar; m_liq.sw_dir = -1; // После High ждём Low } if(m_liq.sw_low == EMPTY_VALUE && has_L) { m_liq.sw_low = sw_L; m_liq.bar_sw_low = pivot_bar; m_liq.sw_dir = 1; // После Low ждём High } //=== Обновление промежуточных экстремумов === //--- ss_high: отслеживаем максимум high с последнего свинга if(high[bar] > m_liq.ss_high || m_liq.ss_high == EMPTY_VALUE) { m_liq.ss_high = high[bar]; m_liq.b_sw_high = bar; } //--- ss_low: отслеживаем минимум low с последнего свинга if(low[bar] < m_liq.ss_low || m_liq.ss_low == EMPTY_VALUE) { m_liq.ss_low = low[bar]; m_liq.b_sw_low = bar; } //================================================================= //=== СЛУЧАЙ 1: Оба пивота найдены одновременно (tz.md: 440-501) === //================================================================= if(has_H && has_L) { //--- Направление 1: ждём High if(m_liq.sw_dir == 1) { //--- Проверяем: есть ли другой PivotLow в окне Len*2 баров for(int i = 1; i <= m_len * 2; i++) { if(PivotLow(low, bar + i, m_len, rates_total) != EMPTY_VALUE) { swl_check = true; break; } } //--- Обновляем оба свинга m_liq.sw_low = sw_L; m_liq.bar_sw_low = pivot_bar; m_liq.sw_high = sw_H; m_liq.bar_sw_high = pivot_bar; //--- ch: бычья свеча на баре пивота? Определяет направление bool ch = close[pivot_bar] > open[pivot_bar]; //--- ch2: сложный паттерн (High < Low или промежуточный максимум > High) //--- Pine: (ss_high[b_len] > sw_high and not swl_check) double prev_ss_high = lb_ss_high; bool ch2 = (m_liq.sw_high < m_liq.sw_low || (prev_ss_high != EMPTY_VALUE && prev_ss_high > m_liq.sw_high && !swl_check)) && m_len > 1; //--- Промежуточные точки для ZigZag double s_h, s_l2; int s_b, s_b2; if(ch) { s_h = (prev_ss_high != EMPTY_VALUE) ? prev_ss_high : sw_H; s_b = (prev_ss_high != EMPTY_VALUE) ? (int)lb_b_sw_high : pivot_bar; } else { s_h = sw_H; s_b = pivot_bar; } double prev_sw_low = lb_sw_low; int prev_bar_sw_low = (int)lb_bar_sw_low; if(ch) { s_l2 = m_liq.sw_low; s_b2 = m_liq.bar_sw_low; } else { s_l2 = (prev_sw_low != EMPTY_VALUE) ? prev_sw_low : m_liq.sw_low; s_b2 = (prev_sw_low != EMPTY_VALUE) ? prev_bar_sw_low : m_liq.bar_sw_low; } //--- ZigZag: основные линии buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; buf_zigzag[s_b2] = s_l2; buf_zigzag[s_b] = s_h; //--- Точки на свингах buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; //--- Дополнительные точки при ch/ch2 if(ch && !ch2) { buf_liq_swing[s_b] = s_h; buf_zigzag[s_b] = s_h; } if(ch2) { double prev_ss_low = lb_ss_low; int prev_b_sw_low = (int)lb_b_sw_low; int prev_b_sw_high = (int)lb_b_sw_high; buf_liq_swing[prev_b_sw_high] = prev_ss_high; buf_zigzag[prev_b_sw_high] = prev_ss_high; if(!ch) { buf_liq_swing[prev_b_sw_low] = prev_ss_low; buf_zigzag[prev_b_sw_low] = prev_ss_low; } } //--- Переключаем направление m_liq.sw_dir = ch ? -1 : 1; sw_skip = true; } //--- Направление -1: ждём Low if(m_liq.sw_dir == -1 && !sw_skip) { //--- Проверяем: есть ли другой PivotHigh в окне for(int i = 1; i <= m_len * 2; i++) { if(PivotHigh(high, bar + i, m_len, rates_total) != EMPTY_VALUE) { swh_check = true; break; } } m_liq.sw_low = sw_L; m_liq.bar_sw_low = pivot_bar; m_liq.sw_high = sw_H; m_liq.bar_sw_high = pivot_bar; //--- ch: медвежья свеча? bool ch = close[pivot_bar] < open[pivot_bar]; double prev_ss_low = lb_ss_low; bool ch2 = (m_liq.sw_high <= m_liq.sw_low || (prev_ss_low != EMPTY_VALUE && prev_ss_low < m_liq.sw_low && !swh_check)) && m_len > 1; //--- Промежуточные точки double s_l, s_h2; int s_b, s_b2; if(ch) { s_l = (prev_ss_low != EMPTY_VALUE) ? prev_ss_low : sw_L; s_b = (prev_ss_low != EMPTY_VALUE) ? (int)lb_b_sw_low : pivot_bar; } else { s_l = sw_L; s_b = pivot_bar; } double prev_sw_high = lb_sw_high; int prev_bar_sw_high = (int)lb_bar_sw_high; if(ch) { s_h2 = m_liq.sw_high; s_b2 = m_liq.bar_sw_high; } else { s_h2 = (prev_sw_high != EMPTY_VALUE) ? prev_sw_high : m_liq.sw_high; s_b2 = (prev_sw_high != EMPTY_VALUE) ? prev_bar_sw_high : m_liq.bar_sw_high; } //--- ZigZag buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; buf_zigzag[s_b] = s_l; buf_zigzag[s_b2] = s_h2; //--- Точки buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; if(ch && !ch2) { buf_liq_swing[s_b] = s_l; buf_zigzag[s_b] = s_l; } if(ch2) { double prev_ss_low2 = lb_ss_low; int prev_b_sw_low = (int)lb_b_sw_low; int prev_b_sw_high = (int)lb_b_sw_high; buf_liq_swing[prev_b_sw_low] = prev_ss_low2; buf_zigzag[prev_b_sw_low] = prev_ss_low2; if(!ch) { double prev_ss_high2 = lb_ss_high; buf_liq_swing[prev_b_sw_high] = prev_ss_high2; buf_zigzag[prev_b_sw_high] = prev_ss_high2; } } m_liq.sw_dir = ch ? 1 : -1; sw_skip = true; } //--- Оба пивота: оба флага изменения m_liq.ch_swl = true; m_liq.ch_swh = true; } //================================================================= //=== СЛУЧАЙ 2: Dir=1 (ждём High), один пивот (tz.md: 504-531) === //================================================================= if(m_liq.sw_dir == 1 && !sw_skip) { //--- Нашли ожидаемый High if(has_H) { //--- Проверяем наличие PivotLow в окне Len*2 for(int i = 0; i <= m_len * 2; i++) { if(PivotLow(low, bar + i, m_len, rates_total) != EMPTY_VALUE) { swl_check = true; break; } } m_liq.sw_high = sw_H; m_liq.bar_sw_high = pivot_bar; m_liq.ch_swh = true; m_liq.sw_dir = -1; // Теперь ждём Low //--- ch: сложный паттерн double prev_ss_high = lb_ss_high; bool ch = m_liq.sw_high < m_liq.sw_low || (prev_ss_high != EMPTY_VALUE && prev_ss_high > m_liq.sw_high && !swl_check); //--- Определяем точку от которой рисуем линию double s_l, prev_ss_low; int s_b; prev_ss_low = lb_ss_low; if(ch) { s_l = (prev_ss_low != EMPTY_VALUE) ? prev_ss_low : m_liq.sw_low; s_b = (prev_ss_low != EMPTY_VALUE) ? (int)lb_b_sw_low : m_liq.bar_sw_low; } else { s_l = m_liq.sw_low; s_b = m_liq.bar_sw_low; } //--- ZigZag: линия от Low к High buf_zigzag[s_b] = s_l; buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; //--- Дополнительные точки при сложном паттерне if(ch) { int prev_b_sw_high = (int)lb_b_sw_high; int prev_b_sw_low = (int)lb_b_sw_low; if(prev_b_sw_low > 0 && prev_b_sw_low < rates_total) { buf_zigzag[prev_b_sw_low] = prev_ss_low; buf_liq_swing[prev_b_sw_low] = prev_ss_low; } if(prev_b_sw_high > 0 && prev_b_sw_high < rates_total) { buf_zigzag[prev_b_sw_high] = prev_ss_high; buf_liq_swing[prev_b_sw_high] = prev_ss_high; } } sw_skip = true; } //--- Неожиданный Low (сложный паттерн, tz.md: 524-531) if(has_L && !sw_skip) { //--- Промежуточный максимум становится новым High double prev_ss_high = lb_ss_high; int prev_b_sw_high = (int)lb_b_sw_high; m_liq.sw_low = sw_L; m_liq.bar_sw_low = pivot_bar; if(prev_ss_high != EMPTY_VALUE) { m_liq.sw_high = prev_ss_high; m_liq.bar_sw_high = prev_b_sw_high; } m_liq.ch_swl = true; m_liq.ch_swh = true; m_liq.sw_dir = 1; // Остаёмся в том же направлении //--- ZigZag buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; sw_skip = true; } } //================================================================= //=== СЛУЧАЙ 3: Dir=-1 (ждём Low), один пивот (tz.md: 533-558) === //================================================================= if(m_liq.sw_dir == -1 && !sw_skip) { //--- Нашли ожидаемый Low if(has_L) { //--- Проверяем наличие PivotHigh в окне for(int i = 0; i <= m_len * 2; i++) { if(PivotHigh(high, bar + i, m_len, rates_total) != EMPTY_VALUE) { swh_check = true; break; } } m_liq.sw_low = sw_L; m_liq.bar_sw_low = pivot_bar; m_liq.ch_swl = true; m_liq.sw_dir = 1; // Теперь ждём High //--- ch: сложный паттерн double prev_ss_low = lb_ss_low; bool ch = m_liq.sw_high <= m_liq.sw_low || (prev_ss_low != EMPTY_VALUE && prev_ss_low < m_liq.sw_low && !swh_check); double s_h, prev_ss_high; int s_b; prev_ss_high = lb_ss_high; if(ch) { s_h = (prev_ss_high != EMPTY_VALUE) ? prev_ss_high : m_liq.sw_high; s_b = (prev_ss_high != EMPTY_VALUE) ? (int)lb_b_sw_high : m_liq.bar_sw_high; } else { s_h = m_liq.sw_high; s_b = m_liq.bar_sw_high; } //--- ZigZag: линия от High к Low buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; buf_zigzag[s_b] = s_h; buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; //--- Дополнительные точки при сложном паттерне if(ch) { int prev_b_sw_low = (int)lb_b_sw_low; int prev_b_sw_high = (int)lb_b_sw_high; if(prev_b_sw_low > 0 && prev_b_sw_low < rates_total) { buf_zigzag[prev_b_sw_low] = prev_ss_low; buf_liq_swing[prev_b_sw_low] = prev_ss_low; } if(prev_b_sw_high > 0 && prev_b_sw_high < rates_total) { buf_zigzag[prev_b_sw_high] = prev_ss_high; buf_liq_swing[prev_b_sw_high] = prev_ss_high; } } sw_skip = true; } //--- Неожиданный High (сложный паттерн, tz.md: 552-558) if(has_H && !sw_skip) { double prev_ss_low = lb_ss_low; int prev_b_sw_low = (int)lb_b_sw_low; m_liq.sw_high = sw_H; m_liq.bar_sw_high = pivot_bar; if(prev_ss_low != EMPTY_VALUE) { m_liq.sw_low = prev_ss_low; m_liq.bar_sw_low = prev_b_sw_low; } m_liq.ch_swl = true; m_liq.ch_swh = true; m_liq.sw_dir = -1; // Остаёмся в том же направлении buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; sw_skip = true; } } //================================================================= //=== Финальная точка: рисуем точку на каждом новом свинге === //================================================================= if(m_liq.ch_swh && m_liq.bar_sw_high >= 0 && m_liq.bar_sw_high < rates_total) { buf_liq_swing[m_liq.bar_sw_high] = m_liq.sw_high; buf_zigzag[m_liq.bar_sw_high] = m_liq.sw_high; } if(m_liq.ch_swl && m_liq.bar_sw_low >= 0 && m_liq.bar_sw_low < rates_total) { buf_liq_swing[m_liq.bar_sw_low] = m_liq.sw_low; buf_zigzag[m_liq.bar_sw_low] = m_liq.sw_low; } //================================================================= //=== Сброс промежуточных экстремумов после смены свинга === //=== (tz.md: 565-576) === //================================================================= if(m_liq.ch_swh || m_liq.ch_swl) { //--- Пересчитываем ph/pl — максимум и минимум в окне Len баров int diff_ph = (m_liq.sw_dir == 1) ? 0 : 1; int diff_pl = (m_liq.sw_dir == -1) ? 0 : 1; int range_h = (m_len == 1) ? 1 : m_len - diff_ph; int range_l = (m_len == 1) ? 1 : m_len - diff_pl; //--- Находим максимум high в окне [bar, bar+range_h-1] double ph = high[bar]; int b_ph = bar; for(int i = 1; i < range_h && (bar + i) < rates_total; i++) { if(high[bar + i] > ph) { ph = high[bar + i]; b_ph = bar + i; } } //--- Находим минимум low в окне [bar, bar+range_l-1] double pl = low[bar]; int b_pl = bar; for(int i = 1; i < range_l && (bar + i) < rates_total; i++) { if(low[bar + i] < pl) { pl = low[bar + i]; b_pl = bar + i; } } //--- Сбрасываем промежуточные экстремумы m_liq.ss_low = pl; m_liq.b_sw_low = b_pl; m_liq.ss_high = ph; m_liq.b_sw_high = b_ph; } } //+------------------------------------------------------------------+ //| Машина состояний Structure Swings + BOS/MSS | //| Перевод логики из tz.md строки 658-806 | //+------------------------------------------------------------------+ void CSwingDetector::ProcessStructureSwing(int bar, const double &high[], const double &low[], const double &close[], double &buf_str_swing[], double &buf_bos_up[], double &buf_bos_dn[], double &buf_mss_up[], double &buf_mss_dn[], int rates_total) { //--- Сброс per-bar флагов m_str.ch_in_h = false; m_str.ch_in_l = false; m_str.bos_up = false; m_str.bos_down = false; m_str.mss_up = false; m_str.mss_down = false; bool in_skip = false; //--- Сохраняем предыдущие значения для lookback [1] m_prev_i_high = m_str.i_high; m_prev_i_low = m_str.i_low; m_prev_bar_i_high = m_str.bar_i_high; m_prev_bar_i_low = m_str.bar_i_low; m_prev_cf_in_high = m_str.cf_in_high; m_prev_cf_in_low = m_str.cf_in_low; m_prev_bar_in_high = m_str.bar_in_high; m_prev_bar_in_low = m_str.bar_in_low; m_prev_in_high = m_str.in_high; m_prev_in_low = m_str.in_low; //=== Шаг 1: Обнаружение пивотов (Valid Pivot condition) === bool cf_high_in = (PivotHigh(high, bar, m_len_in, rates_total) != EMPTY_VALUE); bool cf_low_in = (PivotLow(low, bar, m_len_in, rates_total) != EMPTY_VALUE); if(cf_high_in) m_valid_pivot_high = true; if(cf_low_in) m_valid_pivot_low = true; //=== Шаг 2: Отслеживание неподтверждённых экстремумов === //--- Новый максимум — сбрасываем валидацию if(high[bar] > m_str.i_high || m_str.i_high == EMPTY_VALUE) { m_str.i_high = high[bar]; m_str.bar_i_high = bar; m_valid_cf_high = false; m_valid_pivot_high = false; } //--- Новый минимум — сбрасываем валидацию if(low[bar] < m_str.i_low || m_str.i_low == EMPTY_VALUE) { m_str.i_low = low[bar]; m_str.bar_i_low = bar; m_valid_cf_low = false; m_valid_pivot_low = false; } //=== Шаг 3: Valid Pullback — проверяем откат от экстремума === //--- Откат от High: цена должна упасть ниже low бара экстремума if(m_str.bar_i_high >= 0 && m_str.bar_i_high < rates_total) { if(m_valid_method == VALID_CLOSE) { if(close[bar] < low[m_str.bar_i_high]) m_valid_cf_high = true; } else { if(low[bar] < low[m_str.bar_i_high]) m_valid_cf_high = true; } } //--- Откат от Low: цена должна подняться выше high бара экстремума if(m_str.bar_i_low >= 0 && m_str.bar_i_low < rates_total) { if(m_valid_method == VALID_CLOSE) { if(close[bar] > high[m_str.bar_i_low]) m_valid_cf_low = true; } else { if(high[bar] > high[m_str.bar_i_low]) m_valid_cf_low = true; } } //--- Оба подтверждены → сброс валидации (tz.md: 696-698) if(m_str.cf_in_high && m_str.cf_in_low) { m_valid_cf_high = false; m_valid_cf_low = false; m_valid_pivot_high = false; m_valid_pivot_low = false; } //=== Шаг 4: Обнаружение пробоев структуры === bool in_break_up = (m_str.in_high != EMPTY_VALUE) && (close[bar] > m_str.in_high); bool in_break_down = (m_str.in_low != EMPTY_VALUE) && (close[bar] < m_str.in_low); //=== Шаг 5: Логика направления — бычий тренд (tz.md: 706-726) === if(m_str.in_dir_big == 1) { //--- MSS Down: пробой Low при подтверждённом Low if(in_break_down && m_str.cf_in_low) { m_str.cf_in_high = true; m_str.cf_in_low = false; m_str.ch_in_h = true; m_str.mss_down = true; } //--- Подтверждение нового High (valid pullback + valid pivot) if(m_str.in_high != EMPTY_VALUE && m_str.in_dir_small == 1 && m_valid_cf_high && m_valid_pivot_high) { m_str.cf_in_high = true; m_str.ch_in_h = true; } //--- BOS Up: пробой High при подтверждённом High if(m_str.in_dir_small == -1 && in_break_up && m_str.cf_in_high) { m_str.cf_in_high = false; m_str.cf_in_low = true; m_str.ch_in_l = true; m_str.bos_up = true; } } //=== Шаг 6: Логика направления — медвежий тренд (tz.md: 728-750) === if(m_str.in_dir_big == -1) { //--- MSS Up: пробой High при подтверждённом High if(in_break_up && m_str.cf_in_high) { m_str.cf_in_high = false; m_str.cf_in_low = true; m_str.ch_in_l = true; m_str.mss_up = true; } //--- Подтверждение нового Low (valid pullback + valid pivot) if(m_str.in_low != EMPTY_VALUE && m_str.in_dir_small == -1 && m_valid_cf_low && m_valid_pivot_low) { m_str.cf_in_low = true; m_str.ch_in_l = true; } //--- BOS Down: пробой Low при подтверждённом Low if(m_str.in_dir_small == 1 && in_break_down && m_str.cf_in_low) { m_str.cf_in_high = true; m_str.cf_in_low = false; m_str.ch_in_h = true; m_str.bos_down = true; } } //=== Шаг 7: Обновление Structure Swings (tz.md: 752-758) === if(m_str.ch_in_l) { m_str.in_low = m_str.i_low; m_str.bar_in_low = m_str.bar_i_low; m_str.in_dir_small = 1; // Теперь ищем High } if(m_str.ch_in_h) { m_str.in_high = m_str.i_high; m_str.bar_in_high = m_str.bar_i_high; m_str.in_dir_small = -1; // Теперь ищем Low } //=== Шаг 8: Инициализация — первые два Structure Swings (tz.md: 762-776) === if(m_str.in_high == EMPTY_VALUE && m_str.i_high != EMPTY_VALUE && cf_high_in) { m_str.i_low = low[bar]; m_str.bar_i_low = bar; m_str.cf_in_high = true; m_con_first++; m_str.ch_in_h = true; m_str.in_high = m_str.i_high; m_str.bar_in_high = m_str.bar_i_high; m_str.in_dir_small = -1; if(m_con_first == 2) m_str.in_dir_small = 1; if(!m_str.cf_in_low) m_str.in_dir_big = 1; } if(m_str.in_low == EMPTY_VALUE && m_str.i_low != EMPTY_VALUE && cf_low_in) { m_str.i_high = high[bar]; m_str.bar_i_high = bar; m_str.cf_in_low = true; m_con_first++; m_str.ch_in_l = true; m_str.in_low = m_str.i_low; m_str.bar_in_low = m_str.bar_i_low; m_str.in_dir_small = 1; if(m_con_first == 2) m_str.in_dir_small = -1; if(!m_str.cf_in_high) m_str.in_dir_big = -1; } //--- Сброс счётчика после инициализации обоих свингов if(m_str.cf_in_high && m_str.cf_in_low) m_con_first = 0; //=== Шаг 9: Сброс неподтверждённых экстремумов (tz.md: 781-792) === if((!m_str.cf_in_high && high[bar] >= m_prev_i_high && m_prev_i_high != EMPTY_VALUE) || m_str.mss_up || m_str.bos_up) { m_str.i_low = EMPTY_VALUE; } if((!m_str.cf_in_low && low[bar] <= m_prev_i_low && m_prev_i_low != EMPTY_VALUE) || m_str.mss_down || m_str.bos_down) { m_str.i_high = EMPTY_VALUE; } //--- Восстановление после BOS/MSS (tz.md: 786-792) if(m_str.mss_up || m_str.bos_up) { if(m_str.bar_in_low > m_prev_bar_i_high) { m_str.i_high = high[bar]; m_str.bar_i_high = bar; } } if(m_str.mss_down || m_str.bos_down) { if(m_str.bar_in_high > m_prev_bar_i_low) { m_str.i_low = low[bar]; m_str.bar_i_low = bar; } } //=== Шаг 10: Смена тренда (tz.md: 794-798) === if(in_break_down && m_str.cf_in_high && m_str.in_dir_big == 1) { m_str.in_dir_big = -1; in_skip = true; } if(!in_skip && in_break_up && m_str.cf_in_low && m_str.in_dir_big == -1) { m_str.in_dir_big = 1; } //=== Шаг 11: Заполнение буферов отрисовки === bool cf_ud = m_str.bos_up || m_str.mss_up || m_str.bos_down || m_str.mss_down; //--- Structure Swing точки — только при BOS/MSS событиях (tz.md: 800-806) if(cf_ud) { //--- Точка на подтверждённом Low if(m_str.cf_in_low || m_prev_cf_in_low) { int sw_bar = m_str.cf_in_low ? m_str.bar_in_low : m_prev_bar_in_low; double sw_val = m_str.cf_in_low ? m_str.in_low : m_prev_in_low; if(sw_bar >= 0 && sw_bar < rates_total) buf_str_swing[sw_bar] = sw_val; } //--- Точка на подтверждённом High if(m_str.cf_in_high || m_prev_cf_in_high) { int sw_bar = m_str.cf_in_high ? m_str.bar_in_high : m_prev_bar_in_high; double sw_val = m_str.cf_in_high ? m_str.in_high : m_prev_in_high; if(sw_bar >= 0 && sw_bar < rates_total) buf_str_swing[sw_bar] = sw_val; } } //--- BOS/MSS горизонтальные линии if(cf_ud) { //--- Определяем уровень и бар события double level; int start_bar; if(m_str.bos_up) { level = m_str.in_high; start_bar = m_str.bar_in_high; //--- Заполняем горизонтальную линию от бара свинга до текущего for(int i = start_bar; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_bos_up[i] = level; } } if(m_str.bos_down) { level = m_str.in_low; start_bar = m_str.bar_in_low; for(int i = start_bar; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_bos_dn[i] = level; } } if(m_str.mss_up) { //--- MSS Up: пробой High вверх (смена на бычий тренд) level = m_str.in_high; start_bar = m_str.bar_in_high; for(int i = start_bar; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_mss_up[i] = level; } } if(m_str.mss_down) { //--- MSS Down: пробой Low вниз (смена на медвежий тренд) level = m_str.in_low; start_bar = m_str.bar_in_low; for(int i = start_bar; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_mss_dn[i] = level; } } } //=== Шаг 12: Рисование текущих непробитых уровней === //--- Заполняем BOS/MSS буферы горизонтальными линиями для активных уровней if(m_str.in_dir_big == 1) { //--- Бычий тренд: In_High → BOS_Up, In_Low → MSS_Dn if(m_str.cf_in_high && m_str.in_high != EMPTY_VALUE) { for(int i = m_str.bar_in_high; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_bos_up[i] = m_str.in_high; } } if(m_str.cf_in_low && m_str.in_low != EMPTY_VALUE) { for(int i = m_str.bar_in_low; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_mss_dn[i] = m_str.in_low; } } } else if(m_str.in_dir_big == -1) { //--- Медвежий тренд: In_Low → BOS_Dn, In_High → MSS_Up if(m_str.cf_in_low && m_str.in_low != EMPTY_VALUE) { for(int i = m_str.bar_in_low; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_bos_dn[i] = m_str.in_low; } } if(m_str.cf_in_high && m_str.in_high != EMPTY_VALUE) { for(int i = m_str.bar_in_high; i >= bar; i--) { if(i >= 0 && i < rates_total) buf_mss_up[i] = m_str.in_high; } } } } #endif // CSWING_DETECTOR_MQH