Ind_Aleks_ICT_Entry_V2_TS_I.../CSwingDetector.mqh

1306 lines
50 KiB
MQL5
Raw Permalink Normal View History

2026-02-10 21:54:56 +03:00
//+------------------------------------------------------------------+
//| 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