1306 lines
50 KiB
MQL5
1306 lines
50 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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
|