From 87312df1f431213d0451dcaefd3caf39b013ecbe Mon Sep 17 00:00:00 2001 From: Bell Date: Sat, 6 Dec 2025 22:40:49 +0700 Subject: [PATCH] update --- Experts/EA_ICT_v2.mq5 | 238 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 195 insertions(+), 43 deletions(-) diff --git a/Experts/EA_ICT_v2.mq5 b/Experts/EA_ICT_v2.mq5 index 96c82f2..0c233dc 100644 --- a/Experts/EA_ICT_v2.mq5 +++ b/Experts/EA_ICT_v2.mq5 @@ -14,8 +14,8 @@ input bool DetectMSS_LTF = true; // Cấu hình Swing input int htfSwingRange = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy -input int mtfSwingRange = 3; // X: số nến trước và sau để xác định 1 đỉnh/đáy -input int ltfSwingRange = 3; // X: số nến trước và sau để xác định 1 đỉnh/đáy +input int mtfSwingRange = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy +input int ltfSwingRange = 2; // X: số nến trước và sau để xác định 1 đỉnh/đáy input int MaxSwingKeep = 2; // Số đỉnh/đáy gần nhất cần lưu (bạn yêu cầu 2) // Cấu hình vẽ Swing trên chart @@ -87,6 +87,7 @@ struct MSSInfo { struct BOSInfo { bool found; + bool isReal; int direction; // 1 = up, -1 = down datetime break_time; // time của bar xác nhận (bar đóng) double break_price; // close của bar xác nhận @@ -154,17 +155,12 @@ void StoreNewBOS(int slot, const BOSInfo &bos) BOS_Count[slot] = 2; } - if(PrintToExperts) - PrintFormat("StoreNewBOS(fix): slot=%d -> stored new at index0 direction=%d broken_price=%.5f time=%s count=%d", - slot, bos.direction, bos.broken_sw_price, TimeToString(bos.broken_sw_time, TIME_DATE|TIME_MINUTES), BOS_Count[slot]); - // Vẽ lại tất cả BOS cho slot (DrawAllBOSForSlot sẽ tạo lại objects và ghi BOS_Names) DrawAllBOSForSlot(slot); } // Vẽ tất cả BOS đã lưu cho 1 slot (0=HTF,1=MTF,2=LTF) -// Nếu BOS_Count[slot] == 0 thì không vẽ gì. -// Lưu ý: DrawBOS sẽ quản lý tên object cho từng storeIndex (0/1). +// SỬA: không xóa theo prefix chung nữa, chỉ xóa các composite names đã lưu trong BOS_Names void DrawAllBOSForSlot(int slot) { if(!ShowSwingMarkers) return; @@ -172,25 +168,54 @@ void DrawAllBOSForSlot(int slot) if(cnt <= 0) return; string sym = Symbol(); - // --- NEW: xóa mọi object BOS thuộc slot này trước khi vẽ lại để tránh tích lũy --- - string bosPrefix = SwingObjPrefix + "BOS_" + IntegerToString(slot) + "_"; - int totalObjs = ObjectsTotal(0); - for(int i = totalObjs - 1; i >= 0; i--) + // --- SAFE CLEANUP: xóa composite object cũ mà chúng ta từng lưu tên trong BOS_Names --- + for(int j = 0; j < 2; j++) { - string nm = ObjectName(0, i); - if(StringFind(nm, bosPrefix, 0) == 0) - ObjectDelete(0, nm); + if(StringLen(BOS_Names[slot][j]) > 0) + { + // DeleteCompositeBOS sẽ kiểm tra tồn tại từng object con trước khi xóa + DeleteCompositeBOS(BOS_Names[slot][j]); + BOS_Names[slot][j] = ""; // clear saved composite name so DrawBOS will recreate + BOS_HaveZone[slot][j] = false; + if(PrintToExperts) PrintFormat("DrawAllBOSForSlot: slot=%d cleared old composite index=%d", slot, j); + } } // draw older first (index1) then newer (index0) + // Note: DrawBOS sẽ set BOS_Names[slot][storeIndex] after drawing, so we pass storeIndex to persist names if(cnt == 2) { - if(BOSStore[slot][1].found) DrawBOS(sym, slot, BOSStore[slot][1], 1); - if(BOSStore[slot][0].found) DrawBOS(sym, slot, BOSStore[slot][0], 0); + if(BOSStore[slot][1].found) + { + DrawBOS(sym, slot, BOSStore[slot][1], 1); + // If DrawBOS for some reason didn't set BOS_Names (e.g. missing data), ensure we don't leave inconsistent state + if(StringLen(BOS_Names[slot][1]) == 0) + { + // attempt fallback: create minimal line to mark BOS to avoid total disappearance + BOS_Names[slot][1] = ""; // keep empty; optional: log + if(PrintToExperts) PrintFormat("DrawAllBOSForSlot: slot=%d index1 drawn but composite name empty", slot); + } + } + + if(BOSStore[slot][0].found) + { + DrawBOS(sym, slot, BOSStore[slot][0], 0); + if(StringLen(BOS_Names[slot][0]) == 0) + { + if(PrintToExperts) PrintFormat("DrawAllBOSForSlot: slot=%d index0 drawn but composite name empty", slot); + } + } } else // cnt == 1 { - if(BOSStore[slot][0].found) DrawBOS(sym, slot, BOSStore[slot][0], 0); + if(BOSStore[slot][0].found) + { + DrawBOS(sym, slot, BOSStore[slot][0], 0); + if(StringLen(BOS_Names[slot][0]) == 0) + { + if(PrintToExperts) PrintFormat("DrawAllBOSForSlot: slot=%d single drawn but composite name empty", slot); + } + } } } @@ -235,6 +260,7 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, out.broken_sw_time = 0; out.confirm_bar_index = -1; out.consec_closes = 0; + out.isReal = false; // default // Cần ít nhất 1 swing high hoặc low (ở slot) double sh0 = (SwingHighCountTF[slot] >= 1) ? SwingHighPriceTF[slot][0] : 0.0; @@ -247,16 +273,12 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tol = (point > 0.0) ? point * 0.5 : 0.0; - // index của bar gần nhất thuộc timeframe: iBarShift bằng time => sử dụng iBarShift(TimeOf reference) - // Tuy nhiên ta scan bars newer (index 0 = current unclosed, 1 = last closed). - // Thường ta kiểm closed bars: index >=1 int barsAvailable = iBars(symbol, timeframe); if(barsAvailable < 2) return out; - // --- BULLISH BOS: price close > sh0 + threshold --- + // --- BULLISH BOS: first try REAL (close > sh0 + threshold) --- if(sh0 != 0.0) { - // scan last lookForwardBars closed bars (index 1..lookForwardBars) for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++) { double c = iClose(symbol, timeframe, idx); @@ -264,17 +286,16 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, double h = iHigh(symbol, timeframe, idx); datetime t = iTime(symbol, timeframe, idx); if(c == 0 || h == 0) continue; - // require close strictly greater than sh0 + minBreakPoints + + // REAL: require close strictly greater than sh0 + minBreakPoints if(c > sh0 + minBreakPoints) { - // check consec closes starting from this idx going newer (idx, idx-1, ...) int consec = 0; for(int k = idx; k >= 1 && k >= idx - (consecRequired - 1); k--) { double ck = iClose(symbol, timeframe, k); double ok = iOpen(symbol, timeframe, k); if(ck == 0) break; - // if require body check then close must be bullish (ck > ok) if(useBodyCheck) { if(ck > ok && ck > sh0 + minBreakPoints) consec++; @@ -296,13 +317,42 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, out.broken_sw_time = SwingHighTimeTF[slot][0]; out.confirm_bar_index = idx; out.consec_closes = consec; + out.isReal = true; return out; } } } + + // If no REAL bullish BOS found, check for FAKE bullish BOS: + // condition: high > sh0 + threshold (wick exceeded), but close <= sh0 + threshold + for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++) + { + double c = iClose(symbol, timeframe, idx); + double h = iHigh(symbol, timeframe, idx); + datetime t = iTime(symbol, timeframe, idx); + if(h == 0) continue; + + if(h > sh0 + minBreakPoints && c <= sh0 + minBreakPoints) + { + // classify as fake BOS + out.found = true; + out.direction = 1; + out.break_time = t; + out.break_price = c; + out.broken_sw_price = sh0; + out.broken_sw_time = SwingHighTimeTF[slot][0]; + out.confirm_bar_index = idx; + out.consec_closes = 0; + out.isReal = false; + if(PrintToExperts) + PrintFormat("DetectBOSOnSlot: slot=%d FAKE BULL detected idx=%d time=%s high=%.5f close=%.5f sh0=%.5f", + slot, idx, TimeToString(t, TIME_DATE|TIME_MINUTES), h, c, sh0); + return out; + } + } } - // --- BEARISH BOS: price close < sl0 - threshold --- + // --- BEARISH BOS: first try REAL (close < sl0 - threshold) --- if(sl0 != 0.0) { for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++) @@ -312,6 +362,7 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, double l = iLow(symbol, timeframe, idx); datetime t = iTime(symbol, timeframe, idx); if(c == 0 || l == 0) continue; + if(c < sl0 - minBreakPoints) { int consec = 0; @@ -341,15 +392,46 @@ BOSInfo DetectBOSOnSlot(string symbol, ENUM_TIMEFRAMES timeframe, int slot, out.broken_sw_time = SwingLowTimeTF[slot][0]; out.confirm_bar_index = idx; out.consec_closes = consec; + out.isReal = true; return out; } } } + + // If no REAL bearish BOS found, check for FAKE bearish BOS: + // condition: low < sl0 - threshold (wick exceeded downward), but close >= sl0 - threshold + for(int idx = 1; idx <= MathMin(lookForwardBars, barsAvailable - 1); idx++) + { + double c = iClose(symbol, timeframe, idx); + double l = iLow(symbol, timeframe, idx); + datetime t = iTime(symbol, timeframe, idx); + if(l == 0) continue; + + if(l < sl0 - minBreakPoints && c >= sl0 - minBreakPoints) + { + // classify as fake BOS + out.found = true; + out.direction = -1; + out.break_time = t; + out.break_price = c; + out.broken_sw_price = sl0; + out.broken_sw_time = SwingLowTimeTF[slot][0]; + out.confirm_bar_index = idx; + out.consec_closes = 0; + out.isReal = false; + if(PrintToExperts) + PrintFormat("DetectBOSOnSlot: slot=%d FAKE BEAR detected idx=%d time=%s low=%.5f close=%.5f sl0=%.5f", + slot, idx, TimeToString(t, TIME_DATE|TIME_MINUTES), l, c, sl0); + return out; + } + } } return out; } +// DrawBOS: vẽ 1 BOS (không shift/xóa store) — parameters: symbol, slot, bos info, storeIndex (0/1) +// Nếu bạn gọi DrawBOS với storeIndex = -1 thì hàm sẽ chỉ tạo objects tạm (không lưu tên). // DrawBOS: vẽ 1 BOS (không shift/xóa store) — parameters: symbol, slot, bos info, storeIndex (0/1) // Nếu bạn gọi DrawBOS với storeIndex = -1 thì hàm sẽ chỉ tạo objects tạm (không lưu tên). void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) @@ -396,15 +478,10 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) if(t_h_end == t_start) t_h_end = (datetime)((long)t_start + (long)bar_secs); // --- TẠO TÊN UNIQUE: bao gồm slot + storeIndex + thời điểm để tránh trùng --- - // nếu storeIndex == -1 thì dùng -1 trong tên để vẫn unique cho temp draws int idxPart = (storeIndex >= 0) ? storeIndex : -1; - - // use broken_sw_time (BOSInfo field) as stable stamp; fallback to t_start int timeStampInt = (info.broken_sw_time != 0) ? (int)info.broken_sw_time : (int)t_start; string stamp = StringFormat("%d_%d_%d", slot, idxPart, timeStampInt); - - string nm_line = basePrefix + "HLINE_" + stamp; string nm_arrow = basePrefix + "ARW_" + stamp; string nm_txt = basePrefix + "LBL_" + stamp; @@ -416,7 +493,6 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) { if(StringLen(BOS_Names[slot][storeIndex]) > 0) { - // kiểm tra tồn tại của các object con string parts[]; int n = StringSplit(BOS_Names[slot][storeIndex], '|', parts); bool all_exist = true; @@ -428,7 +504,6 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) if(all_exist) { - // đã vẽ trước đó, không cần vẽ lại if(PrintToExperts) PrintFormat("DrawBOS: slot=%d storeIndex=%d -> already drawn, skip redraw", slot, storeIndex); return; } @@ -443,12 +518,14 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) } } - // Create line + // Create horizontal line (trend object used as line) if(!ObjectCreate(0, nm_line, OBJ_TREND, 0, t_start, p_h, t_h_end, p_h)) PrintFormat("DrawBOS: Cannot create %s", nm_line); else { - ObjectSetInteger(0, nm_line, OBJPROP_COLOR, (info.direction==1)?clrLime:clrRed); + // line color: real use green/red, fake use yellow + int lineColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow; + ObjectSetInteger(0, nm_line, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, nm_line, OBJPROP_WIDTH, 2); ObjectSetInteger(0, nm_line, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, nm_line, OBJPROP_RAY_RIGHT, false); @@ -456,25 +533,28 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) ObjectSetInteger(0, nm_line, OBJPROP_BACK, true); } - // Arrow + // Arrow (or fallback text arrow) at t_h_end,p_h if(ObjectCreate(0, nm_arrow, OBJ_ARROW, 0, t_h_end, p_h)) { int code = (info.direction==1) ? 233 : 234; #ifdef __MQL5__ ObjectSetInteger(0, nm_arrow, OBJPROP_ARROWCODE, code); #endif - ObjectSetInteger(0, nm_arrow, OBJPROP_COLOR, (info.direction==1)?clrLime:clrRed); + int arrowColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow; + ObjectSetInteger(0, nm_arrow, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, nm_arrow, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm_arrow, OBJPROP_BACK, true); } else { + // fallback to text arrow if arrow object cannot be created string nm_fallback = basePrefix + "ARW_T_" + stamp; if(ObjectCreate(0, nm_fallback, OBJ_TEXT, 0, t_h_end, p_h)) { string arrowTxt = (info.direction==1) ? "▲" : "▼"; ObjectSetString(0, nm_fallback, OBJPROP_TEXT, arrowTxt); - ObjectSetInteger(0, nm_fallback, OBJPROP_COLOR, (info.direction==1)?clrLime:clrRed); + int arrowColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow; + ObjectSetInteger(0, nm_fallback, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, nm_fallback, OBJPROP_FONTSIZE, SwingMarkerFontSize+2); ObjectSetInteger(0, nm_fallback, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm_fallback, OBJPROP_BACK, true); @@ -485,12 +565,16 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) double y_offset = SymbolInfoDouble(symbol, SYMBOL_POINT) * 4.0; double label_price = (info.direction==1) ? (p_h + y_offset) : (p_h - y_offset); + // Label text and color depending on real/fake + string labelText = info.isReal ? "BOS" : "FAKE"; + int labelColor = info.isReal ? ((info.direction==1)?clrLime:clrRed) : clrYellow; + if(!ObjectCreate(0, nm_txt, OBJ_TEXT, 0, t_h_end, label_price)) PrintFormat("DrawBOS: Cannot create %s", nm_txt); else { - ObjectSetString(0, nm_txt, OBJPROP_TEXT, "BOS"); - ObjectSetInteger(0, nm_txt, OBJPROP_COLOR, (info.direction==1)?clrLime:clrRed); + ObjectSetString(0, nm_txt, OBJPROP_TEXT, labelText); + ObjectSetInteger(0, nm_txt, OBJPROP_COLOR, labelColor); ObjectSetInteger(0, nm_txt, OBJPROP_FONTSIZE, SwingMarkerFontSize + 1); ObjectSetInteger(0, nm_txt, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm_txt, OBJPROP_BACK, true); @@ -500,7 +584,6 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) string compositeName = nm_line + "|" + nm_arrow + "|" + nm_txt; if(storeIndex >= 0) { - // nếu trước đó có tên ở vị trí này -> xóa (tránh leak) -- nhưng đã xử lý ở trên if(StringLen(BOS_Names[slot][storeIndex]) > 0) DeleteCompositeBOS(BOS_Names[slot][storeIndex]); BOS_Names[slot][storeIndex] = compositeName; @@ -508,7 +591,7 @@ void DrawBOS(string symbol, int slot, const BOSInfo &info, int storeIndex = -1) } if(PrintToExperts) - PrintFormat("DrawBOS: slot=%d drew BOS at storeIndex=%d names=(%s)", slot, storeIndex, compositeName); + PrintFormat("DrawBOS: slot=%d drew BOS at storeIndex=%d kind=%s names=(%s)", slot, storeIndex, (info.isReal ? "REAL":"FAKE"), compositeName); } // xóa compositeName dạng "lineName|arrowName|labelName" @@ -947,6 +1030,9 @@ void DrawMss(string symbol, ENUM_TIMEFRAMES tf, const MSSInfo &mss, int slot) ObjectDelete(0, nm); } + // Thời lượng bar (giây) để dùng khi cần + datetime bar_secs = (datetime)PeriodSeconds(tf); + // VẼ LABEL SWEEP tại chính xác sweep_time và sweep_price (không offset) if(mss.sweep_time != 0 && mss.sweep_price != 0.0) { @@ -986,6 +1072,72 @@ void DrawMss(string symbol, ENUM_TIMEFRAMES tf, const MSSInfo &mss, int slot) PrintFormat("DrawMss: Cannot create %s", lblMss); } } + + // --------- NEW: draw horizontal guide line from swept swing to sweep candle ---------- + // Use swept_swing_time & swept_swing_price (from MSSInfo). If missing, try fallback to broken_swing_time/price or sweep_time. + datetime t_swing = mss.swept_swing_time; + double p_swing = mss.swept_swing_price; + + // fallback if missing + if(t_swing == 0 || p_swing == 0.0) + { + // try broken swing + if(mss.broken_swing_time != 0 && mss.broken_swing_price != 0.0) + { + t_swing = mss.broken_swing_time; + p_swing = mss.broken_swing_price; + } + else if(mss.sweep_time != 0 && mss.sweep_price != 0.0) + { + // last resort: draw small mark at sweep time & price + t_swing = mss.sweep_time - bar_secs; // place a little earlier + p_swing = mss.sweep_price; + } + } + + // Only draw if we've got valid time & price + if(t_swing != 0 && p_swing != 0.0 && mss.sweep_time != 0) + { + // name with slot stamp to ensure uniqueness + string lineName = basePrefix + "GUIDE_" + IntegerToString((int)mss.sweep_time); + + // We want a horizontal visual guide at the swing price from swing time -> sweep time. + // Use OBJ_TREND with two points: (t_swing, p_swing) -> (mss.sweep_time, p_swing) + if(!ObjectCreate(0, lineName, OBJ_TREND, 0, t_swing, p_swing, mss.sweep_time, p_swing)) + { + PrintFormat("DrawMss: Cannot create guide line %s", lineName); + } + else + { + ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrMagenta); + ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, lineName, OBJPROP_STYLE, STYLE_DOT); + ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, false); + ObjectSetInteger(0, lineName, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, lineName, OBJPROP_BACK, true); + } + + // Optional: small label at the sweep_time to show swing price value + string valLabel = basePrefix + "GUIDE_VAL_" + IntegerToString((int)mss.sweep_time); + if(!ObjectCreate(0, valLabel, OBJ_TEXT, 0, mss.sweep_time, p_swing)) + { + // ignore if cannot create + } + else + { + string txt = DoubleToString(p_swing, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); + ObjectSetString(0, valLabel, OBJPROP_TEXT, txt); + ObjectSetInteger(0, valLabel, OBJPROP_COLOR, clrMagenta); + ObjectSetInteger(0, valLabel, OBJPROP_FONTSIZE, SwingMarkerFontSize - 1); + ObjectSetInteger(0, valLabel, OBJPROP_BACK, true); + ObjectSetInteger(0, valLabel, OBJPROP_SELECTABLE, false); + } + } + + if(PrintToExperts) + PrintFormat("DrawMss: slot=%d drew MSS visuals (sweep_time=%s sweep_price=%.5f swept_swing_time=%s swept_swing_price=%.5f)", + slot, TimeToString(mss.sweep_time, TIME_DATE|TIME_MINUTES), mss.sweep_price, + TimeToString(mss.swept_swing_time, TIME_DATE|TIME_MINUTES), mss.swept_swing_price); } // Hàm tiện ích chuyển giá trị xu hướng thành chuỗi