EA_SMC_Mql5/Include/CStructureAnalyzer.mqh

829 lines
70 KiB
MQL5
Raw Permalink Normal View History

2025-07-03 23:37:10 +07:00
<EFBFBD><EFBFBD>//+------------------------------------------------------------------+
//| CStructureAnalyzer.mqh |
//| The brain that analyzes Market Structure (Swings & Trend) |
//| v12.0 - Final Stable "Marking" Arch |
//+------------------------------------------------------------------+
#ifndef CSTRUCTUREANALYZER_MQH
#define CSTRUCTUREANALYZER_MQH
#include "CZoneManager.mqh"
#include "CEnumSharing.mqh"
class CStructureAnalyzer {
private:
CZoneManager* m_zone_manager_ptr;
double m_atr_noise_filter_multiplier;
int m_max_bars_for_bos;
bool m_use_trend_filter;
ENUM_BOS_TYPE m_bos_confirmation_type;
int m_pd_array_lookback;
datetime m_last_daily_swing_promo_day;
bool m_use_rsi_filter;
int m_rsi_overbought;
int m_rsi_oversold;
double m_retest_min_atr_moveaway;
ENUM_MARKET_STRUCTURE_STATE m_market_structure_state;
bool m_use_volume_filter;
bool m_filter_swings_by_fvg;
bool m_filter_swings_by_sweep;
ENUM_ZONE_QUALITY_LEVEL m_min_zone_quality;
// --- Private Helper Functions ---
void FilterSwingsByRSI(CArrayObj* swings_all, CDataContext &context)
{
if(!m_use_rsi_filter || swings_all == NULL) return;
for(int i = 0; i < swings_all.Total(); i++)
{
SwingPoint* swing = swings_all.At(i);
// #-@ 20*'45H"1D!H91*4 A%0D!HC
H Noise
if(swing == NULL || swing.status != STATUS_UNCONFIRMED) continue;
double rsi_value = context.rsi_buffer[swing.bar_index];
bool rsi_condition_met = false;
if(swing.type == SWING_HIGH && rsi_value >= m_rsi_overbought) {
rsi_condition_met = true;
}
else if(swing.type == SWING_LOW && rsi_value <= m_rsi_oversold) {
rsi_condition_met = true;
}
// I2D!H@I2@7H-D RSI C+I5#2@G NOISE 15
if(!rsi_condition_met) {
swing.status = STATUS_NOISE;
}
}
}
// @%5H"+I25H@G "5#2" Swing 5H@G Noise (D!H%)
void MarkNoiseSwings(CArrayObj* swings_all, CDataContext &context) {
if(swings_all == NULL || swings_all.Total() < 2) return;
// '%92+%1!2+I2@7H-'2!%- 1"C2#@#5"@5"
for(int i = swings_all.Total() - 1; i >= 1; i--) {
SwingPoint *current_swing = swings_all.At(i);
SwingPoint *prev_swing = swings_all.At(i - 1);
if(current_swing == NULL || prev_swing == NULL) continue;
// I2-1C-1+6H9#0!'%%DA%I' C+II2!D
if(current_swing.status != STATUS_UNCONFIRMED || prev_swing.status != STATUS_UNCONFIRMED) continue;
double swing_length_price = MathAbs(current_swing.price - prev_swing.price);
double atr_val = context.atr_buffer[current_swing.bar_index];
if(atr_val > 0 && swing_length_price < (atr_val * m_atr_noise_filter_multiplier)) {
current_swing.status = STATUS_NOISE;
prev_swing.status = STATUS_NOISE;
}
}
}
void ConfirmAndLinkSwings(CArrayObj* swings_all, CDataContext &context)
{
if(swings_all == NULL || swings_all.Total() < 2) return;
for(int i = 0; i < swings_all.Total(); i++)
{
SwingPoint* current_swing = swings_all.At(i);
if(current_swing == NULL || current_swing.status != STATUS_UNCONFIRMED) {
continue;
}
ENUM_SWING_TYPE opposite_type = (current_swing.type == SWING_HIGH) ? SWING_LOW : SWING_HIGH;
// *** FIXED: Changed variable name from all_swings to swings_all ***
SwingPoint* prev_opposite_swing = GetPreviousSwing(i, opposite_type, swings_all);
if(prev_opposite_swing == NULL) {
current_swing.status = STATUS_MINOR;
continue;
}
bool is_bos = false;
if(current_swing.type == SWING_HIGH && current_swing.price < prev_opposite_swing.price) { // Bearish BOS
is_bos = true;
} else if (current_swing.type == SWING_LOW && current_swing.price > prev_opposite_swing.price) { // Bullish BOS
is_bos = true;
}
if(is_bos) {
current_swing.status = STATUS_MINOR;
prev_opposite_swing.status = STATUS_INDUCEMENT;
// *** FIXED: Changed variable name from all_swings to swings_all ***
SwingPoint* origin_swing = GetPreviousSwing(i, current_swing.type, swings_all);
if(origin_swing != NULL) {
origin_swing.status = STATUS_MAJOR;
// C
I@'%2- prev_opposite_swing @G8"7"1 BOS 5H
1@'H2
origin_swing.bos_confirmation_time = prev_opposite_swing.time;
}
} else {
current_swing.status = STATUS_MINOR;
}
}
}
bool CheckBOSAfterSwing(SwingPoint* candidate, int candidate_idx, CArrayObj* swings_all, CDataContext &context) {
if(candidate == NULL) return false;
if(candidate.type == SWING_LOW) {
SwingPoint* prev_high = GetPreviousSwing(candidate_idx, SWING_HIGH, swings_all);
if(prev_high == NULL) return false;
for(int i = candidate.bar_index - 1; i >= 0 && i > candidate.bar_index - m_max_bars_for_bos; i--) {
if(context.rates[i].close > prev_high.price) {
candidate.bos_confirmation_time = context.rates[i].time; return true;
}
}
}
else if(candidate.type == SWING_HIGH) {
SwingPoint* prev_low = GetPreviousSwing(candidate_idx, SWING_LOW, swings_all);
if(prev_low == NULL) return false;
for(int i = candidate.bar_index - 1; i >= 0 && i > candidate.bar_index - m_max_bars_for_bos; i--) {
if(context.rates[i].close < prev_low.price) {
candidate.bos_confirmation_time = context.rates[i].time; return true;
}
}
}
return false;
}
SwingPoint* GetPreviousSwing(int start_index, ENUM_SWING_TYPE type_to_find, CArrayObj* swings_all) {
if(start_index <= 0) return NULL;
for(int i = start_index - 1; i >= 0; i--) {
SwingPoint *swing = swings_all.At(i);
if(swing != NULL && swing.status != STATUS_NOISE && swing.type == type_to_find) return swing;
}
return NULL;
}
void AssociateZonesWithSwings(CArrayObj* swings_all) {
if(CheckPointer(m_zone_manager_ptr) == POINTER_INVALID || CheckPointer(swings_all) == POINTER_INVALID) return;
CArrayObj* all_zones = m_zone_manager_ptr.GetZoneList();
if(CheckPointer(all_zones) == POINTER_INVALID) return;
for(int i = 0; i < all_zones.Total(); i++) {
CSignalZone* zone = all_zones.At(i);
if(zone == NULL || zone.origin_swing_ptr != NULL || zone.zone_type != ZONE_OB) continue;
SwingPoint* closest_confirmed_swing = NULL;
long min_time_diff = LONG_MAX;
for(int j = 0; j < swings_all.Total(); j++) {
SwingPoint* swing = swings_all.At(j);
if(swing == NULL || (swing.status != STATUS_MAJOR && swing.status != STATUS_MINOR)) continue;
if(swing.time < zone.time) {
long time_diff = zone.time - swing.time;
if(time_diff < min_time_diff) {
min_time_diff = time_diff;
closest_confirmed_swing = swing;
}
}
}
if(closest_confirmed_swing != NULL) {
zone.origin_swing_ptr = closest_confirmed_swing;
}
}
}
bool IsZoneInList(CSignalZone* zone, CArrayObj* list)
{
if(zone == NULL || list == NULL) return false;
for(int i=0; i < list.Total(); i++)
{
CSignalZone* z_in_list = list.At(i);
if(z_in_list != NULL && z_in_list.zone_id == zone.zone_id) {
return true; // @-A%I'
}
}
return false; // D!H@-
}
bool IsBosConfirmedFvg(CSignalZone* fvg_zone, CArrayObj* all_swings, CDataContext &context)
{
if(fvg_zone == NULL || fvg_zone.zone_type != ZONE_FVG || all_swings == NULL) return false;
// 1. +2 Swing H-+I25HC%I5H*81 FVG @7H-@G8-I2-4-B#*#I2
SwingPoint* preceding_swing = NULL;
int fvg_bar_index_start = iBarShift(_Symbol, PERIOD_CURRENT, fvg_zone.time);
if(fvg_zone.direction == DIRECTION_BULLISH) { // Bullish FVG @42#228H6I, I-+2 Swing High H-+I2@7H-@#
preceding_swing = GetPreviousSwing(fvg_bar_index_start, SWING_HIGH, all_swings);
} else { // Bearish FVG @42#224I1'%, I-+2 Swing Low H-+I2@7H-@#
preceding_swing = GetPreviousSwing(fvg_bar_index_start, SWING_LOW, all_swings);
}
if(preceding_swing == NULL) return false; // I2D!H!5*'4H-+I2C+I-I2-4 GD!H1
// 2. #'*-'H2 "AH@5"5H*#I2 FVG" (Displacement Candle) *2!2#40%8 Swing H-+I21IDI+#7-D!H
// B"@#20@
G"I-+%1D 3 AH21' FVG @7H-#55H2#@%7H-5H#8A#!2
for(int k=0; k<3; k++)
{
int displacement_candle_index = fvg_bar_index_start + 1 + k;
if(displacement_candle_index < 0 || displacement_candle_index >= context.bars_to_load) continue;
MqlRates displacement_candle = context.rates[displacement_candle_index];
if(fvg_zone.direction == DIRECTION_BULLISH) {
// 2#@%7H-5H6I5H#%1 I-4@+7- Swing High H-+I2DI
if(displacement_candle.close > preceding_swing.price) return true;
} else { // BEARISH
// 2#@%7H-5H%5H#%1 I-4CI Swing Low H-+I2DI
if(displacement_candle.close < preceding_swing.price) return true;
}
}
return false;
}
void InvalidateBrokenZones(CArrayObj* all_zones, CArrayObj* major_swings)
{
if(all_zones == NULL || major_swings == NULL || major_swings.Total() == 0) return;
// +2 Major Swing %H2*8
SwingPoint* last_major_swing = major_swings.At(major_swings.Total() - 1);
if(last_major_swing == NULL) return;
for(int i = 0; i < all_zones.Total(); i++)
{
CSignalZone* zone = all_zones.At(i);
if(zone == NULL || zone.state == STATE_INVALIDATED) continue;
// B 09 Invalidate I2@4 Major Swing C+!H1H#I2! "+%1" 25HB @4
if(last_major_swing.time > zone.time)
{
// I2B  Demand (Bullish) @-1 Major High C+!H . Invalidate
if(zone.direction == DIRECTION_BULLISH && last_major_swing.type == SWING_HIGH) {
zone.state = STATE_INVALIDATED;
}
// I2B  Supply (Bearish) @-1 Major Low C+!H . Invalidate
else if(zone.direction == DIRECTION_BEARISH && last_major_swing.type == SWING_LOW) {
zone.state = STATE_INVALIDATED;
}
}
}
}
// 5H7-1L
15H@#20C
I@G1''8!8 2B +%1
void GradeAllZones(CDataContext &context)
{
if(CheckPointer(m_zone_manager_ptr) == POINTER_INVALID) return;
CArrayObj* all_zones = m_zone_manager_ptr.GetZoneList();
CArrayObj* all_swings = m_zone_manager_ptr.GetAllSwings();
if(all_zones == NULL || all_swings == NULL) return;
// 3' Premium/Discount Array
double equilibrium = 0;
int swings_to_check = MathMin(all_swings.Total(), m_pd_array_lookback);
if(swings_to_check > 1) {
double highest_high = 0;
double lowest_low = DBL_MAX;
int count = 0;
for(int j = all_swings.Total() - 1; j >= 0 && count < swings_to_check; j--) {
SwingPoint *s = all_swings.At(j);
if(s == NULL || s.status == STATUS_NOISE) continue;
if(s.price > highest_high) highest_high = s.price;
if(s.price < lowest_low) lowest_low = s.price;
count++;
}
if(highest_high > 0 && lowest_low < DBL_MAX) {
equilibrium = lowest_low + (highest_high - lowest_low) / 2.0;
}
}
// '%9B  "81'" @7H- "C+I@#" +#7- "5#2" *20'2!*31 
for(int i = 0; i < all_zones.Total(); i++)
{
CSignalZone* zone = all_zones.At(i);
if(zone == NULL) continue;
// I2B 9 Invalidate DA%I' C+I7-'H2D!H*31 15
if(zone.state == STATE_INVALIDATED) {
zone.is_significant = false;
continue;
}
// --- @#4H!#0'2#4%@-#L ---
// Filter 1: Trend Alignment
bool is_trend_aligned = false;
if(zone.zone_type == ZONE_FLIP) {
is_trend_aligned = true; // Flip zone @GI-"@'I
} else {
switch(m_market_structure_state) {
case MS_BULLISH: case MS_BULLISH_CHoCH:
if(zone.direction == DIRECTION_BULLISH) is_trend_aligned = true;
break;
case MS_BEARISH: case MS_BEARISH_CHoCH:
if(zone.direction == DIRECTION_BEARISH) is_trend_aligned = true;
break;
default: is_trend_aligned = true; break;
}
}
// Filter 2: Premium/Discount Array
bool is_in_pd_array = (equilibrium <= 0) ||
(zone.direction == DIRECTION_BULLISH && zone.top < equilibrium) ||
(zone.direction == DIRECTION_BEARISH && zone.bottom > equilibrium);
// Filter 3: Structural Relevance
bool passed_structure_filter = false;
if(zone.origin_swing_ptr != NULL) {
if(zone.zone_type == ZONE_FVG) {
if(IsBosConfirmedFvg(zone, all_swings, context)) passed_structure_filter = true;
} else { // OB A%0 FLIP
passed_structure_filter = true;
}
}
// ---- 1*4C "5#2" is_significant ----
if(is_trend_aligned && is_in_pd_array && passed_structure_filter) {
zone.is_significant = true;
} else {
zone.is_significant = false;
}
}
}
// +++ NEW FUNCTION to find and create Flip Zones +++
void CheckForFlipZones(CDataContext &context)
{
CArrayObj* all_zones = m_zone_manager_ptr.GetZoneList();
if(all_zones == NULL) return;
// '%99B 1I+!
for(int i = 0; i < all_zones.Total(); i++)
{
CSignalZone* zone = all_zones.At(i);
// @#2*C@ 20B 5H@4H9 Invalidate D
if(zone == NULL || zone.state != STATE_INVALIDATED || zone.zone_type == ZONE_FLIP) continue;
// #'*-'H2AH@5"5H3%2"B 1I@G Impulse Candle +#7-D!H
// @#20!-+2AH5H@4H4%H2*8 (last_closed_bar = 1)
int last_closed_bar = 1;
double impulse_body = MathAbs(context.rates[last_closed_bar].close - context.rates[last_closed_bar].open);
double atr_val = context.atr_buffer[last_closed_bar];
bool is_strong_break = (atr_val > 0 && impulse_body >= atr_val * 1.5); // C
I 1.5 @H2- ATR @G@L
if(is_strong_break)
{
// I22#0%8AGA#- C+I "%4" B 5I
if(zone.direction == DIRECTION_BEARISH && context.rates[last_closed_bar].close > zone.top) // Supply 90%86I . %2"@G Demand
{
//PrintFormat("FLIP ZONE CREATED: Bearish zone at %s flipped to Bullish.", TimeToString(zone.time));
zone.direction = DIRECTION_BULLISH;
zone.state = STATE_FRESH; // #5@ G*20C+I#I-!C
I2
zone.zone_type = ZONE_FLIP;
zone.mitigation_count = 0;
zone.trades_taken_count = 0;
}
else if(zone.direction == DIRECTION_BULLISH && context.rates[last_closed_bar].close < zone.bottom) // Demand 90%8% . %2"@G Supply
{
//PrintFormat("FLIP ZONE CREATED: Bullish zone at %s flipped to Bearish.", TimeToString(zone.time));
zone.direction = DIRECTION_BEARISH;
zone.state = STATE_FRESH;
zone.zone_type = ZONE_FLIP;
zone.mitigation_count = 0;
zone.trades_taken_count = 0;
}
}
}
}
// This function replaces GradeAllZones. It uses a stateless, re-evaluation model.
void FilterSignificantZones(CDataContext &context)
{
// --- 1. @#5"!I-!9% A%0*#I2%4*L
1H'#2' ---
CArrayObj* all_zones = m_zone_manager_ptr.GetZoneList();
CArrayObj* all_swings = m_zone_manager_ptr.GetAllSwings();
CArrayObj* significant_zones_list = m_zone_manager_ptr.GetSignificantZones();
if(all_zones == NULL || all_swings == NULL || significant_zones_list == NULL) return;
// *#I2%4*L
1H'#2' (Workbench) @7H-1%%1L
CArrayObj* temp_list = new CArrayObj();
temp_list.FreeMode(false);
// --- 2. 3' Premium/Discount Array ---
double equilibrium = 0;
int swings_to_check = MathMin(all_swings.Total(), m_pd_array_lookback);
if(swings_to_check > 1) {
double highest_high = 0;
double lowest_low = DBL_MAX;
int count = 0;
for(int j = all_swings.Total() - 1; j >= 0 && count < swings_to_check; j--) {
SwingPoint *s = all_swings.At(j);
if(s == NULL || s.status == STATUS_NOISE) continue;
if(s.price > highest_high) highest_high = s.price;
if(s.price < lowest_low) lowest_low = s.price;
count++;
}
if(highest_high > 0 && lowest_low < DBL_MAX) {
equilibrium = lowest_low + (highest_high - lowest_low) / 2.0;
}
}
// --- 3. '%9B 1I+!@7H-1#-%C%4*L
1H'#2' ---
for(int i = 0; i < all_zones.Total(); i++)
{
CSignalZone* zone = all_zones.At(i);
// H25H A: 4%@-#L7I2
if(zone == NULL || zone.state == STATE_INVALIDATED) continue;
// H25H B: 4%@-#L Trend Alignment
bool is_trend_aligned = false;
if(zone.zone_type == ZONE_FLIP) {
is_trend_aligned = true;
} else {
switch(m_market_structure_state) {
case MS_BULLISH: case MS_BULLISH_CHoCH:
if(zone.direction == DIRECTION_BULLISH) is_trend_aligned = true;
break;
case MS_BEARISH: case MS_BEARISH_CHoCH:
if(zone.direction == DIRECTION_BEARISH) is_trend_aligned = true;
break;
default: is_trend_aligned = true; break;
}
}
if(!is_trend_aligned) continue;
// H25H C: 4%@-#L Premium/Discount Array
bool is_in_pd_array = (equilibrium <= 0) ||
(zone.direction == DIRECTION_BULLISH && zone.top < equilibrium) ||
(zone.direction == DIRECTION_BEARISH && zone.bottom > equilibrium);
if(!is_in_pd_array) continue;
// H25H D: 4%@-#L8 2B  (Zone Quality Dial)
bool quality_passed = false;
switch(m_min_zone_quality)
{
case ZQ_STANDARD: // 8 2!2#2: -AH!5*'4I3@4G-
if(zone.origin_swing_ptr != NULL) quality_passed = true;
break;
case ZQ_BOS_CONFIRMED: // 8 2*9: I2@G FVG I-"7"1I'" BOS
if(zone.origin_swing_ptr != NULL) {
if(zone.zone_type == ZONE_OB || zone.zone_type == ZONE_FLIP || zone.zone_type == ZONE_BREAKOUT_RETEST) {
quality_passed = true;
} else if (zone.zone_type == ZONE_FVG) {
if(IsBosConfirmedFvg(zone, all_swings, context)) {
quality_passed = true;
}
}
}
break;
}
if(!quality_passed) continue;
// ---- I2B H28H2 C+I@4H!%C%4*L
1H'#2' ----
temp_list.Add(zone);
}
// --- 4. Atomic Update: %I2%4*L@H2 A%I'3I-!9%2%4*L
1H'#2'!2C*HA ---
significant_zones_list.Clear();
for(int i = 0; i < temp_list.Total(); i++) {
significant_zones_list.Add(temp_list.At(i));
}
//PrintFormat("FilterSignificantZones Complete. Found %d significant zones.", significant_zones_list.Total());
// 5. 3'2!*0-2
delete temp_list;
}
void PromoteDailySwings(CArrayObj* swings_all)
{
if(swings_all == NULL || swings_all.Total() == 0) return;
datetime today = iTime(_Symbol, PERIOD_D1, 0);
if(today == m_last_daily_swing_promo_day) return; // '15I3DA%I' D!HI-3 I3
datetime prev_day_start = iTime(_Symbol, PERIOD_D1, 1);
datetime prev_day_end = today - 1;
int prev_day_start_idx = iBarShift(_Symbol, PERIOD_CURRENT, prev_day_start);
int prev_day_end_idx = iBarShift(_Symbol, PERIOD_CURRENT, prev_day_end);
if(prev_day_start_idx < 0 || prev_day_end_idx < 0) return;
// +2 High/Low -'1H-+I2
int high_idx = iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, prev_day_start_idx - prev_day_end_idx + 1, prev_day_end_idx);
int low_idx = iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, prev_day_start_idx - prev_day_end_idx + 1, prev_day_end_idx);
datetime pdh_time = iTime(_Symbol, PERIOD_CURRENT, high_idx);
datetime pdl_time = iTime(_Symbol, PERIOD_CURRENT, low_idx);
// I+2*'45H#1 PDH/PDL A%I'B#B!@G Major
for(int i=0; i<swings_all.Total(); i++)
{
SwingPoint *s = swings_all.At(i);
if(s == NULL) continue;
if(s.time == pdh_time && s.type == SWING_HIGH){
s.status = STATUS_MAJOR;
//PrintFormat("PROMOTED: Previous Day High at %s to MAJOR SWING.", TimeToString(s.time));
}
if(s.time == pdl_time && s.type == SWING_LOW){
s.status = STATUS_MAJOR;
//PrintFormat("PROMOTED: Previous Day Low at %s to MAJOR SWING.", TimeToString(s.time));
}
}
m_last_daily_swing_promo_day = today; // 16'H2'15I3A%I'
}
// --- NEW HELPER: Grades swings based on whether they swept liquidity ---
void GradeSwingsByLiquiditySweep(CArrayObj* swings_all)
{
if(!m_filter_swings_by_sweep || swings_all == NULL) return;
for(int i = 0; i < swings_all.Total(); i++)
{
SwingPoint* current_swing = swings_all.At(i);
if(current_swing == NULL || current_swing.status != STATUS_UNCONFIRMED) continue;
// !-+2"I-+%1@7H-+2 prev_swing #0@ @5"'1
SwingPoint* prev_swing_of_same_type = GetPreviousSwing(i, current_swing.type, swings_all);
if(prev_swing_of_same_type != NULL)
{
bool sweep_condition_met = false;
// Swing High C+!H I-*9'H2 Swing High @H2
if(current_swing.type == SWING_HIGH && current_swing.price > prev_swing_of_same_type.price) {
sweep_condition_met = true;
}
// Swing Low C+!H I-H3'H2 Swing Low @H2
else if(current_swing.type == SWING_LOW && current_swing.price < prev_swing_of_same_type.price) {
sweep_condition_met = true;
}
// I2D!HH2@7H-D Sweep C+I5#2@G NOISE
if(!sweep_condition_met) {
current_swing.status = STATUS_NOISE;
}
}
else
{
// I2@G*'4A#*85H+21'@#5"@5"D!HDI G-20%H-"H2DH-
// +#7-014I@%"GDI@7H-'2!@I!'
// current_swing.status = STATUS_NOISE;
}
}
}
// --- NEW HELPER: Grades swings based on whether they leave an FVG ---
void GradeSwingsByDisplacement(CArrayObj* swings_all, CDataContext &context)
{
if(!m_filter_swings_by_fvg || swings_all == NULL) return;
for(int i = 0; i < swings_all.Total(); i++)
{
SwingPoint* swing = swings_all.At(i);
// #-@ 20*'45H"1D!H91*4 A%0D!HC
H Noise
if(swing == NULL || swing.status != STATUS_UNCONFIRMED) continue;
bool fvg_found = false;
// !-+2 FVG C "2" 5H8H--2*'4 (#0!2 5 AH)
for(int j = 1; j <= 5; j++)
{
int fvg_middle_idx = swing.bar_index - j;
if(fvg_middle_idx - 1 < 0 || fvg_middle_idx + 1 >= context.bars_to_load) continue;
if(swing.type == SWING_LOW) { // !-+2 Bullish FVG
if(context.rates[fvg_middle_idx-1].low > context.rates[fvg_middle_idx+1].high) {
fvg_found = true;
break;
}
} else if(swing.type == SWING_HIGH) { // !-+2 Bearish FVG
if(context.rates[fvg_middle_idx-1].high < context.rates[fvg_middle_idx+1].low) {
fvg_found = true;
break;
}
}
}
// I2+2 FVG C leg 5H--2*'4D!H@- C+I5#2@G NOISE
if(!fvg_found) {
swing.status = STATUS_NOISE;
}
}
}
// --- UPDATED to prioritize MAJOR swings for structure analysis ---
void UpdateMarketStructureState(CArrayObj* all_swings)
{
// --- 1. I+2 "1A*+%1" 4 1' (@ 20 Major Swings) ---
if(all_swings == NULL) {
m_market_structure_state = MS_UNDEFINED;
return;
}
SwingPoint* H1 = NULL; // Major High %H2*8
SwingPoint* H2 = NULL; // Major High H-+I2
SwingPoint* L1 = NULL; // Major Low %H2*8
SwingPoint* L2 = NULL; // Major Low H-+I2
int highs_found = 0;
int lows_found = 0;
// '%92*'4C+!H*8D+2@H2*8
for(int i = all_swings.Total() - 1; i >= 0; i--) {
SwingPoint* s = all_swings.At(i);
// *** 2#@%5H"A%5H*31 5H*8: *C@ 20*'45H@G MAJOR @H21I ***
if(s == NULL || s.status != STATUS_MAJOR) continue;
if(s == NULL) continue;
if(s.type == SWING_HIGH) {
if(highs_found == 0) H1 = s;
else if(highs_found == 1) H2 = s;
highs_found++;
}
else if(s.type == SWING_LOW) {
if(lows_found == 0) L1 = s;
else if(lows_found == 1) L2 = s;
lows_found++;
}
if(highs_found >= 2 && lows_found >= 2) break;
}
// --- 2. #'*-'2!*!9#L-I-!9% ---
if(H1 == NULL || H2 == NULL || L1 == NULL || L2 == NULL) {
m_market_structure_state = MS_UNDEFINED;
// D!HI- Print GDI @7H-D!HC+I Log #@4D-5H"1D!H!5 Major Swing #
return;
}
// --- 3. 2#'4@#20+LB#*#I2 (Logic @4! AHC
II-!9%5H56I) ---
bool is_making_higher_highs = H1.price > H2.price;
bool is_making_higher_lows = L1.price > L2.price;
bool is_making_lower_lows = L1.price < L2.price;
bool is_making_lower_highs = H1.price < H2.price;
if(is_making_higher_highs && is_making_higher_lows) {
m_market_structure_state = MS_BULLISH;
}
else if(is_making_lower_highs && is_making_lower_lows) {
m_market_structure_state = MS_BEARISH;
}
else if(is_making_higher_highs && is_making_lower_lows) {
m_market_structure_state = MS_BEARISH_CHoCH;
}
else if(is_making_lower_highs && is_making_higher_lows) {
m_market_structure_state = MS_BULLISH_CHoCH;
}
else {
m_market_structure_state = MS_UNDEFINED;
}
//PrintFormat("Market Structure State Updated: %s (Based on Major Swings)", EnumToString(m_market_structure_state));
}
// +++ NEW: Function to update advanced confirmation flags for zones +++
void UpdateZoneConfirmationFlags(CArrayObj* all_zones, CArrayObj* all_swings, CDataContext &context, double min_atr_moveaway)
{
if(all_zones == NULL || all_swings == NULL) return;
for(int i = 0; i < all_zones.Total(); i++)
{
CSignalZone* zone = all_zones.At(i);
if(zone == NULL || zone.state == STATE_INVALIDATED) continue;
// --- 1. #'*- Displacement Confirmation ---
if(!zone.has_displacement)
{
// +2AH@5"5H-"9H+%1B 
int zone_idx = iBarShift(_Symbol, PERIOD_CURRENT, zone.time);
for(int k = zone_idx - 1; k >= 0; k--) {
double atr_val = context.atr_buffer[k];
double required_dist = atr_val * min_atr_moveaway;
if(zone.direction == DIRECTION_BULLISH && context.rates[k].high - zone.top >= required_dist) {
zone.has_displacement = true;
break;
}
if(zone.direction == DIRECTION_BEARISH && zone.bottom - context.rates[k].low >= required_dist) {
zone.has_displacement = true;
break;
}
}
}
// --- 2. #'*- Structure Confirmation ---
if(!zone.has_structure_confirmation)
{
for(int k = 0; k < all_swings.Total(); k++) {
SwingPoint* s = all_swings.At(k);
// +2 Minor/Major Swing 5H@4+%1B  A%0@G1H#I2!
if(s.time > zone.time && s.status >= STATUS_MINOR) {
if((zone.direction == DIRECTION_BULLISH && s.type == SWING_HIGH) ||
(zone.direction == DIRECTION_BEARISH && s.type == SWING_LOW))
{
zone.has_structure_confirmation = true;
break;
}
}
}
}
}
}
public:
CStructureAnalyzer(CZoneManager* zone_manager, double atr_filter, int max_bos_bars, ENUM_BOS_TYPE bos_type, int pd_lookback, bool use_rsi, int rsi_period, int rsi_ob, int rsi_os, bool filter_fvg, bool filter_sweep, double retest_atr_mult, ENUM_ZONE_QUALITY_LEVEL min_quality) {
m_zone_manager_ptr = zone_manager;
m_atr_noise_filter_multiplier = atr_filter;
m_max_bars_for_bos = max_bos_bars;
m_bos_confirmation_type = bos_type;
m_pd_array_lookback = pd_lookback;
m_use_rsi_filter = use_rsi;
m_rsi_overbought = rsi_ob;
m_rsi_oversold = rsi_os;
m_filter_swings_by_fvg = filter_fvg;
m_filter_swings_by_sweep = filter_sweep;
m_retest_min_atr_moveaway = retest_atr_mult;
m_min_zone_quality = min_quality;
m_market_structure_state = MS_UNDEFINED;
}
// This is the master function that orchestrates the entire analysis pipeline.
void Analyze(CDataContext &context)
{
// --- 0. @#5"!I-!9%@7I-I ---
if(m_zone_manager_ptr == NULL) return;
CArrayObj* all_swings = m_zone_manager_ptr.GetAllSwings(); // This should be GetAllSwings()
CArrayObj* all_zones = m_zone_manager_ptr.GetZoneList();
if(all_swings == NULL || all_zones == NULL) return;
// =============================================================
// --- PIPELINE 2#'4@#20+LB#*#I2A%0B  ---
// =============================================================
// --- *H'5H 1: 2#'4@#20+L*'4 (Swing Analysis) ---
// 1.1 B#B!*'4*31 2B#*#I2#2"'1H-@*!-
PromoteDailySwings(all_swings);
// 1.2 #-*'48 2H3 (Noise) I'"2@5"1 ATR
MarkNoiseSwings(all_swings, context);
// 1.3 #-*'48 2H3I'" Indicator @4H!@4! (I2@4C
I2)
FilterSwingsByRSI(all_swings, context);
// 1.4 #-*'45HD!H!5A#*H (D!H!5 FVG 2!!2)
GradeSwingsByDisplacement(all_swings, context);
// 1.5 #-*'45HD!HDI'2* 2%H-
GradeSwingsByLiquiditySweep(all_swings);
// 1.6 '4@#20+L'2!*1!1LA%0"7"1*20*'4 (Major/Minor/Inducement) 22#@4 BOS
ConfirmAndLinkSwings(all_swings, context);
// 1.7 1@#5"*'45HH22##-A%I'%C%4*L"H-"
m_zone_manager_ptr.RepopulateFilteredSwingLists();
// 1.8 *#8B#*#I2%2B"#'! (HH/HL/LH/LL) 2 Major Swings
UpdateMarketStructureState(all_swings);
// --- *H'5H 2: 2#'4@#20+LB  (Zone Analysis) ---
// 2.1 -1@*207I2-B  (Fresh . Active . Mitigated)
m_zone_manager_ptr.UpdateAllZoneStates(context);
// 2.2 #'+2A%0*#I2 Flip Zone 2B 5H@4H93%2"
CheckForFlipZones(context);
// 2.3 C+I@#B 1I+! (5#2 is_significant) B"-41I-!9%%H2*81I+!
UpdateZoneConfirmationFlags(all_zones, all_swings, context, m_retest_min_atr_moveaway);
FilterSignificantZones(context);
GradeAllZones(context);
// 2.4 3%2"B -"H22'#2!B#*#I2+%1 (1I-*8I2"@7H-'2!%- 1")
InvalidateBrokenZones(all_zones, m_zone_manager_ptr.GetMajorSwings());
//Print("--- Analysis pipeline for this bar completed. ---");
}
ENUM_MARKET_STRUCTURE_STATE GetMarketStructureState() { return m_market_structure_state; }
};
#endif