EA_SMC_Mql5/Include/CSetupScanner.mqh
2025-07-03 23:37:05 +07:00

249 lines
No EOL
21 KiB
MQL5

//+------------------------------------------------------------------+
//| 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