forked from ntsinh90/EA_SMC_Mql5
249 lines
No EOL
21 KiB
MQL5
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 |