//+------------------------------------------------------------------+ //| CSetupScanner.mqh | //| v26.0 - Advanced Swing-First Architecture | //+------------------------------------------------------------------+ #ifndef CSETUPSCANNER_MQH #define CSETUPSCANNER_MQH #include "CZoneManager.mqh" #include "SMC_DataStructures.mqh" class CSetupScanner { private: CZoneManager* m_zone_manager_ptr; int m_swing_length; long m_magic_number; double m_ob_impulse_mult; // ตัวคูณสำหรับเช็ค Impulse ของ OB double m_min_fvg_atr_mult; bool m_use_sto_filter; int m_sto_overbought; int m_sto_oversold; // --- Private Helper Functions --- bool SwingExists(datetime time) { if(m_zone_manager_ptr == NULL) return true; CArrayObj* swing_list = m_zone_manager_ptr.GetAllSwings(); if(swing_list == NULL) return true; for(int i = 0; i < swing_list.Total(); i++) { SwingPoint* s = swing_list.At(i); if(s != NULL && s.time == time) return true; } return false; } bool ZoneExists(datetime time, ENUM_ZONE_TYPE type) { if(m_zone_manager_ptr == NULL) return true; CArrayObj* zone_list = m_zone_manager_ptr.GetZoneList(); if(zone_list == NULL) return true; for(int i = 0; i < zone_list.Total(); i++) { CSignalZone* z = zone_list.At(i); if(z != NULL && z.time == time && z.zone_type == type) return true; } return false; } // --- UPDATED with Flexible Stochastic Window Check --- SwingPoint* CheckIfBarIsSwing(int index, CDataContext &context) { // ตรวจสอบขอบเขตของ Array เพื่อความปลอดภัย if(index < m_swing_length || index >= context.bars_to_load - m_swing_length) return NULL; datetime bar_time = context.rates[index].time; if(SwingExists(bar_time)) return NULL; // --- 1. ตรวจสอบ Fractal Pattern (เหมือนเดิม) --- bool is_swing_high = true; for(int j = 1; j <= m_swing_length; j++) { if(context.rates[index].high <= context.rates[index-j].high || context.rates[index].high <= context.rates[index+j].high) { is_swing_high = false; break; } } if(is_swing_high) { // --- 2. NEW: ตรวจสอบ Stochastic ในกรอบ 5 แท่งเทียน --- if(m_use_sto_filter) { bool sto_confirmed = false; // วนลูปตรวจสอบ 2 แท่งซ้าย, แท่งสวิง, 2 แท่งขวา for (int k = index - 3; k <= index + 3; k++) { // ป้องกันการ Error หาก index อยู่นอกขอบเขต if (k < 0 || k >= context.bars_to_load) continue; double sto_value = context.sto_main_buffer[k]; if (sto_value >= m_sto_overbought) { sto_confirmed = true; break; // เจอแล้ว ไม่ต้องหาต่อ } } // ถ้าใน 5 แท่งนี้ ไม่มีแท่งไหนเข้าเงื่อนไขเลย ให้ถือว่าสวิงนี้ไม่ผ่าน if (!sto_confirmed) return NULL; } // ถ้าผ่านทุกอย่าง ให้สร้างสวิง SwingPoint* new_swing = new SwingPoint(); new_swing.time = bar_time; new_swing.price = context.rates[index].high; new_swing.type = SWING_HIGH; new_swing.bar_index = index; return new_swing; } // --- ตรวจสอบ Swing Low (เหมือนเดิม) --- bool is_swing_low = true; for(int j = 1; j <= m_swing_length; j++) { if(context.rates[index].low >= context.rates[index-j].low || context.rates[index].low >= context.rates[index+j].low) { is_swing_low = false; break; } } if(is_swing_low) { // --- 2. NEW: ตรวจสอบ Stochastic ในกรอบ 5 แท่งเทียน --- if(m_use_sto_filter) { bool sto_confirmed = false; for (int k = index - 5; k <= index + 5; k++) { if (k < 0 || k >= context.bars_to_load) continue; double sto_value = context.sto_main_buffer[k]; if (sto_value <= m_sto_oversold) { sto_confirmed = true; break; } } if (!sto_confirmed) return NULL; } // ถ้าผ่านทุกอย่าง ให้สร้างสวิง SwingPoint* new_swing = new SwingPoint(); new_swing.time = bar_time; new_swing.price = context.rates[index].low; new_swing.type = SWING_LOW; new_swing.bar_index = index; return new_swing; } return NULL; } void FindOrderBlockAtSwing(SwingPoint* swing, CDataContext &context) { if(swing == NULL) return; if(ZoneExists(swing.time, ZONE_OB) || (m_zone_manager_ptr.GetTradedLevelInfo(swing.time) != NULL)) return; int ob_candle_idx = swing.bar_index; int impulse_candle_idx = ob_candle_idx - 1; if(impulse_candle_idx < 0) return; double impulse_body = MathAbs(context.rates[impulse_candle_idx].close - context.rates[impulse_candle_idx].open); double atr_val = context.atr_buffer[impulse_candle_idx]; if(atr_val <= 0 || impulse_body < atr_val * m_ob_impulse_mult) return; bool is_bullish_ob = (context.rates[ob_candle_idx].close < context.rates[ob_candle_idx].open) && (context.rates[impulse_candle_idx].close > context.rates[impulse_candle_idx].open); bool is_bearish_ob = (context.rates[ob_candle_idx].close > context.rates[ob_candle_idx].open) && (context.rates[impulse_candle_idx].close < context.rates[impulse_candle_idx].open); if(!is_bullish_ob && !is_bearish_ob) return; int fvg_check_idx = impulse_candle_idx - 1; if(fvg_check_idx < 0) return; bool fvg_confirms = (is_bullish_ob && context.rates[ob_candle_idx].high < context.rates[fvg_check_idx].low) || (is_bearish_ob && context.rates[ob_candle_idx].low > context.rates[fvg_check_idx].high); if(fvg_confirms) { CSignalZone* new_ob = new CSignalZone(m_magic_number); new_ob.time = swing.time; new_ob.top = context.rates[ob_candle_idx].high; new_ob.bottom = context.rates[ob_candle_idx].low; new_ob.zone_type = ZONE_OB; new_ob.direction = is_bullish_ob ? DIRECTION_BULLISH : DIRECTION_BEARISH; new_ob.origin_swing_ptr = swing; m_zone_manager_ptr.AddZone(new_ob); } } // หา FVG ที่เกิดขึ้นในขา (Leg) ที่พุ่งออกจากสวิง void FindFVGAfterSwing(SwingPoint* swing, CDataContext &context) { if(swing == NULL) return; // มองหา FVG ใน 5 แท่งเทียนหลังสวิง 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; datetime fvg_time = context.rates[fvg_middle_idx].time; if(ZoneExists(fvg_time, ZONE_FVG)) continue; double fvg_top = 0, fvg_bottom = 0; ENUM_DIRECTION dir = DIRECTION_NONE; if(swing.type == SWING_LOW && context.rates[fvg_middle_idx-1].low > context.rates[fvg_middle_idx+1].high) { dir = DIRECTION_BULLISH; fvg_top = context.rates[fvg_middle_idx-1].low; fvg_bottom = context.rates[fvg_middle_idx+1].high; } else if(swing.type == SWING_HIGH && context.rates[fvg_middle_idx-1].high < context.rates[fvg_middle_idx+1].low) { dir = DIRECTION_BEARISH; fvg_top = context.rates[fvg_middle_idx-1].high; fvg_bottom = context.rates[fvg_middle_idx+1].low; } if(dir != DIRECTION_NONE) { double fvg_size = MathAbs(fvg_top - fvg_bottom); double atr_val = context.atr_buffer[fvg_middle_idx]; if(atr_val > 0 && fvg_size >= atr_val * m_min_fvg_atr_mult) { CSignalZone* new_fvg = new CSignalZone(m_magic_number); new_fvg.time = fvg_time; new_fvg.top = fvg_top; new_fvg.bottom = fvg_bottom; new_fvg.zone_type = ZONE_FVG; new_fvg.direction = dir; new_fvg.origin_swing_ptr = swing; m_zone_manager_ptr.AddZone(new_fvg); break; // เจอ FVG ที่ดีอันแรกใน Leg แล้วหยุดหา } } } } // ฟังก์ชันควบคุมหลัก ที่ถูกเรียกเมื่อเจอสวิงใหม่ void FindZonesFromSwing(SwingPoint* swing, CDataContext &context) { if(swing == NULL) return; FindOrderBlockAtSwing(swing, context); FindFVGAfterSwing(swing, context); } public: CSetupScanner(CZoneManager* zone_manager, int swing_len, long magic, double min_fvg_mult, bool use_sto, int sto_ob, int sto_os) { m_zone_manager_ptr = zone_manager; m_swing_length = swing_len > 0 ? swing_len : 1; // ป้องกันค่า 0 m_magic_number = magic; m_ob_impulse_mult = 1.5; m_min_fvg_atr_mult = min_fvg_mult; m_use_sto_filter = use_sto; m_sto_overbought = sto_ob; m_sto_oversold = sto_os; } ~CSetupScanner() {} void ScanHistory(CDataContext &context) { if(m_zone_manager_ptr == NULL) return; for(int i = context.bars_to_load - m_swing_length - 1; i >= m_swing_length; i--) { SwingPoint* new_swing = CheckIfBarIsSwing(i, context); if(new_swing != NULL) { m_zone_manager_ptr.AddSwing(new_swing); FindZonesFromSwing(new_swing, context); } } } void ScanIncremental(CDataContext &context, int new_bars_count) { if(m_zone_manager_ptr == NULL) return; for(int i = 1; i <= new_bars_count; i++) { int bar_to_check_idx = m_swing_length + (new_bars_count - i); if(bar_to_check_idx >= context.bars_to_load) continue; SwingPoint* new_swing = CheckIfBarIsSwing(bar_to_check_idx, context); if(new_swing != NULL) { m_zone_manager_ptr.AddSwing(new_swing); FindZonesFromSwing(new_swing, context); } } } }; #endif