//+------------------------------------------------------------------+ //| Swing.mqh — pivot & swing set H0–L1 (tham khảo HyperICT) | //+------------------------------------------------------------------+ #ifndef ICT2026_SWING_MQH #define ICT2026_SWING_MQH #include bool IctIsSwingHigh(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const int range) { const int str = MathMax(1, range); const double h = iHigh(sym, tf, shift); for(int k = 1; k <= str; k++) { if(shift + k >= Bars(sym, tf) || shift - k < 0) return false; if(h <= iHigh(sym, tf, shift + k) || h <= iHigh(sym, tf, shift - k)) return false; } return true; } bool IctIsSwingLow(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const int range) { const int str = MathMax(1, range); const double l = iLow(sym, tf, shift); for(int k = 1; k <= str; k++) { if(shift + k >= Bars(sym, tf) || shift - k < 0) return false; if(l >= iLow(sym, tf, shift + k) || l >= iLow(sym, tf, shift - k)) return false; } return true; } void IctSortSwingsByTime(IctSwingPoint &pts[], const int count) { for(int i = 0; i < count - 1; i++) for(int j = i + 1; j < count; j++) if(pts[j].time < pts[i].time) { const IctSwingPoint t = pts[i]; pts[i] = pts[j]; pts[j] = t; } } void IctCollectSwings(const string sym, const ENUM_TIMEFRAMES tf, const int range, const int lookback, IctSwingPoint &highs[], IctSwingPoint &lows[]) { ArrayResize(highs, 0); ArrayResize(lows, 0); const int str = MathMax(1, range); const int bars = Bars(sym, tf); const int last = MathMin(lookback, bars - str - 1); if(last < str + 5) return; int nH = 0, nL = 0; for(int sh = last; sh >= 1; sh--) { if(IctIsSwingHigh(sym, tf, sh, range)) { ArrayResize(highs, nH + 1); highs[nH].price = iHigh(sym, tf, sh); highs[nH].time = iTime(sym, tf, sh); highs[nH].shift = sh; nH++; } if(IctIsSwingLow(sym, tf, sh, range)) { ArrayResize(lows, nL + 1); lows[nL].price = iLow(sym, tf, sh); lows[nL].time = iTime(sym, tf, sh); lows[nL].shift = sh; nL++; } } IctSortSwingsByTime(highs, nH); IctSortSwingsByTime(lows, nL); } IctSwingPoint IctFindMostRecentOlder(IctSwingPoint &pts[], const int count, const int anchorShift) { IctSwingPoint empty; empty.Clear(); int best = -1; for(int i = 0; i < count; i++) { if(pts[i].shift <= anchorShift) continue; if(best < 0 || pts[i].shift < pts[best].shift) best = i; } if(best < 0) return empty; return pts[best]; } IctSwingSet IctPickStructuralBear(IctSwingPoint &highs[], const int nH, IctSwingPoint &lows[], const int nL) { IctSwingSet out; out.Clear(); if(nH < 2 || nL < 2) return out; out.l0 = lows[nL - 1]; out.hasL0 = true; out.l1 = IctFindMostRecentOlder(lows, nL, out.l0.shift); if(!out.l1.Valid()) return out; out.hasL1 = true; out.h0 = IctFindMostRecentOlder(highs, nH, out.l1.shift); if(!out.h0.Valid()) return out; out.hasH0 = true; out.h1 = IctFindMostRecentOlder(highs, nH, out.h0.shift); if(!out.h1.Valid()) return out; out.hasH1 = true; return out; } IctSwingSet IctPickStructuralBull(IctSwingPoint &highs[], const int nH, IctSwingPoint &lows[], const int nL) { IctSwingSet out; out.Clear(); if(nH < 2 || nL < 2) return out; out.h0 = highs[nH - 1]; out.hasH0 = true; out.l0 = IctFindMostRecentOlder(lows, nL, out.h0.shift); if(!out.l0.Valid()) return out; out.hasL0 = true; out.h1 = IctFindMostRecentOlder(highs, nH, out.l0.shift); if(!out.h1.Valid()) return out; out.hasH1 = true; out.l1 = IctFindMostRecentOlder(lows, nL, out.h1.shift); if(!out.l1.Valid()) return out; out.hasL1 = true; return out; } ENUM_ICT_STRUCT IctClassifyStructure(IctSwingSet &sw) { if(!sw.IsComplete()) return ICT_STRUCT_NONE; if(sw.h0.price > sw.h1.price && sw.l0.price > sw.l1.price) return ICT_STRUCT_BULL; if(sw.h0.price < sw.h1.price && sw.l0.price < sw.l1.price) return ICT_STRUCT_BEAR; return ICT_STRUCT_NONE; } bool IctIsPullbackValid(IctSwingSet &sw, const double ratio) { if(!sw.IsComplete() || ratio <= 0.0) return true; const double impulse = MathAbs(sw.h1.price - sw.l1.price); const double pullback = MathAbs(sw.h1.price - sw.l0.price); if(impulse <= 0.0) return false; return pullback >= impulse * ratio; } IctSwingSet IctBuildSwingSet(const string sym, const ENUM_TIMEFRAMES tf, const int range, const int lookback, ENUM_ICT_STRUCT &biasOut) { IctSwingSet out; out.Clear(); biasOut = ICT_STRUCT_NONE; IctSwingPoint highs[], lows[]; IctCollectSwings(sym, tf, range, lookback, highs, lows); const int nH = ArraySize(highs); const int nL = ArraySize(lows); IctSwingSet bear = IctPickStructuralBear(highs, nH, lows, nL); IctSwingSet bull = IctPickStructuralBull(highs, nH, lows, nL); const bool hasBear = bear.IsComplete(); const bool hasBull = bull.IsComplete(); const ENUM_ICT_STRUCT biasBear = hasBear ? IctClassifyStructure(bear) : ICT_STRUCT_NONE; const ENUM_ICT_STRUCT biasBull = hasBull ? IctClassifyStructure(bull) : ICT_STRUCT_NONE; if(biasBear == ICT_STRUCT_BEAR) { out = bear; biasOut = ICT_STRUCT_BEAR; } else if(biasBull == ICT_STRUCT_BULL) { out = bull; biasOut = ICT_STRUCT_BULL; } else if(hasBear) { out = bear; biasOut = IctClassifyStructure(bear); } else if(hasBull) { out = bull; biasOut = IctClassifyStructure(bull); } return out; } // Intraday: 2 đỉnh + 2 đáy gần nhất (mới hơn = phần tử cuối sau sort time) bool IctTakeLastTwoSwings(IctSwingPoint &pts[], const int count, IctSwingPoint &older, IctSwingPoint &newer) { older.Clear(); newer.Clear(); if(count < 2) return false; older = pts[count - 2]; newer = pts[count - 1]; return older.Valid() && newer.Valid(); } int IctFilterSwingsByMaxShift(IctSwingPoint &pts[], const int count, const int maxShift) { if(maxShift <= 0 || count < 1) return count; int w = 0; for(int i = 0; i < count; i++) { if(pts[i].shift > maxShift) continue; if(w != i) pts[w] = pts[i]; w++; } if(w < count) ArrayResize(pts, w); return w; } // Phân loại HH-HL / LH-LL từ pivot gần nhất — tránh ghép leg 4 swing cũ (sai trên H1) bool IctBuildSwingSetRecentPivots(const string sym, const ENUM_TIMEFRAMES tf, const int range, const int lookback, const int maxRecentShift, IctSwingSet &out, ENUM_ICT_STRUCT &biasOut) { out.Clear(); biasOut = ICT_STRUCT_NONE; IctSwingPoint highs[], lows[]; IctCollectSwings(sym, tf, range, lookback, highs, lows); int nH = ArraySize(highs); int nL = ArraySize(lows); if(maxRecentShift > 0) { nH = IctFilterSwingsByMaxShift(highs, nH, maxRecentShift); nL = IctFilterSwingsByMaxShift(lows, nL, maxRecentShift); } IctSwingPoint hOld, hNew, lOld, lNew; if(!IctTakeLastTwoSwings(highs, nH, hOld, hNew)) return false; if(!IctTakeLastTwoSwings(lows, nL, lOld, lNew)) return false; out.h0 = hNew; out.hasH0 = true; out.h1 = hOld; out.hasH1 = true; out.l0 = lNew; out.hasL0 = true; out.l1 = lOld; out.hasL1 = true; const bool hh = hNew.price > hOld.price + _Point; const bool hl = lNew.price > lOld.price + _Point; const bool lh = hNew.price < hOld.price - _Point; const bool ll = lNew.price < lOld.price - _Point; if(hh && hl) biasOut = ICT_STRUCT_BULL; else if(lh && ll) biasOut = ICT_STRUCT_BEAR; else biasOut = ICT_STRUCT_NONE; return out.IsComplete(); } void IctGetKeyLevels(const ENUM_ICT_STRUCT bias, IctSwingSet &sw, double &keyLv1, double &keyLv2) { keyLv1 = 0.0; keyLv2 = 0.0; if(!sw.IsComplete()) return; if(bias == ICT_STRUCT_BULL) { keyLv1 = sw.l0.price; keyLv2 = sw.h0.price; } else if(bias == ICT_STRUCT_BEAR) { keyLv1 = sw.h0.price; keyLv2 = sw.l0.price; } } #endif