Ind_Aleks_ICT_Entry_V2_TS_I.../CFVGModule.mqh
Alexandr Nikolaev 142b0e2556 fix: use close[1] for FVG zone visibility to prevent flickering
When trend_only filter is on, zones no longer disappear/reappear
as price enters/exits the zone intra-bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:25:26 +03:00

855 行
39 KiB
MQL5

//+------------------------------------------------------------------+
//| CFVGModule.mqh |
//| Модуль обнаружения FVG и iFVG (Fair Value Gap) |
//| для ICT Entry индикатора |
//+------------------------------------------------------------------+
#ifndef CFVG_MODULE_MQH
#define CFVG_MODULE_MQH
#define FVG_MAX_STORAGE 50
//+------------------------------------------------------------------+
//| Структура зоны FVG/iFVG |
//| end_bar == -1 → зона активна |
//| end_bar >= 0 → замёрзла (митигирована) |
//+------------------------------------------------------------------+
struct SFVGZone
{
double top; // верхняя граница
double bot; // нижняя граница
int start_bar; // левый (старый) край (AS_SERIES: бар создания = bar+2)
int end_bar; // правый край: -1=активна, иначе=бар митигации
string obj_name; // имя OBJ_RECTANGLE на графике ("" = нет объекта)
datetime start_time; // datetime левого края зоны
};
//+------------------------------------------------------------------+
//| Класс CFVGModule |
//+------------------------------------------------------------------+
class CFVGModule
{
public:
CFVGModule();
~CFVGModule();
//--- Инициализация
bool Init(int qty_history, color fvg_col, color ifvg_col,
bool show_fvg, bool show_ifvg, bool trend_only,
bool eq_filter, bool eq_strict, bool eq_use_lvl2);
//--- Сброс + удаление всех объектов с графика
void Reset();
//--- Инициализация буферов при полном пересчёте
void InitBuffers(double &buf_fvg_top[], double &buf_fvg_bot[],
double &buf_ifvg_top[], double &buf_ifvg_bot[],
int rates_total);
//--- Расчёт на подтверждённом баре (bar >= 1)
void Calculate(int bar,
const double &high[],
const double &low[],
const double &close[],
const datetime &time[],
double &buf_fvg_top[],
double &buf_fvg_bot[],
double &buf_ifvg_top[],
double &buf_ifvg_bot[],
int rates_total,
int in_dir_big,
const double &buf_eq[],
const double &buf_premium2[],
const double &buf_discount2[]);
//--- Продление активных зон на live bar=0 (в направлении тренда)
void UpdateBar0(int in_dir_big,
double &buf_fvg_top[],
double &buf_fvg_bot[],
double &buf_ifvg_top[],
double &buf_ifvg_bot[],
double eq_bar1,
double premium2_bar1,
double discount2_bar1);
//--- Удалить все FVG/iFVG объекты с графика (вызывать в OnDeinit)
void Cleanup();
//--- Обновить видимость объектов согласно фильтрам
void ApplyVisibility(int in_dir_big, double current_price,
double eq_value, double premium2_value,
double discount2_value);
//--- Доступ к данным для Entry-сигналов
bool HasDemandFVG() const;
bool HasSupplyFVG() const;
bool HasDemandIFVG() const;
bool HasSupplyIFVG() const;
double GetDemandFVGTop() const;
double GetDemandFVGBot() const;
double GetSupplyFVGTop() const;
double GetSupplyFVGBot() const;
double GetDemandIFVGTop() const;
double GetDemandIFVGBot() const;
double GetSupplyIFVGTop() const;
double GetSupplyIFVGBot() const;
private:
int m_hist_bars;
color m_fvg_col; // цвет FVG объектов
color m_ifvg_col; // цвет iFVG объектов
bool m_show_fvg; // показывать FVG объекты
bool m_show_ifvg; // показывать iFVG объекты
bool m_trend_only; // только активные зоны по тренду
bool m_eq_filter; // фильтр по Equilibrium
bool m_eq_strict; // строгий: вся зона за линией
bool m_eq_use_lvl2; // использовать 2-й уровень
//--- Массивы зон (index 0 = новейшая). cnt = ВСЕГО зон (активные + замороженные)
SFVGZone m_dfvg_zones[];
int m_dfvg_cnt;
SFVGZone m_sfvg_zones[];
int m_sfvg_cnt;
//--- Pre-Inversion FVG (кандидаты на инверсию): параллельные массивы
double m_dfvg_R_top[], m_dfvg_R_bot[];
int m_dfvg_R_bar[];
int m_dfvg_R_cnt;
double m_sfvg_R_top[], m_sfvg_R_bot[];
int m_sfvg_R_bar[];
int m_sfvg_R_cnt;
//--- iFVG зоны
SFVGZone m_ifvg_d_zones[];
int m_ifvg_d_cnt;
SFVGZone m_ifvg_s_zones[];
int m_ifvg_s_cnt;
//--- Вспомогательные методы
void UnshiftZone(SFVGZone &zones[], int &cnt, int max_cnt,
double top, double bot, int start_bar, datetime start_time);
void UnshiftPreInv(double &top_arr[], double &bot_arr[], int &bar_arr[], int &cnt,
double top, double bot, int bar_idx);
void RemovePreInv(double &top_arr[], double &bot_arr[], int &bar_arr[], int &cnt, int idx);
void FillZoneRange(double &buf_top[], double &buf_bot[],
double top, double bot,
int start_bar, int cur_bar, int rates_total);
void PruneOldZones(SFVGZone &zones[], int &cnt, int max_cnt,
double &buf_top[], double &buf_bot[], int rates_total);
int FindActiveZone(const SFVGZone &zones[], int cnt) const;
//--- Создать OBJ_RECTANGLE на графике, вернуть имя объекта
string CreateRect(string prefix, datetime t1, double price_top,
datetime t2, double price_bot, color clr);
//--- Обновить правый край объекта (ObjectMove point 1)
void MoveRectRight(string name, datetime t, double price_bot);
double ResolveEqLevel(int in_dir_big, double eq_val,
double premium2_val, double discount2_val) const;
bool PassesEqFilter(double zone_top, double zone_bot,
int in_dir_big, bool zone_is_demand,
double eq_value) const;
};
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CFVGModule::CFVGModule()
{
m_hist_bars = 0;
m_fvg_col = clrDodgerBlue;
m_ifvg_col = clrDeepSkyBlue;
m_show_fvg = true;
m_show_ifvg = true;
m_trend_only = true;
m_eq_filter = false;
m_eq_strict = false;
m_eq_use_lvl2 = false;
m_dfvg_cnt = 0;
m_sfvg_cnt = 0;
m_dfvg_R_cnt = 0;
m_sfvg_R_cnt = 0;
m_ifvg_d_cnt = 0;
m_ifvg_s_cnt = 0;
}
//+------------------------------------------------------------------+
//| Деструктор |
//+------------------------------------------------------------------+
CFVGModule::~CFVGModule()
{
}
//+------------------------------------------------------------------+
//| Инициализация |
//+------------------------------------------------------------------+
bool CFVGModule::Init(int qty_history, color fvg_col, color ifvg_col,
bool show_fvg, bool show_ifvg, bool trend_only,
bool eq_filter, bool eq_strict, bool eq_use_lvl2)
{
m_hist_bars = (qty_history >= 0) ? qty_history : 0;
m_fvg_col = fvg_col;
m_ifvg_col = ifvg_col;
m_show_fvg = show_fvg;
m_show_ifvg = show_ifvg;
m_trend_only = trend_only;
m_eq_filter = eq_filter;
m_eq_strict = eq_strict;
m_eq_use_lvl2 = eq_use_lvl2;
int max_zones = FVG_MAX_STORAGE + 1;
int max_preinv = 20;
if(ArrayResize(m_dfvg_zones, max_zones) < 0) return false;
if(ArrayResize(m_sfvg_zones, max_zones) < 0) return false;
if(ArrayResize(m_ifvg_d_zones, max_zones) < 0) return false;
if(ArrayResize(m_ifvg_s_zones, max_zones) < 0) return false;
if(ArrayResize(m_dfvg_R_top, max_preinv) < 0) return false;
if(ArrayResize(m_dfvg_R_bot, max_preinv) < 0) return false;
if(ArrayResize(m_dfvg_R_bar, max_preinv) < 0) return false;
if(ArrayResize(m_sfvg_R_top, max_preinv) < 0) return false;
if(ArrayResize(m_sfvg_R_bot, max_preinv) < 0) return false;
if(ArrayResize(m_sfvg_R_bar, max_preinv) < 0) return false;
Reset();
return true;
}
//+------------------------------------------------------------------+
//| Сброс состояния + удаление объектов с графика |
//+------------------------------------------------------------------+
void CFVGModule::Reset()
{
Cleanup();
m_dfvg_cnt = 0;
m_sfvg_cnt = 0;
m_dfvg_R_cnt = 0;
m_sfvg_R_cnt = 0;
m_ifvg_d_cnt = 0;
m_ifvg_s_cnt = 0;
}
//+------------------------------------------------------------------+
//| Удалить все FVG/iFVG объекты с графика |
//+------------------------------------------------------------------+
void CFVGModule::Cleanup()
{
ObjectsDeleteAll(0, "ICTE_FVG_D_");
ObjectsDeleteAll(0, "ICTE_FVG_S_");
ObjectsDeleteAll(0, "ICTE_iFVG_D_");
ObjectsDeleteAll(0, "ICTE_iFVG_S_");
}
//+------------------------------------------------------------------+
//| Инициализация буферов нулями (DRAW_FILLING скрыт, но буферы |
//| используются для Data Window) |
//+------------------------------------------------------------------+
void CFVGModule::InitBuffers(double &buf_fvg_top[], double &buf_fvg_bot[],
double &buf_ifvg_top[], double &buf_ifvg_bot[],
int rates_total)
{
ArrayInitialize(buf_fvg_top, 0.0);
ArrayInitialize(buf_fvg_bot, 0.0);
ArrayInitialize(buf_ifvg_top, 0.0);
ArrayInitialize(buf_ifvg_bot, 0.0);
}
//+------------------------------------------------------------------+
//| Создать OBJ_RECTANGLE, вернуть имя объекта (или "" при ошибке) |
//+------------------------------------------------------------------+
string CFVGModule::CreateRect(string prefix, datetime t1, double price_top,
datetime t2, double price_bot, color clr)
{
string name = prefix + IntegerToString((long)t1);
if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, price_top, t2, price_bot))
return "";
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FILL, true);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
return name;
}
//+------------------------------------------------------------------+
//| Сдвинуть правый край объекта (point 1) к новому времени |
//+------------------------------------------------------------------+
void CFVGModule::MoveRectRight(string name, datetime t, double price_bot)
{
if(name != "")
ObjectMove(0, name, 1, t, price_bot);
}
//+------------------------------------------------------------------+
//| Вставить зону в начало массива (index 0 = новейшая) |
//+------------------------------------------------------------------+
void CFVGModule::UnshiftZone(SFVGZone &zones[], int &cnt, int max_cnt,
double top, double bot, int start_bar, datetime start_time)
{
int shift_to = MathMin(cnt, max_cnt - 1);
for(int i = shift_to; i > 0; i--)
zones[i] = zones[i-1];
zones[0].top = top;
zones[0].bot = bot;
zones[0].start_bar = start_bar;
zones[0].end_bar = -1;
zones[0].obj_name = "";
zones[0].start_time = start_time;
if(cnt < max_cnt) cnt++;
}
//+------------------------------------------------------------------+
//| Вставить в начало Pre-Inversion массивов |
//+------------------------------------------------------------------+
void CFVGModule::UnshiftPreInv(double &top_arr[], double &bot_arr[], int &bar_arr[], int &cnt,
double top, double bot, int bar_idx)
{
int max = ArraySize(top_arr);
int shift_to = MathMin(cnt, max - 1);
for(int i = shift_to; i > 0; i--)
{
top_arr[i] = top_arr[i-1];
bot_arr[i] = bot_arr[i-1];
bar_arr[i] = bar_arr[i-1];
}
top_arr[0] = top;
bot_arr[0] = bot;
bar_arr[0] = bar_idx;
if(cnt < max) cnt++;
}
//+------------------------------------------------------------------+
//| Удалить элемент из Pre-Inversion массивов по индексу |
//+------------------------------------------------------------------+
void CFVGModule::RemovePreInv(double &top_arr[], double &bot_arr[], int &bar_arr[], int &cnt, int idx)
{
if(idx < 0 || idx >= cnt) return;
for(int i = idx; i < cnt - 1; i++)
{
top_arr[i] = top_arr[i+1];
bot_arr[i] = bot_arr[i+1];
bar_arr[i] = bar_arr[i+1];
}
cnt--;
}
//+------------------------------------------------------------------+
//| Заполнить диапазон буферов значениями зоны [start_bar..cur_bar] |
//+------------------------------------------------------------------+
void CFVGModule::FillZoneRange(double &buf_top[], double &buf_bot[],
double top, double bot,
int start_bar, int cur_bar, int rates_total)
{
int from = MathMin(start_bar, rates_total - 1);
int to = MathMax(cur_bar, 0);
for(int i = from; i >= to; i--)
{
buf_top[i] = top;
buf_bot[i] = bot;
}
}
//+------------------------------------------------------------------+
//| Удалить старейшие зоны если cnt >= max_cnt |
//| Вызывать ПЕРЕД UnshiftZone |
//+------------------------------------------------------------------+
void CFVGModule::PruneOldZones(SFVGZone &zones[], int &cnt, int max_cnt,
double &buf_top[], double &buf_bot[], int rates_total)
{
while(cnt >= max_cnt)
{
SFVGZone oldest = zones[cnt - 1];
//--- Удалить объект с графика
if(oldest.obj_name != "")
ObjectDelete(0, oldest.obj_name);
//--- Очистить бары в буфере
int right = (oldest.end_bar >= 0) ? oldest.end_bar : 0;
int from = MathMin(oldest.start_bar, rates_total - 1);
int to = MathMax(right, 0);
for(int i = from; i >= to; i--)
{
buf_top[i] = 0.0;
buf_bot[i] = 0.0;
}
cnt--;
}
}
//+------------------------------------------------------------------+
//| Найти индекс первой (новейшей) активной зоны или -1 |
//+------------------------------------------------------------------+
int CFVGModule::FindActiveZone(const SFVGZone &zones[], int cnt) const
{
for(int i = 0; i < cnt; i++)
if(zones[i].end_bar == -1) return i;
return -1;
}
//+------------------------------------------------------------------+
//| Доступ к данным для Entry-сигналов |
//+------------------------------------------------------------------+
bool CFVGModule::HasDemandFVG() const { return FindActiveZone(m_dfvg_zones, m_dfvg_cnt) >= 0; }
bool CFVGModule::HasSupplyFVG() const { return FindActiveZone(m_sfvg_zones, m_sfvg_cnt) >= 0; }
bool CFVGModule::HasDemandIFVG() const { return FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt) >= 0; }
bool CFVGModule::HasSupplyIFVG() const { return FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt) >= 0; }
double CFVGModule::GetDemandFVGTop() const
{ int i = FindActiveZone(m_dfvg_zones, m_dfvg_cnt); return (i >= 0) ? m_dfvg_zones[i].top : 0.0; }
double CFVGModule::GetDemandFVGBot() const
{ int i = FindActiveZone(m_dfvg_zones, m_dfvg_cnt); return (i >= 0) ? m_dfvg_zones[i].bot : 0.0; }
double CFVGModule::GetSupplyFVGTop() const
{ int i = FindActiveZone(m_sfvg_zones, m_sfvg_cnt); return (i >= 0) ? m_sfvg_zones[i].top : 0.0; }
double CFVGModule::GetSupplyFVGBot() const
{ int i = FindActiveZone(m_sfvg_zones, m_sfvg_cnt); return (i >= 0) ? m_sfvg_zones[i].bot : 0.0; }
double CFVGModule::GetDemandIFVGTop() const
{ int i = FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt); return (i >= 0) ? m_ifvg_d_zones[i].top : 0.0; }
double CFVGModule::GetDemandIFVGBot() const
{ int i = FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt); return (i >= 0) ? m_ifvg_d_zones[i].bot : 0.0; }
double CFVGModule::GetSupplyIFVGTop() const
{ int i = FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt); return (i >= 0) ? m_ifvg_s_zones[i].top : 0.0; }
double CFVGModule::GetSupplyIFVGBot() const
{ int i = FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt); return (i >= 0) ? m_ifvg_s_zones[i].bot : 0.0; }
//+------------------------------------------------------------------+
//| Главный метод расчёта на подтверждённом баре (bar >= 1) |
//+------------------------------------------------------------------+
void CFVGModule::Calculate(int bar,
const double &high[],
const double &low[],
const double &close[],
const datetime &time[],
double &buf_fvg_top[],
double &buf_fvg_bot[],
double &buf_ifvg_top[],
double &buf_ifvg_bot[],
int rates_total,
int in_dir_big,
const double &buf_eq[],
const double &buf_premium2[],
const double &buf_discount2[])
{
if(bar < 1 || bar >= rates_total) return;
int max_zones = FVG_MAX_STORAGE + 1;
double eq_value = ResolveEqLevel(in_dir_big,
(bar < rates_total) ? buf_eq[bar] : EMPTY_VALUE,
(bar < rates_total) ? buf_premium2[bar] : EMPTY_VALUE,
(bar < rates_total) ? buf_discount2[bar] : EMPTY_VALUE);
//=========================================================
// ШАГ 1: Митигация FVG
//=========================================================
//--- Demand FVG: low <= bot → полная митигация
{
int ai = FindActiveZone(m_dfvg_zones, m_dfvg_cnt);
if(ai >= 0 && low[bar] <= m_dfvg_zones[ai].bot)
{
m_dfvg_zones[ai].end_bar = bar;
//--- Зафиксировать правый край объекта на баре митигации
MoveRectRight(m_dfvg_zones[ai].obj_name, time[bar], m_dfvg_zones[ai].bot);
// Pre-Inversion (dfvg_R) НЕ удаляем: он нужен для инверсии в Supply iFVG.
}
}
//--- Supply FVG: high >= top → полная митигация
{
int ai = FindActiveZone(m_sfvg_zones, m_sfvg_cnt);
if(ai >= 0 && high[bar] >= m_sfvg_zones[ai].top)
{
m_sfvg_zones[ai].end_bar = bar;
MoveRectRight(m_sfvg_zones[ai].obj_name, time[bar], m_sfvg_zones[ai].bot);
// Pre-Inversion (sfvg_R) НЕ удаляем: он нужен для инверсии в Demand iFVG.
}
}
//=========================================================
// ШАГ 2: Митигация iFVG
//=========================================================
//--- Demand iFVG: low <= bot
{
int ai = FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt);
if(ai >= 0 && low[bar] <= m_ifvg_d_zones[ai].bot)
{
m_ifvg_d_zones[ai].end_bar = bar;
MoveRectRight(m_ifvg_d_zones[ai].obj_name, time[bar], m_ifvg_d_zones[ai].bot);
}
}
//--- Supply iFVG: high >= top
{
int ai = FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt);
if(ai >= 0 && high[bar] >= m_ifvg_s_zones[ai].top)
{
m_ifvg_s_zones[ai].end_bar = bar;
MoveRectRight(m_ifvg_s_zones[ai].obj_name, time[bar], m_ifvg_s_zones[ai].bot);
}
}
//=========================================================
// ШАГ 3: Инверсия FVG → iFVG (только close, не wick)
//=========================================================
//--- Supply Pre-Inv → Demand iFVG (close > sfvg_R_top[0])
if(m_sfvg_R_cnt > 0 && close[bar] > m_sfvg_R_top[0])
{
double i_top = m_sfvg_R_top[0];
double i_bot = m_sfvg_R_bot[0];
int i_bar = m_sfvg_R_bar[0];
datetime i_time = (i_bar >= 0 && i_bar < rates_total) ? time[i_bar] : time[bar];
PruneOldZones(m_ifvg_d_zones, m_ifvg_d_cnt, max_zones, buf_ifvg_top, buf_ifvg_bot, rates_total);
UnshiftZone(m_ifvg_d_zones, m_ifvg_d_cnt, max_zones, i_top, i_bot, i_bar, i_time);
//--- Создать объект на графике
m_ifvg_d_zones[0].obj_name = CreateRect("ICTE_iFVG_D_", i_time, i_top, time[bar], i_bot, m_ifvg_col);
//--- Заполнить буфер (если проходит Equilibrium-фильтр)
if(PassesEqFilter(i_top, i_bot, in_dir_big, true, eq_value))
FillZoneRange(buf_ifvg_top, buf_ifvg_bot, i_top, i_bot, i_bar, bar, rates_total);
RemovePreInv(m_sfvg_R_top, m_sfvg_R_bot, m_sfvg_R_bar, m_sfvg_R_cnt, 0);
}
//--- Demand Pre-Inv → Supply iFVG (close < dfvg_R_bot[0])
if(m_dfvg_R_cnt > 0 && close[bar] < m_dfvg_R_bot[0])
{
double i_top = m_dfvg_R_top[0];
double i_bot = m_dfvg_R_bot[0];
int i_bar = m_dfvg_R_bar[0];
datetime i_time = (i_bar >= 0 && i_bar < rates_total) ? time[i_bar] : time[bar];
PruneOldZones(m_ifvg_s_zones, m_ifvg_s_cnt, max_zones, buf_ifvg_top, buf_ifvg_bot, rates_total);
UnshiftZone(m_ifvg_s_zones, m_ifvg_s_cnt, max_zones, i_top, i_bot, i_bar, i_time);
m_ifvg_s_zones[0].obj_name = CreateRect("ICTE_iFVG_S_", i_time, i_top, time[bar], i_bot, m_ifvg_col);
if(PassesEqFilter(i_top, i_bot, in_dir_big, false, eq_value))
FillZoneRange(buf_ifvg_top, buf_ifvg_bot, i_top, i_bot, i_bar, bar, rates_total);
RemovePreInv(m_dfvg_R_top, m_dfvg_R_bot, m_dfvg_R_bar, m_dfvg_R_cnt, 0);
}
//=========================================================
// ШАГ 4: Создание новых FVG (bar+2 < rates_total)
//=========================================================
if(bar + 2 < rates_total)
{
//--- Demand FVG: зазор вверх — low[bar] > high[bar+2]
if(low[bar] > high[bar + 2])
{
double new_top = low[bar];
double new_bot = high[bar + 2];
int new_start = bar + 2;
datetime new_time = time[new_start];
PruneOldZones(m_dfvg_zones, m_dfvg_cnt, max_zones, buf_fvg_top, buf_fvg_bot, rates_total);
UnshiftZone(m_dfvg_zones, m_dfvg_cnt, max_zones, new_top, new_bot, new_start, new_time);
//--- Создать объект (левый край = new_time, правый = time[bar])
m_dfvg_zones[0].obj_name = CreateRect("ICTE_FVG_D_", new_time, new_top, time[bar], new_bot, m_fvg_col);
UnshiftPreInv(m_dfvg_R_top, m_dfvg_R_bot, m_dfvg_R_bar, m_dfvg_R_cnt, new_top, new_bot, new_start);
//--- Заполнить буфер (если проходит Equilibrium-фильтр)
if(PassesEqFilter(new_top, new_bot, in_dir_big, true, eq_value))
FillZoneRange(buf_fvg_top, buf_fvg_bot, new_top, new_bot, new_start, bar, rates_total);
}
//--- Supply FVG: зазор вниз — high[bar] < low[bar+2]
if(high[bar] < low[bar + 2])
{
double new_top = low[bar + 2];
double new_bot = high[bar];
int new_start = bar + 2;
datetime new_time = time[new_start];
PruneOldZones(m_sfvg_zones, m_sfvg_cnt, max_zones, buf_fvg_top, buf_fvg_bot, rates_total);
UnshiftZone(m_sfvg_zones, m_sfvg_cnt, max_zones, new_top, new_bot, new_start, new_time);
m_sfvg_zones[0].obj_name = CreateRect("ICTE_FVG_S_", new_time, new_top, time[bar], new_bot, m_fvg_col);
UnshiftPreInv(m_sfvg_R_top, m_sfvg_R_bot, m_sfvg_R_bar, m_sfvg_R_cnt, new_top, new_bot, new_start);
if(PassesEqFilter(new_top, new_bot, in_dir_big, false, eq_value))
FillZoneRange(buf_fvg_top, buf_fvg_bot, new_top, new_bot, new_start, bar, rates_total);
}
}
//=========================================================
// ШАГ 5: Продление активных зон на текущий бар + ObjectMove
//=========================================================
//--- FVG: продлеваем активную зону (demand приоритет)
{
int d_ai = FindActiveZone(m_dfvg_zones, m_dfvg_cnt);
int s_ai = FindActiveZone(m_sfvg_zones, m_sfvg_cnt);
if(d_ai >= 0 && bar <= m_dfvg_zones[d_ai].start_bar
&& PassesEqFilter(m_dfvg_zones[d_ai].top, m_dfvg_zones[d_ai].bot, in_dir_big, true, eq_value))
{
buf_fvg_top[bar] = m_dfvg_zones[d_ai].top;
buf_fvg_bot[bar] = m_dfvg_zones[d_ai].bot;
MoveRectRight(m_dfvg_zones[d_ai].obj_name, time[bar], m_dfvg_zones[d_ai].bot);
}
else if(s_ai >= 0 && bar <= m_sfvg_zones[s_ai].start_bar
&& PassesEqFilter(m_sfvg_zones[s_ai].top, m_sfvg_zones[s_ai].bot, in_dir_big, false, eq_value))
{
buf_fvg_top[bar] = m_sfvg_zones[s_ai].top;
buf_fvg_bot[bar] = m_sfvg_zones[s_ai].bot;
MoveRectRight(m_sfvg_zones[s_ai].obj_name, time[bar], m_sfvg_zones[s_ai].bot);
}
}
//--- iFVG: продлеваем активную зону
{
int d_ai = FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt);
int s_ai = FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt);
if(d_ai >= 0 && bar <= m_ifvg_d_zones[d_ai].start_bar
&& PassesEqFilter(m_ifvg_d_zones[d_ai].top, m_ifvg_d_zones[d_ai].bot, in_dir_big, true, eq_value))
{
buf_ifvg_top[bar] = m_ifvg_d_zones[d_ai].top;
buf_ifvg_bot[bar] = m_ifvg_d_zones[d_ai].bot;
MoveRectRight(m_ifvg_d_zones[d_ai].obj_name, time[bar], m_ifvg_d_zones[d_ai].bot);
}
else if(s_ai >= 0 && bar <= m_ifvg_s_zones[s_ai].start_bar
&& PassesEqFilter(m_ifvg_s_zones[s_ai].top, m_ifvg_s_zones[s_ai].bot, in_dir_big, false, eq_value))
{
buf_ifvg_top[bar] = m_ifvg_s_zones[s_ai].top;
buf_ifvg_bot[bar] = m_ifvg_s_zones[s_ai].bot;
MoveRectRight(m_ifvg_s_zones[s_ai].obj_name, time[bar], m_ifvg_s_zones[s_ai].bot);
}
}
//=========================================================
// ШАГ 6: Расширить OBJ_RECTANGLE ВСЕХ активных зон
//=========================================================
for(int i = 0; i < m_dfvg_cnt; i++)
if(m_dfvg_zones[i].end_bar == -1 && m_dfvg_zones[i].obj_name != "" && bar <= m_dfvg_zones[i].start_bar)
MoveRectRight(m_dfvg_zones[i].obj_name, time[bar], m_dfvg_zones[i].bot);
for(int i = 0; i < m_sfvg_cnt; i++)
if(m_sfvg_zones[i].end_bar == -1 && m_sfvg_zones[i].obj_name != "" && bar <= m_sfvg_zones[i].start_bar)
MoveRectRight(m_sfvg_zones[i].obj_name, time[bar], m_sfvg_zones[i].bot);
for(int i = 0; i < m_ifvg_d_cnt; i++)
if(m_ifvg_d_zones[i].end_bar == -1 && m_ifvg_d_zones[i].obj_name != "" && bar <= m_ifvg_d_zones[i].start_bar)
MoveRectRight(m_ifvg_d_zones[i].obj_name, time[bar], m_ifvg_d_zones[i].bot);
for(int i = 0; i < m_ifvg_s_cnt; i++)
if(m_ifvg_s_zones[i].end_bar == -1 && m_ifvg_s_zones[i].obj_name != "" && bar <= m_ifvg_s_zones[i].start_bar)
MoveRectRight(m_ifvg_s_zones[i].obj_name, time[bar], m_ifvg_s_zones[i].bot);
}
//+------------------------------------------------------------------+
//| Обновление bar=0: продление активных зон на live-бар |
//+------------------------------------------------------------------+
void CFVGModule::UpdateBar0(int in_dir_big,
double &buf_fvg_top[],
double &buf_fvg_bot[],
double &buf_ifvg_top[],
double &buf_ifvg_bot[],
double eq_bar1,
double premium2_bar1,
double discount2_bar1)
{
//--- Сбросить буферы bar 0
buf_fvg_top[0] = 0.0;
buf_fvg_bot[0] = 0.0;
buf_ifvg_top[0] = 0.0;
buf_ifvg_bot[0] = 0.0;
double eq_value = ResolveEqLevel(in_dir_big, eq_bar1, premium2_bar1, discount2_bar1);
datetime now = TimeCurrent();
if(in_dir_big == 1)
{
//--- Бычий тренд: расширяем Demand FVG + Demand iFVG
int ai = FindActiveZone(m_dfvg_zones, m_dfvg_cnt);
if(ai >= 0 && PassesEqFilter(m_dfvg_zones[ai].top, m_dfvg_zones[ai].bot, in_dir_big, true, eq_value))
{
buf_fvg_top[0] = m_dfvg_zones[ai].top;
buf_fvg_bot[0] = m_dfvg_zones[ai].bot;
MoveRectRight(m_dfvg_zones[ai].obj_name, now, m_dfvg_zones[ai].bot);
}
ai = FindActiveZone(m_ifvg_d_zones, m_ifvg_d_cnt);
if(ai >= 0 && PassesEqFilter(m_ifvg_d_zones[ai].top, m_ifvg_d_zones[ai].bot, in_dir_big, true, eq_value))
{
buf_ifvg_top[0] = m_ifvg_d_zones[ai].top;
buf_ifvg_bot[0] = m_ifvg_d_zones[ai].bot;
MoveRectRight(m_ifvg_d_zones[ai].obj_name, now, m_ifvg_d_zones[ai].bot);
}
}
else if(in_dir_big == -1)
{
//--- Медвежий тренд: расширяем Supply FVG + Supply iFVG
int ai = FindActiveZone(m_sfvg_zones, m_sfvg_cnt);
if(ai >= 0 && PassesEqFilter(m_sfvg_zones[ai].top, m_sfvg_zones[ai].bot, in_dir_big, false, eq_value))
{
buf_fvg_top[0] = m_sfvg_zones[ai].top;
buf_fvg_bot[0] = m_sfvg_zones[ai].bot;
MoveRectRight(m_sfvg_zones[ai].obj_name, now, m_sfvg_zones[ai].bot);
}
ai = FindActiveZone(m_ifvg_s_zones, m_ifvg_s_cnt);
if(ai >= 0 && PassesEqFilter(m_ifvg_s_zones[ai].top, m_ifvg_s_zones[ai].bot, in_dir_big, false, eq_value))
{
buf_ifvg_top[0] = m_ifvg_s_zones[ai].top;
buf_ifvg_bot[0] = m_ifvg_s_zones[ai].bot;
MoveRectRight(m_ifvg_s_zones[ai].obj_name, now, m_ifvg_s_zones[ai].bot);
}
}
//--- Расширить OBJ_RECTANGLE ВСЕХ активных зон до now (независимо от тренда)
for(int i = 0; i < m_dfvg_cnt; i++)
if(m_dfvg_zones[i].end_bar == -1 && m_dfvg_zones[i].obj_name != "")
MoveRectRight(m_dfvg_zones[i].obj_name, now, m_dfvg_zones[i].bot);
for(int i = 0; i < m_sfvg_cnt; i++)
if(m_sfvg_zones[i].end_bar == -1 && m_sfvg_zones[i].obj_name != "")
MoveRectRight(m_sfvg_zones[i].obj_name, now, m_sfvg_zones[i].bot);
for(int i = 0; i < m_ifvg_d_cnt; i++)
if(m_ifvg_d_zones[i].end_bar == -1 && m_ifvg_d_zones[i].obj_name != "")
MoveRectRight(m_ifvg_d_zones[i].obj_name, now, m_ifvg_d_zones[i].bot);
for(int i = 0; i < m_ifvg_s_cnt; i++)
if(m_ifvg_s_zones[i].end_bar == -1 && m_ifvg_s_zones[i].obj_name != "")
MoveRectRight(m_ifvg_s_zones[i].obj_name, now, m_ifvg_s_zones[i].bot);
}
//+------------------------------------------------------------------+
//| Обновить видимость объектов согласно фильтрам |
//| in_dir_big: 1=бычий, -1=медвежий |
//| current_price: close[1] для фильтрации зон выше/ниже цены |
//+------------------------------------------------------------------+
void CFVGModule::ApplyVisibility(int in_dir_big, double current_price,
double eq_value, double premium2_value,
double discount2_value)
{
double eq_lvl = ResolveEqLevel(in_dir_big, eq_value, premium2_value, discount2_value);
//--- Demand FVG: видим при бычьем тренде, зона ниже цены
for(int i = 0; i < m_dfvg_cnt; i++)
{
if(m_dfvg_zones[i].obj_name == "") continue;
bool show = m_show_fvg;
if(show)
{
int right_edge = (m_dfvg_zones[i].end_bar == -1) ? 0 : m_dfvg_zones[i].end_bar;
show = (m_hist_bars == 0)
? (m_dfvg_zones[i].end_bar == -1)
: (right_edge <= m_hist_bars);
}
if(show && m_trend_only)
show = (in_dir_big == 1) && (m_dfvg_zones[i].end_bar == -1) && (m_dfvg_zones[i].top < current_price);
if(show && m_eq_filter)
show = PassesEqFilter(m_dfvg_zones[i].top, m_dfvg_zones[i].bot, in_dir_big, true, eq_lvl);
ObjectSetInteger(0, m_dfvg_zones[i].obj_name, OBJPROP_TIMEFRAMES,
show ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
}
//--- Supply FVG: видим при медвежьем тренде, зона выше цены
for(int i = 0; i < m_sfvg_cnt; i++)
{
if(m_sfvg_zones[i].obj_name == "") continue;
bool show = m_show_fvg;
if(show)
{
int right_edge = (m_sfvg_zones[i].end_bar == -1) ? 0 : m_sfvg_zones[i].end_bar;
show = (m_hist_bars == 0)
? (m_sfvg_zones[i].end_bar == -1)
: (right_edge <= m_hist_bars);
}
if(show && m_trend_only)
show = (in_dir_big == -1) && (m_sfvg_zones[i].end_bar == -1) && (m_sfvg_zones[i].bot > current_price);
if(show && m_eq_filter)
show = PassesEqFilter(m_sfvg_zones[i].top, m_sfvg_zones[i].bot, in_dir_big, false, eq_lvl);
ObjectSetInteger(0, m_sfvg_zones[i].obj_name, OBJPROP_TIMEFRAMES,
show ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
}
//--- Demand iFVG: видим при бычьем тренде, зона ниже цены
for(int i = 0; i < m_ifvg_d_cnt; i++)
{
if(m_ifvg_d_zones[i].obj_name == "") continue;
bool show = m_show_ifvg;
if(show)
{
int right_edge = (m_ifvg_d_zones[i].end_bar == -1) ? 0 : m_ifvg_d_zones[i].end_bar;
show = (m_hist_bars == 0)
? (m_ifvg_d_zones[i].end_bar == -1)
: (right_edge <= m_hist_bars);
}
if(show && m_trend_only)
show = (in_dir_big == 1) && (m_ifvg_d_zones[i].end_bar == -1) && (m_ifvg_d_zones[i].top < current_price);
if(show && m_eq_filter)
show = PassesEqFilter(m_ifvg_d_zones[i].top, m_ifvg_d_zones[i].bot, in_dir_big, true, eq_lvl);
ObjectSetInteger(0, m_ifvg_d_zones[i].obj_name, OBJPROP_TIMEFRAMES,
show ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
}
//--- Supply iFVG: видим при медвежьем тренде, зона выше цены
for(int i = 0; i < m_ifvg_s_cnt; i++)
{
if(m_ifvg_s_zones[i].obj_name == "") continue;
bool show = m_show_ifvg;
if(show)
{
int right_edge = (m_ifvg_s_zones[i].end_bar == -1) ? 0 : m_ifvg_s_zones[i].end_bar;
show = (m_hist_bars == 0)
? (m_ifvg_s_zones[i].end_bar == -1)
: (right_edge <= m_hist_bars);
}
if(show && m_trend_only)
show = (in_dir_big == -1) && (m_ifvg_s_zones[i].end_bar == -1) && (m_ifvg_s_zones[i].bot > current_price);
if(show && m_eq_filter)
show = PassesEqFilter(m_ifvg_s_zones[i].top, m_ifvg_s_zones[i].bot, in_dir_big, false, eq_lvl);
ObjectSetInteger(0, m_ifvg_s_zones[i].obj_name, OBJPROP_TIMEFRAMES,
show ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
}
}
//+------------------------------------------------------------------+
//| Определить значение Equilibrium для фильтрации |
//+------------------------------------------------------------------+
double CFVGModule::ResolveEqLevel(int in_dir_big, double eq_val,
double premium2_val, double discount2_val) const
{
if(!m_eq_filter) return EMPTY_VALUE;
if(m_eq_use_lvl2)
return (in_dir_big == 1) ? discount2_val : (in_dir_big == -1) ? premium2_val : EMPTY_VALUE;
return eq_val;
}
//+------------------------------------------------------------------+
//| Проверка: зона проходит Equilibrium-фильтр? |
//| zone_is_demand: true=Demand, false=Supply |
//| Возвращает true = показывать зону |
//+------------------------------------------------------------------+
bool CFVGModule::PassesEqFilter(double zone_top, double zone_bot,
int in_dir_big, bool zone_is_demand,
double eq_value) const
{
if(!m_eq_filter) return true;
if(eq_value == EMPTY_VALUE) return true;
if(in_dir_big == 0) return true;
//--- Фильтруем только если зона соответствует тренду
if(in_dir_big == 1 && !zone_is_demand) return true; // Supply в бычьем — не фильтруем
if(in_dir_big == -1 && zone_is_demand) return true; // Demand в медвежьем — не фильтруем
if(zone_is_demand)
{
//--- Demand: зона должна быть ниже eq_value (Discount)
return m_eq_strict ? (zone_top <= eq_value) : (zone_bot <= eq_value);
}
else
{
//--- Supply: зона должна быть выше eq_value (Premium)
return m_eq_strict ? (zone_bot >= eq_value) : (zone_top >= eq_value);
}
}
#endif // CFVG_MODULE_MQH