//+------------------------------------------------------------------+ //| MyICT.mq5 | //| Big/Small structure + State machine (NO_TREND → PULLBACK → …) | //+------------------------------------------------------------------+ #property copyright "MyICT" #property version "1.11" #property description "ICT MSS: sweep + BOS + FVG → limit @ breaker | R% | stats" #include //+------------------------------------------------------------------+ enum ENUM_ICT_TREND { ICT_TREND_UNKNOWN = 0, ICT_TREND_UP = 1, ICT_TREND_DOWN = 2, ICT_TREND_NEUTRAL = 3 }; enum ENUM_ICT_STATE { ICT_STATE_NO_TREND = 0, ICT_STATE_PULLBACK = 1, ICT_STATE_WAITING_TRIGGER = 2, ICT_STATE_LIMIT_ORDERED = 3, ICT_STATE_ON_TRADE = 4, ICT_STATE_NONE = 5 }; //+------------------------------------------------------------------+ input group "══ Swing (nến trước/sau pivot) ══" input int InpBigSwingRange = 24; input int InpSmallSwingRange = 2; input int InpSwingLookback = 400; input group "══ Discount / Premium (sóng Big) ══" input double InpZoneEqPct = 50.0; // % từ đáy/đỉnh sóng → equilibrium input double InpZoneTolPoints = 5.0; // dung sai (point) input group "══ MSS / Liquidity / Breaker ══" input int InpMssLookback = 50; // quét sweep + BOS input int InpMssMaxRecent = 10; // MSS chỉ trên N nến gần nhất input double InpSweepTolPoints = 3.0; // wick quét liquidity (point) input double InpSweepTolAtr = 0.05; // + ×ATR input double InpMssDispAtrMult = 0.30; // nến BOS: body tối thiểu ×ATR input bool InpRequireFvg = true; // bắt buộc FVG sau BOS input double InpBreakerOffsetAtr = 0.0; // offset entry breaker (×ATR) input int InpLimitExpireBars = 24; // hủy limit sau N nến input group "══ Session (London / New York) ══" input bool InpUseSessionFilter = true; // chỉ đặt lệnh trong phiên input bool InpSessionUseUtc = false; // false = giờ server broker input int InpLondonStartHour = 8; // London [0..23] input int InpLondonEndHour = 17; input int InpNewYorkStartHour = 13; // New York [0..23] input int InpNewYorkEndHour = 22; input int InpSessionAvoidLastMin = 0; // tránh N phút cuối mỗi phiên (0=tắt) input group "══ Trigger & quản lý lệnh ══" input bool InpTradeEnabled = true; input ulong InpMagic = 20260620; input double InpRiskPercent = 1.0; // 1R = % balance input int InpATRPeriod = 14; input double InpSlAtrMult = 0.25; // SL trên đỉnh sweep / dưới đáy sweep + ATR× input double InpTpAtrBuffer = 0.25; // TP trước đỉnh/đáy sóng Big −/+ ATR× input bool InpUseMinRR = true; // lọc R:R tối thiểu input double InpMinRR = 1.5; // R:R tối thiểu (1:1.5 → 1.5) input int InpSlippagePoints = 30; input bool InpOnePosition = true; input group "══ Thống kê (góc dưới-trái) ══" input bool InpShowStats = true; input int InpStatFontSize = 9; input int InpStatCornerX = 8; input int InpStatCornerY = 8; input color InpStatColor = clrSilver; input group "══ Hiển thị chart ══" input bool InpDrawSwings = true; input bool InpDrawTrendLines = true; input bool InpDrawZones = true; input uchar InpZoneFillAlpha = 68; // 0=đặc, 255=trong suốt (nhỏ=hơn=mờ vừa) input uchar InpZoneActiveAlpha = 50; // vùng bias active (đậm hơn) input color InpClrBigHigh = clrDodgerBlue; input color InpClrBigLow = clrDeepSkyBlue; input color InpClrSmallHigh = clrOrange; input color InpClrSmallLow = clrGold; input color InpClrDiscount = C'0,110,45'; // xanh đậm (dễ thấy trên nền đen) input color InpClrPremium = C'150,45,55'; // đỏ burgundy input color InpClrBreaker = C'120,60,180'; input color InpClrFvg = C'40,140,90'; input color InpClrSweep = clrDeepSkyBlue; input color InpClrZoneLabel = clrWhite; input bool InpDrawMssSetup = true; input color InpClrUp = clrLime; input color InpClrDown = clrTomato; input color InpClrNeutral = clrSilver; input int InpFontSize = 9; input int InpPanelX = 12; input int InpPanelY = 24; input group "══ Debug ══" input bool InpDebug = false; //+------------------------------------------------------------------+ struct SwingPoint { double price; datetime time; int shift; }; struct TrendSnapshot { ENUM_ICT_TREND trend; SwingPoint h0, h1, l0, l1; bool hasH0, hasH1, hasL0, hasL1; }; struct BigWaveZone { bool valid; bool legExtended; // kéo thuận chiều Big (phá L0 khi giảm / phá H0 khi tăng) bool legCounterTrend; // phá ngược Big (phá H0 khi giảm / phá L0 khi tăng) double waveHigh; double waveLow; double equilibrium; double discountTop; double premiumBottom; }; struct IctMssSetup { bool valid; bool isBuy; double liqLevel; double sweepExtreme; int sweepShift; double brokenLevel; int brokenShift; double breakerTop; double breakerBottom; double entry; double sl; double tp; bool hasFvg; double fvgTop; double fvgBottom; int fvgShift; int mssShift; string summary; }; const string OBJ_CH_PFX = "MYICTC_"; const string STAT_PREFIX = "MYICTS_"; const string STAT_LINE1 = STAT_PREFIX + "L1"; const string STAT_LINE2 = STAT_PREFIX + "L2"; TrendSnapshot g_big; TrendSnapshot g_small; BigWaveZone g_zone; IctMssSetup g_setup; ENUM_ICT_STATE g_state = ICT_STATE_NO_TREND; ENUM_ICT_STATE g_statePrev = ICT_STATE_NO_TREND; bool g_biasBuy = false; datetime g_lastBar = 0; datetime g_lastTriggerBar = 0; datetime g_limitPlacedBar = 0; int g_hAtr = INVALID_HANDLE; long g_statWins = 0; long g_statLoss = 0; long g_statTotal = 0; CTrade g_trade; //+------------------------------------------------------------------+ long ActChart() { return ChartID(); } ENUM_TIMEFRAMES ChartTf() { return (ENUM_TIMEFRAMES)Period(); } //+------------------------------------------------------------------+ color WithAlpha(const color clr, const uchar alpha) { return (color)((long)clr | ((long)alpha << 24)); } //+------------------------------------------------------------------+ void Dbg(const string msg) { if(InpDebug) Print("[MyICT] ", msg); } //+------------------------------------------------------------------+ void DeleteObjectsByPrefix(const string prefix) { const long ch = ActChart(); for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--) { const string name = ObjectName(ch, i, 0, -1); if(StringFind(name, prefix) == 0) ObjectDelete(ch, name); } } //+------------------------------------------------------------------+ int ClampHour(const int h) { return MathMax(0, MathMin(23, h)); } //+------------------------------------------------------------------+ int SessionMinutesNow() { MqlDateTime tm; const datetime t = InpSessionUseUtc ? TimeGMT() : TimeCurrent(); TimeToStruct(t, tm); return tm.hour * 60 + tm.min; } //+------------------------------------------------------------------+ bool IsWithinSessionWindow(const int currentMinutes, const int startHour, const int endHour, const int avoidLastMinutes) { const int startMin = ClampHour(startHour) * 60; int endMin = ClampHour(endHour) * 60; if(endMin <= startMin) endMin += 24 * 60; int cur = currentMinutes; if(endMin > 24 * 60 && cur < startMin) cur += 24 * 60; if(cur < startMin || cur >= endMin) return false; const int avoid = MathMax(0, avoidLastMinutes); if(avoid > 0 && cur >= endMin - avoid) return false; return true; } //+------------------------------------------------------------------+ bool IsLondonSession() { return IsWithinSessionWindow(SessionMinutesNow(), InpLondonStartHour, InpLondonEndHour, InpSessionAvoidLastMin); } //+------------------------------------------------------------------+ bool IsNewYorkSession() { return IsWithinSessionWindow(SessionMinutesNow(), InpNewYorkStartHour, InpNewYorkEndHour, InpSessionAvoidLastMin); } //+------------------------------------------------------------------+ bool IsTradeSessionAllowed() { if(!InpUseSessionFilter) return true; return (IsLondonSession() || IsNewYorkSession()); } //+------------------------------------------------------------------+ string SessionStatusText() { if(!InpUseSessionFilter) return "Session: OFF (filter tắt)"; const string tz = InpSessionUseUtc ? "UTC" : "Server"; string s = StringFormat("Session [%s]: ", tz); if(IsLondonSession() && IsNewYorkSession()) s += "London + NY"; else if(IsLondonSession()) s += "London"; else if(IsNewYorkSession()) s += "New York"; else s += "CLOSED"; return s; } //+------------------------------------------------------------------+ double GetAtr(const string sym, const ENUM_TIMEFRAMES tf, const int shift) { if(g_hAtr == INVALID_HANDLE) return 0.0; double b[]; ArraySetAsSeries(b, true); if(CopyBuffer(g_hAtr, 0, shift, 1, b) < 1) return 0.0; return b[0]; } //+------------------------------------------------------------------+ double SweepTolerance(const string sym, const double atr) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); return MathMax(pt * InpSweepTolPoints, atr * InpSweepTolAtr); } //+------------------------------------------------------------------+ double BosTolerance(const string sym, const double atr) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); return MathMax(pt * 2.0, atr * 0.02); } //+------------------------------------------------------------------+ bool FindBearishFvg(const string sym, const ENUM_TIMEFRAMES tf, const int shiftFrom, const int shiftTo, double &fTop, double &fBot, int &fShift) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); const int lo = MathMin(shiftFrom, shiftTo); const int hi = MathMax(shiftFrom, shiftTo); for(int i = lo; i <= hi; i++) { if(i + 2 >= Bars(sym, tf)) continue; const double gapLo = iLow(sym, tf, i); const double gapHi = iHigh(sym, tf, i + 2); if(gapLo > gapHi + pt) { fTop = gapLo; fBot = gapHi; fShift = i; return true; } } return false; } //+------------------------------------------------------------------+ bool FindBullishFvg(const string sym, const ENUM_TIMEFRAMES tf, const int shiftFrom, const int shiftTo, double &fTop, double &fBot, int &fShift) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); const int lo = MathMin(shiftFrom, shiftTo); const int hi = MathMax(shiftFrom, shiftTo); for(int i = lo; i <= hi; i++) { if(i + 2 >= Bars(sym, tf)) continue; const double gapHi = iHigh(sym, tf, i); const double gapLo = iLow(sym, tf, i + 2); if(gapLo > gapHi + pt) { fTop = gapLo; fBot = gapHi; fShift = i; return true; } } return false; } //+------------------------------------------------------------------+ bool WasLiquiditySweepHigh(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const double liq, const double sweepTol, double &sweepHigh) { const double h = iHigh(sym, tf, shift); const double c = iClose(sym, tf, shift); if(h <= liq + sweepTol || c >= liq) return false; sweepHigh = h; return true; } //+------------------------------------------------------------------+ bool WasLiquiditySweepLow(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const double liq, const double sweepTol, double &sweepLow) { const double l = iLow(sym, tf, shift); const double c = iClose(sym, tf, shift); if(l >= liq - sweepTol || c <= liq) return false; sweepLow = l; return true; } //+------------------------------------------------------------------+ void BreakerFromShift(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const bool isBuy, double &top, double &bottom) { const double o = iOpen(sym, tf, shift); const double c = iClose(sym, tf, shift); const double h = iHigh(sym, tf, shift); const double l = iLow(sym, tf, shift); top = MathMax(o, c); bottom = MathMin(o, c); if(top <= bottom + _Point) { top = h; bottom = l; } } //+------------------------------------------------------------------+ bool DetectBearishMssSetup(const string sym, const ENUM_TIMEFRAMES tf, IctMssSetup &out) { ZeroMemory(out); if(!g_small.hasL0 || !g_small.hasH0 || !g_zone.valid) return false; const double atr = GetAtr(sym, tf, 1); if(atr <= 0.0) return false; const double sweepTol = SweepTolerance(sym, atr); const double bosTol = BosTolerance(sym, atr); const double liq = g_small.hasH1 ? MathMax(g_small.h0.price, g_small.h1.price) : g_small.h0.price; const double broken = g_small.l0.price; const int brShift = MathMax(1, g_small.l0.shift); const int maxMss = MathMax(2, InpMssMaxRecent); for(int mssSh = 2; mssSh <= maxMss; mssSh++) { const double cMss = iClose(sym, tf, mssSh); if(cMss >= broken - bosTol) continue; const double body = MathAbs(iClose(sym, tf, mssSh) - iOpen(sym, tf, mssSh)); if(body < atr * InpMssDispAtrMult) continue; int sweepSh = -1; double sweepHi = 0.0; for(int sw = mssSh + 1; sw <= InpMssLookback; sw++) { double sh = 0.0; if(WasLiquiditySweepHigh(sym, tf, sw, liq, sweepTol, sh)) { sweepSh = sw; sweepHi = sh; break; } } if(sweepSh < 0) continue; double fTop = 0.0, fBot = 0.0; int fSh = -1; const bool hasFvg = FindBearishFvg(sym, tf, mssSh, sweepSh + 2, fTop, fBot, fSh); if(InpRequireFvg && !hasFvg) continue; double brTop = 0.0, brBot = 0.0; BreakerFromShift(sym, tf, brShift, false, brTop, brBot); const double entry = brTop + atr * InpBreakerOffsetAtr; const double slBuf = atr * InpSlAtrMult; const double tpBuf = atr * InpTpAtrBuffer; const double sl = sweepHi + slBuf; const double tp = g_zone.waveLow + tpBuf; out.valid = true; out.isBuy = false; out.liqLevel = liq; out.sweepExtreme = sweepHi; out.sweepShift = sweepSh; out.brokenLevel = broken; out.brokenShift = brShift; out.breakerTop = brTop; out.breakerBottom = brBot; out.entry = entry; out.sl = sl; out.tp = tp; out.hasFvg = hasFvg; out.fvgTop = fTop; out.fvgBottom = fBot; out.fvgShift = fSh; out.mssShift = mssSh; out.summary = StringFormat("SELL MSS | sweep H%.5f → BOS L%.5f | breaker %.5f", sweepHi, broken, entry); return true; } return false; } //+------------------------------------------------------------------+ bool DetectBullishMssSetup(const string sym, const ENUM_TIMEFRAMES tf, IctMssSetup &out) { ZeroMemory(out); if(!g_small.hasH0 || !g_small.hasL0 || !g_zone.valid) return false; const double atr = GetAtr(sym, tf, 1); if(atr <= 0.0) return false; const double sweepTol = SweepTolerance(sym, atr); const double bosTol = BosTolerance(sym, atr); const double liq = g_small.hasL1 ? MathMin(g_small.l0.price, g_small.l1.price) : g_small.l0.price; const double broken = g_small.h0.price; const int brShift = MathMax(1, g_small.h0.shift); const int maxMss = MathMax(2, InpMssMaxRecent); for(int mssSh = 2; mssSh <= maxMss; mssSh++) { const double cMss = iClose(sym, tf, mssSh); if(cMss <= broken + bosTol) continue; const double body = MathAbs(iClose(sym, tf, mssSh) - iOpen(sym, tf, mssSh)); if(body < atr * InpMssDispAtrMult) continue; int sweepSh = -1; double sweepLo = 0.0; for(int sw = mssSh + 1; sw <= InpMssLookback; sw++) { double slv = 0.0; if(WasLiquiditySweepLow(sym, tf, sw, liq, sweepTol, slv)) { sweepSh = sw; sweepLo = slv; break; } } if(sweepSh < 0) continue; double fTop = 0.0, fBot = 0.0; int fSh = -1; const bool hasFvg = FindBullishFvg(sym, tf, mssSh, sweepSh + 2, fTop, fBot, fSh); if(InpRequireFvg && !hasFvg) continue; double brTop = 0.0, brBot = 0.0; BreakerFromShift(sym, tf, brShift, true, brTop, brBot); const double entry = brBot - atr * InpBreakerOffsetAtr; const double slBuf = atr * InpSlAtrMult; const double tpBuf = atr * InpTpAtrBuffer; const double sl = sweepLo - slBuf; const double tp = g_zone.waveHigh - tpBuf; out.valid = true; out.isBuy = true; out.liqLevel = liq; out.sweepExtreme = sweepLo; out.sweepShift = sweepSh; out.brokenLevel = broken; out.brokenShift = brShift; out.breakerTop = brTop; out.breakerBottom = brBot; out.entry = entry; out.sl = sl; out.tp = tp; out.hasFvg = hasFvg; out.fvgTop = fTop; out.fvgBottom = fBot; out.fvgShift = fSh; out.mssShift = mssSh; out.summary = StringFormat("BUY MSS | sweep L%.5f → BOS H%.5f | breaker %.5f", sweepLo, broken, entry); return true; } return false; } //+------------------------------------------------------------------+ bool DetectMssSetup(const string sym, const ENUM_TIMEFRAMES tf, IctMssSetup &out) { if(g_biasBuy) return DetectBullishMssSetup(sym, tf, out); return DetectBearishMssSetup(sym, tf, out); } //+------------------------------------------------------------------+ bool MssSetupReady() { return g_setup.valid; } //+------------------------------------------------------------------+ double NormalizeVolume(const string sym, double v) { const double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP); const double vmin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN); const double vmax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX); if(step <= 0.0) return 0.0; v = MathFloor(v / step) * step; if(v < vmin - 1e-12) return 0.0; if(v > vmax) v = vmax; return NormalizeDouble(v, 8); } //+------------------------------------------------------------------+ double VolumeForRisk(const string sym, const bool isBuy, const double entry, const double sl) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); if(MathAbs(entry - sl) < pt) return 0.0; const double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0); double profit = 0.0; if(!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, sym, 1.0, entry, sl, profit)) return 0.0; const double lossPerLot = MathAbs(profit); if(lossPerLot < DBL_EPSILON) return 0.0; return riskMoney / lossPerLot; } //+------------------------------------------------------------------+ double CalcRiskReward(const bool isBuy, const double entry, const double sl, const double tp) { const double risk = isBuy ? (entry - sl) : (sl - entry); const double reward = isBuy ? (tp - entry) : (entry - tp); if(risk <= _Point || reward <= 0.0) return 0.0; return reward / risk; } //+------------------------------------------------------------------+ bool IsRiskRewardOk(const bool isBuy, const double entry, const double sl, const double tp, double &outRR) { outRR = CalcRiskReward(isBuy, entry, sl, tp); if(!InpUseMinRR) return true; const double minRR = MathMax(0.1, InpMinRR); return (outRR >= minRR - 1e-8); } //+------------------------------------------------------------------+ bool StopsValid(const string sym, const bool isBuy, const double entry, const double sl, const double tp) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); const int stops = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL); const int freeze = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL); const double md = MathMax(stops, freeze) * pt; if(md <= 0.0) return true; if(isBuy) { if(entry - sl < md - pt) return false; if(tp - entry < md - pt) return false; } else { if(sl - entry < md - pt) return false; if(entry - tp < md - pt) return false; } return true; } //+------------------------------------------------------------------+ void CancelMyPendingLimits(const string sym) { for(int i = OrdersTotal() - 1; i >= 0; i--) { const ulong ticket = OrderGetTicket(i); if(ticket == 0 || !OrderSelect(ticket)) continue; if(OrderGetString(ORDER_SYMBOL) != sym) continue; if((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic) continue; const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT) g_trade.OrderDelete(ticket); } } //+------------------------------------------------------------------+ void ManagePendingLimits(const string sym, const ENUM_TIMEFRAMES tf) { if(!HasMyPendingLimit(sym)) return; if(InpUseSessionFilter && !IsTradeSessionAllowed()) { CancelMyPendingLimits(sym); if(g_state == ICT_STATE_LIMIT_ORDERED) g_state = ICT_STATE_WAITING_TRIGGER; Dbg("Hủy limit — ngoài phiên London/NY"); return; } const datetime t0 = iTime(sym, tf, 0); if(t0 == 0 || g_limitPlacedBar == 0) return; const int barsWait = iBarShift(sym, tf, g_limitPlacedBar, true); if(barsWait >= InpLimitExpireBars) { CancelMyPendingLimits(sym); g_state = ICT_STATE_WAITING_TRIGGER; Dbg("Hủy limit — hết hạn " + IntegerToString(InpLimitExpireBars) + " nến"); return; } if(!g_setup.valid) return; const double live = g_setup.isBuy ? MathMin(iLow(sym, tf, 0), iLow(sym, tf, 1)) : MathMax(iHigh(sym, tf, 0), iHigh(sym, tf, 1)); if(!g_setup.isBuy && live > g_setup.sweepExtreme + SweepTolerance(sym, GetAtr(sym, tf, 1))) { CancelMyPendingLimits(sym); g_state = ICT_STATE_WAITING_TRIGGER; Dbg("Hủy SELL limit — phá lại trên sweep"); } else if(g_setup.isBuy && live < g_setup.sweepExtreme - SweepTolerance(sym, GetAtr(sym, tf, 1))) { CancelMyPendingLimits(sym); g_state = ICT_STATE_WAITING_TRIGGER; Dbg("Hủy BUY limit — phá lại dưới sweep"); } } //+------------------------------------------------------------------+ bool TryPlaceMssLimit(const string sym, const ENUM_TIMEFRAMES tf) { if(!InpTradeEnabled) return false; if(!IsTradeSessionAllowed()) { Dbg("MSS skip: ngoài phiên London/NY"); return false; } if(g_state != ICT_STATE_WAITING_TRIGGER) return false; if(InpOnePosition && HasMyPosition(sym)) return false; if(HasMyPendingLimit(sym)) return false; const datetime tBar = iTime(sym, tf, 1); if(tBar == 0 || tBar == g_lastTriggerBar) return false; const double refClose = iClose(sym, tf, 1); if(g_biasBuy) { if(!PriceInDiscount(refClose)) return false; } else { if(!PriceInPremium(refClose)) return false; } IctMssSetup setup; if(!DetectMssSetup(sym, tf, setup)) { g_setup.valid = false; return false; } g_setup = setup; const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); const double entry = NormalizeDouble(setup.entry, dig); const double sl = NormalizeDouble(setup.sl, dig); const double tp = NormalizeDouble(setup.tp, dig); MqlTick tk; if(!SymbolInfoTick(sym, tk)) return false; if(setup.isBuy) { if(tp <= entry || sl >= entry || entry >= tk.ask - _Point) { Dbg("MSS Buy skip: entry/TP/SL"); return false; } } else { if(tp >= entry || sl <= entry || entry <= tk.bid + _Point) { Dbg("MSS Sell skip: entry/TP/SL"); return false; } } if(!StopsValid(sym, setup.isBuy, entry, sl, tp)) { Dbg("MSS skip: STOPS_LEVEL"); return false; } double rr = 0.0; if(!IsRiskRewardOk(setup.isBuy, entry, sl, tp, rr)) { Dbg(StringFormat("MSS skip: R:R %.2f < 1:%.2f", rr, InpMinRR)); return false; } double vol = NormalizeVolume(sym, VolumeForRisk(sym, setup.isBuy, entry, sl)); if(vol <= 0.0) { Dbg("MSS skip: volume=0"); return false; } g_trade.SetExpertMagicNumber(InpMagic); g_trade.SetDeviationInPoints(InpSlippagePoints); SetTradeFilling(); const datetime exp = tBar + (datetime)(PeriodSeconds(tf) * MathMax(1, InpLimitExpireBars)); const string cmt = setup.isBuy ? "MyICT_BUY_BB" : "MyICT_SELL_BB"; const bool ok = setup.isBuy ? g_trade.BuyLimit(vol, entry, sym, sl, tp, ORDER_TIME_SPECIFIED, exp, cmt) : g_trade.SellLimit(vol, entry, sym, sl, tp, ORDER_TIME_SPECIFIED, exp, cmt); if(!ok) { Print("[MyICT] MSS limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment()); return false; } g_lastTriggerBar = tBar; g_limitPlacedBar = tBar; g_state = ICT_STATE_LIMIT_ORDERED; PrintFormat("[MyICT] %s | vol=%.2f entry=%.5f SL=%.5f TP=%.5f R:R=1:%.2f | %s", cmt, vol, entry, sl, tp, rr, setup.summary); return true; } //+------------------------------------------------------------------+ string TrendText(const ENUM_ICT_TREND t) { switch(t) { case ICT_TREND_UP: return "UP (HH+HL)"; case ICT_TREND_DOWN: return "DOWN (LH+LL)"; case ICT_TREND_NEUTRAL: return "NEUTRAL"; default: return "—"; } } //+------------------------------------------------------------------+ string StateText(const ENUM_ICT_STATE s) { switch(s) { case ICT_STATE_NO_TREND: return "NO_TREND"; case ICT_STATE_PULLBACK: return "PULLBACK"; case ICT_STATE_WAITING_TRIGGER: return "WAITING_TRIGGER"; case ICT_STATE_LIMIT_ORDERED: return "LIMIT_ORDERED"; case ICT_STATE_ON_TRADE: return "ON_TRADE"; case ICT_STATE_NONE: return "NONE"; default: return "?"; } } //+------------------------------------------------------------------+ color StateColor(const ENUM_ICT_STATE s) { switch(s) { case ICT_STATE_WAITING_TRIGGER: return clrGold; case ICT_STATE_LIMIT_ORDERED: return clrOrange; case ICT_STATE_ON_TRADE: return clrLime; case ICT_STATE_PULLBACK: return clrAqua; case ICT_STATE_NONE: return clrGray; default: return InpClrNeutral; } } //+------------------------------------------------------------------+ bool IsSwingHigh(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const int swingRange) { const int str = MathMax(1, swingRange); 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 IsSwingLow(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const int swingRange) { const int str = MathMax(1, swingRange); 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 SortSwingsByTime(SwingPoint &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 SwingPoint t = pts[i]; pts[i] = pts[j]; pts[j] = t; } } //+------------------------------------------------------------------+ void CollectSwings(const string sym, const ENUM_TIMEFRAMES tf, const int swingRange, SwingPoint &highs[], SwingPoint &lows[]) { ArrayResize(highs, 0); ArrayResize(lows, 0); const int str = MathMax(1, swingRange); const int bars = Bars(sym, tf); const int last = MathMin(InpSwingLookback, bars - str - 1); if(last < str + 5) return; int nH = 0, nL = 0; for(int sh = last; sh >= 1; sh--) { if(IsSwingHigh(sym, tf, sh, swingRange)) { ArrayResize(highs, nH + 1); highs[nH].price = iHigh(sym, tf, sh); highs[nH].time = iTime(sym, tf, sh); highs[nH].shift = sh; nH++; } if(IsSwingLow(sym, tf, sh, swingRange)) { ArrayResize(lows, nL + 1); lows[nL].price = iLow(sym, tf, sh); lows[nL].time = iTime(sym, tf, sh); lows[nL].shift = sh; nL++; } } SortSwingsByTime(highs, nH); SortSwingsByTime(lows, nL); } //+------------------------------------------------------------------+ void PickLastTwo(const SwingPoint &pts[], const int count, SwingPoint &s0, SwingPoint &s1, bool &has0, bool &has1) { has0 = has1 = false; if(count < 1) return; s0 = pts[count - 1]; has0 = true; if(count < 2) return; s1 = pts[count - 2]; has1 = true; } //+------------------------------------------------------------------+ ENUM_ICT_TREND ClassifyTrend(const bool hasH0, const bool hasH1, const bool hasL0, const bool hasL1, const SwingPoint &h0, const SwingPoint &h1, const SwingPoint &l0, const SwingPoint &l1) { if(!hasH0 || !hasH1 || !hasL0 || !hasL1) return ICT_TREND_UNKNOWN; if(h0.price > h1.price && l0.price > l1.price) return ICT_TREND_UP; if(h0.price < h1.price && l0.price < l1.price) return ICT_TREND_DOWN; return ICT_TREND_NEUTRAL; } //+------------------------------------------------------------------+ bool AnalyzeLayer(const string sym, const ENUM_TIMEFRAMES tf, const int swingRange, TrendSnapshot &out) { ZeroMemory(out); out.trend = ICT_TREND_UNKNOWN; SwingPoint highs[], lows[]; CollectSwings(sym, tf, swingRange, highs, lows); PickLastTwo(highs, ArraySize(highs), out.h0, out.h1, out.hasH0, out.hasH1); PickLastTwo(lows, ArraySize(lows), out.l0, out.l1, out.hasL0, out.hasL1); out.trend = ClassifyTrend(out.hasH0, out.hasH1, out.hasL0, out.hasL1, out.h0, out.h1, out.l0, out.l1); return true; } //+------------------------------------------------------------------+ bool BigTrendIsClear() { return (g_big.trend == ICT_TREND_UP || g_big.trend == ICT_TREND_DOWN); } //+------------------------------------------------------------------+ bool BigIsBearish() { if(g_big.trend == ICT_TREND_DOWN) return true; return (g_big.hasH0 && g_big.hasH1 && g_big.h0.price < g_big.h1.price); } //+------------------------------------------------------------------+ bool BigIsBullish() { if(g_big.trend == ICT_TREND_UP) return true; return (g_big.hasH0 && g_big.hasH1 && g_big.h0.price > g_big.h1.price); } //+------------------------------------------------------------------+ bool IsPullbackStructure() { if(!BigTrendIsClear()) return false; if(g_big.trend == ICT_TREND_UP && g_small.trend == ICT_TREND_DOWN) { g_biasBuy = true; return true; } if(g_big.trend == ICT_TREND_DOWN && g_small.trend == ICT_TREND_UP) { g_biasBuy = false; return true; } return false; } //+------------------------------------------------------------------+ double ExtremeLowBetween(const string sym, const ENUM_TIMEFRAMES tf, const int shiftFrom, const int shiftTo) { double mn = DBL_MAX; const int lo = MathMin(shiftFrom, shiftTo); const int hi = MathMax(shiftFrom, shiftTo); for(int sh = lo; sh <= hi; sh++) { const double l = iLow(sym, tf, sh); if(l < mn) mn = l; } return (mn == DBL_MAX ? 0.0 : mn); } //+------------------------------------------------------------------+ double ExtremeHighBetween(const string sym, const ENUM_TIMEFRAMES tf, const int shiftFrom, const int shiftTo) { double mx = 0.0; const int lo = MathMin(shiftFrom, shiftTo); const int hi = MathMax(shiftFrom, shiftTo); for(int sh = lo; sh <= hi; sh++) { const double h = iHigh(sym, tf, sh); if(h > mx) mx = h; } return mx; } //+------------------------------------------------------------------+ void CalcBigWaveZone(const string sym, const ENUM_TIMEFRAMES tf) { ZeroMemory(g_zone); if(!g_big.hasH0 || !g_big.hasL0) return; g_zone.legExtended = false; g_zone.legCounterTrend = false; g_zone.waveHigh = g_big.h0.price; g_zone.waveLow = g_big.l0.price; const double atr = GetAtr(sym, tf, 1); const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); const double tol = MathMax(pt * 5.0, (atr > 0.0 ? atr * 0.05 : pt * 10.0)); const int shH0 = MathMax(0, g_big.h0.shift); const int shL0 = MathMax(0, g_big.l0.shift); const int shNow = 0; const double liveLow = MathMin(iLow(sym, tf, 0), iLow(sym, tf, 1)); const double liveHigh = MathMax(iHigh(sym, tf, 0), iHigh(sym, tf, 1)); // Big GIẢM — thuận: phá dưới L0 → H0 → đáy mới if(BigIsBearish() && liveLow < g_big.l0.price - tol) { double extLow = ExtremeLowBetween(sym, tf, shH0, shNow); if(extLow <= 0.0) extLow = liveLow; else extLow = MathMin(extLow, liveLow); g_zone.waveHigh = g_big.h0.price; g_zone.waveLow = extLow; g_zone.legExtended = (extLow < g_big.l0.price - tol * 0.5); } // Big GIẢM — ngược: phá trên H0 → L0 → đỉnh cao nhất (chưa có Big H0 mới) else if(BigIsBearish() && liveHigh > g_big.h0.price + tol) { double extHigh = ExtremeHighBetween(sym, tf, shL0, shNow); if(extHigh <= 0.0) extHigh = liveHigh; else extHigh = MathMax(extHigh, liveHigh); g_zone.waveLow = g_big.l0.price; g_zone.waveHigh = extHigh; g_zone.legCounterTrend = (extHigh > g_big.h0.price + tol * 0.5); } // Big TĂNG — thuận: phá trên H0 → L0 → đỉnh mới else if(BigIsBullish() && liveHigh > g_big.h0.price + tol) { double extHigh = ExtremeHighBetween(sym, tf, shL0, shNow); if(extHigh <= 0.0) extHigh = liveHigh; else extHigh = MathMax(extHigh, liveHigh); g_zone.waveLow = g_big.l0.price; g_zone.waveHigh = extHigh; g_zone.legExtended = (extHigh > g_big.h0.price + tol * 0.5); } // Big TĂNG — ngược: phá dưới L0 → H0 → đáy thấp nhất else if(BigIsBullish() && liveLow < g_big.l0.price - tol) { double extLow = ExtremeLowBetween(sym, tf, shH0, shNow); if(extLow <= 0.0) extLow = liveLow; else extLow = MathMin(extLow, liveLow); g_zone.waveHigh = g_big.h0.price; g_zone.waveLow = extLow; g_zone.legCounterTrend = (extLow < g_big.l0.price - tol * 0.5); } if(g_zone.waveHigh < g_zone.waveLow) { const double t = g_zone.waveHigh; g_zone.waveHigh = g_zone.waveLow; g_zone.waveLow = t; } const double range = g_zone.waveHigh - g_zone.waveLow; if(range <= _Point * 2) return; const double pct = MathMax(1.0, MathMin(99.0, InpZoneEqPct)) / 100.0; g_zone.equilibrium = g_zone.waveLow + range * pct; g_zone.discountTop = g_zone.equilibrium; g_zone.premiumBottom = g_zone.equilibrium; g_zone.valid = true; if((g_zone.legExtended || g_zone.legCounterTrend) && InpDebug) Dbg(StringFormat("Zone %s | H=%.5f L=%.5f Eq=%.5f", g_zone.legCounterTrend ? "phá cấu trúc Big" : "kéo thuận chiều", g_zone.waveHigh, g_zone.waveLow, g_zone.equilibrium)); } //+------------------------------------------------------------------+ bool PriceInDiscount(const double price) { if(!g_zone.valid) return false; const double tol = InpZoneTolPoints * _Point; return (price <= g_zone.discountTop + tol); } //+------------------------------------------------------------------+ bool PriceInPremium(const double price) { if(!g_zone.valid) return false; const double tol = InpZoneTolPoints * _Point; return (price >= g_zone.premiumBottom - tol); } //+------------------------------------------------------------------+ bool HasMyPosition(const string sym) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; if(PositionGetString(POSITION_SYMBOL) != sym) continue; if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic) continue; return true; } return false; } //+------------------------------------------------------------------+ bool HasMyPendingLimit(const string sym) { for(int i = OrdersTotal() - 1; i >= 0; i--) { const ulong ticket = OrderGetTicket(i); if(ticket == 0 || !OrderSelect(ticket)) continue; if(OrderGetString(ORDER_SYMBOL) != sym) continue; if((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic) continue; const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT) return true; } return false; } //+------------------------------------------------------------------+ void SetTradeFilling() { const long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); if((fm & SYMBOL_FILLING_IOC) != 0) g_trade.SetTypeFilling(ORDER_FILLING_IOC); else if((fm & SYMBOL_FILLING_FOK) != 0) g_trade.SetTypeFilling(ORDER_FILLING_FOK); else g_trade.SetTypeFilling(ORDER_FILLING_RETURN); } //+------------------------------------------------------------------+ ENUM_ICT_STATE ComputeStructureState(const string sym, const ENUM_TIMEFRAMES tf) { if(!BigTrendIsClear()) return ICT_STATE_NO_TREND; if(!IsPullbackStructure()) return ICT_STATE_NO_TREND; CalcBigWaveZone(sym, tf); const double refPrice = g_biasBuy ? MathMin(iClose(sym, tf, 0), iClose(sym, tf, 1)) : MathMax(iClose(sym, tf, 0), iClose(sym, tf, 1)); if(g_biasBuy) { if(PriceInDiscount(refPrice)) return ICT_STATE_WAITING_TRIGGER; return ICT_STATE_PULLBACK; } if(PriceInPremium(refPrice)) return ICT_STATE_WAITING_TRIGGER; return ICT_STATE_PULLBACK; } //+------------------------------------------------------------------+ void UpdateStateMachine(const string sym, const ENUM_TIMEFRAMES tf) { g_statePrev = g_state; if(HasMyPosition(sym)) { g_state = ICT_STATE_ON_TRADE; return; } ManagePendingLimits(sym, tf); if(HasMyPendingLimit(sym)) { g_state = ICT_STATE_LIMIT_ORDERED; return; } if(g_statePrev == ICT_STATE_ON_TRADE || g_statePrev == ICT_STATE_LIMIT_ORDERED) { g_state = ICT_STATE_NONE; Dbg("NONE — lệnh đã đóng (SL/TP hoặc hủy)"); return; } if(g_statePrev == ICT_STATE_NONE) { // một nến NONE rồi quay lại đánh giá cấu trúc } g_state = ComputeStructureState(sym, tf); if(g_state == ICT_STATE_WAITING_TRIGGER) TryPlaceMssLimit(sym, tf); if(g_state != g_statePrev && InpDebug) Dbg(StringFormat("State %s → %s", StateText(g_statePrev), StateText(g_state))); } //+------------------------------------------------------------------+ void DrawArrowTag(const string tag, const datetime t, const double price, const bool isHigh, const string label, const color clr, const int width) { const long ch = ActChart(); const string arr = OBJ_CH_PFX + tag + "_AR"; const string txt = OBJ_CH_PFX + tag + "_TX"; if(ObjectFind(ch, arr) < 0) ObjectCreate(ch, arr, OBJ_ARROW, 0, t, price); ObjectMove(ch, arr, 0, t, price); ObjectSetInteger(ch, arr, OBJPROP_ARROWCODE, isHigh ? 217 : 218); ObjectSetInteger(ch, arr, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP); ObjectSetInteger(ch, arr, OBJPROP_COLOR, clr); ObjectSetInteger(ch, arr, OBJPROP_WIDTH, width); ObjectSetInteger(ch, arr, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, arr, OBJPROP_HIDDEN, true); const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); const double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); const double yOff = pt * (isHigh ? (width > 1 ? 150 : 90) : (width > 1 ? -150 : -90)); if(ObjectFind(ch, txt) < 0) ObjectCreate(ch, txt, OBJ_TEXT, 0, t, NormalizeDouble(price + yOff, dig)); ObjectMove(ch, txt, 0, t, NormalizeDouble(price + yOff, dig)); ObjectSetString(ch, txt, OBJPROP_TEXT, label); ObjectSetInteger(ch, txt, OBJPROP_COLOR, clr); ObjectSetInteger(ch, txt, OBJPROP_FONTSIZE, InpFontSize); ObjectSetString(ch, txt, OBJPROP_FONT, "Arial Bold"); ObjectSetInteger(ch, txt, OBJPROP_ANCHOR, isHigh ? ANCHOR_LOWER : ANCHOR_UPPER); ObjectSetInteger(ch, txt, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, txt, OBJPROP_HIDDEN, true); } //+------------------------------------------------------------------+ void DrawSegLine(const string name, const datetime t1, const double p1, const datetime t2, const double p2, const color clr, const int width) { const long ch = ActChart(); const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); if(ObjectFind(ch, name) < 0) ObjectCreate(ch, name, OBJ_TREND, 0, t1, p1, t2, p2); ObjectMove(ch, name, 0, t1, NormalizeDouble(p1, dig)); ObjectMove(ch, name, 1, t2, NormalizeDouble(p2, dig)); ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); ObjectSetInteger(ch, name, OBJPROP_STYLE, width > 1 ? STYLE_SOLID : STYLE_DOT); ObjectSetInteger(ch, name, OBJPROP_WIDTH, width); ObjectSetInteger(ch, name, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true); } //+------------------------------------------------------------------+ void DrawZoneRect(const string name, const datetime t1, const datetime t2, const double pTop, const double pBot, const color clr, const uchar fillAlpha, const string tip) { const long ch = ActChart(); const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); const double y1 = NormalizeDouble(MathMax(pTop, pBot), dig); const double y2 = NormalizeDouble(MathMin(pTop, pBot), dig); if(ObjectFind(ch, name) < 0) ObjectCreate(ch, name, OBJ_RECTANGLE, 0, t1, y1, t2, y2); ObjectMove(ch, name, 0, t1, y1); ObjectMove(ch, name, 1, t2, y2); ObjectSetInteger(ch, name, OBJPROP_COLOR, WithAlpha(clr, fillAlpha)); ObjectSetInteger(ch, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(ch, name, OBJPROP_WIDTH, 1); ObjectSetInteger(ch, name, OBJPROP_FILL, true); ObjectSetInteger(ch, name, OBJPROP_BACK, true); ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true); ObjectSetString(ch, name, OBJPROP_TOOLTIP, tip); } //+------------------------------------------------------------------+ void DrawZoneTag(const string name, const datetime t, const double price, const string text, const color clr) { const long ch = ActChart(); const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); if(ObjectFind(ch, name) < 0) ObjectCreate(ch, name, OBJ_TEXT, 0, t, NormalizeDouble(price, dig)); ObjectMove(ch, name, 0, t, NormalizeDouble(price, dig)); ObjectSetString(ch, name, OBJPROP_TEXT, text); ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpFontSize - 1)); ObjectSetString(ch, name, OBJPROP_FONT, "Arial"); ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT); ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true); ObjectSetInteger(ch, name, OBJPROP_BACK, false); } //+------------------------------------------------------------------+ void DrawLayer(const string tag, const TrendSnapshot &snap, const color clrHi, const color clrLo, const int lineWidth) { if(!InpDrawSwings) return; if(snap.hasH0) DrawArrowTag(tag + "_H0", snap.h0.time, snap.h0.price, true, tag + " H0", clrHi, lineWidth); if(snap.hasH1) DrawArrowTag(tag + "_H1", snap.h1.time, snap.h1.price, true, tag + " H1", clrHi, lineWidth); if(snap.hasL0) DrawArrowTag(tag + "_L0", snap.l0.time, snap.l0.price, false, tag + " L0", clrLo, lineWidth); if(snap.hasL1) DrawArrowTag(tag + "_L1", snap.l1.time, snap.l1.price, false, tag + " L1", clrLo, lineWidth); if(!InpDrawTrendLines) return; if(snap.hasH0 && snap.hasH1) DrawSegLine(OBJ_CH_PFX + tag + "_LNH", snap.h1.time, snap.h1.price, snap.h0.time, snap.h0.price, clrHi, lineWidth); if(snap.hasL0 && snap.hasL1) DrawSegLine(OBJ_CH_PFX + tag + "_LNL", snap.l1.time, snap.l1.price, snap.l0.time, snap.l0.price, clrLo, lineWidth); } //+------------------------------------------------------------------+ void DrawZones(const string sym, const ENUM_TIMEFRAMES tf) { if(!InpDrawZones || !g_zone.valid) return; const datetime t2 = iTime(sym, tf, 0) + (datetime)PeriodSeconds(tf); datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1)); if(g_zone.legCounterTrend) { if(BigIsBearish() && g_big.hasL0) t1 = g_big.l0.time; else if(BigIsBullish() && g_big.hasH0) t1 = g_big.h0.time; } else if(g_zone.legExtended) { if(BigIsBearish() && g_big.hasH0) t1 = g_big.h0.time; else if(BigIsBullish() && g_big.hasL0) t1 = g_big.l0.time; } if(t1 == 0 || t2 == 0) return; const bool activePullback = (g_state == ICT_STATE_PULLBACK || g_state == ICT_STATE_WAITING_TRIGGER); const uchar discAlpha = (activePullback && g_biasBuy) ? InpZoneActiveAlpha : InpZoneFillAlpha; const uchar premAlpha = (activePullback && !g_biasBuy) ? InpZoneActiveAlpha : InpZoneFillAlpha; string extTip = ""; if(g_zone.legCounterTrend) extTip = " (phá cấu trúc Big — L0↔đỉnh/đáy mới)"; else if(g_zone.legExtended) extTip = " (kéo thuận chiều Big)"; DrawZoneRect(OBJ_CH_PFX + "Z_DISC", t1, t2, g_zone.discountTop, g_zone.waveLow, InpClrDiscount, discAlpha, "Discount — nửa dưới sóng Big" + extTip); DrawZoneRect(OBJ_CH_PFX + "Z_PREM", t1, t2, g_zone.waveHigh, g_zone.premiumBottom, InpClrPremium, premAlpha, "Premium — nửa trên sóng Big" + extTip); DrawSegLine(OBJ_CH_PFX + "Z_EQ", t1, g_zone.equilibrium, t2, g_zone.equilibrium, clrGold, 1); ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_WIDTH, 2); const double midDisc = (g_zone.waveLow + g_zone.discountTop) * 0.5; const double midPrem = (g_zone.premiumBottom + g_zone.waveHigh) * 0.5; DrawZoneTag(OBJ_CH_PFX + "LBL_DISC", t2, midDisc, " DISCOUNT ", InpClrZoneLabel); DrawZoneTag(OBJ_CH_PFX + "LBL_PREM", t2, midPrem, " PREMIUM ", InpClrZoneLabel); } //+------------------------------------------------------------------+ void DrawMssSetup(const string sym, const ENUM_TIMEFRAMES tf) { if(!InpDrawMssSetup || !g_setup.valid) return; const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); const datetime tBr = iTime(sym, tf, g_setup.brokenShift); const datetime tSw = iTime(sym, tf, g_setup.sweepShift); const datetime tEnd = iTime(sym, tf, 0) + (datetime)PeriodSeconds(tf); DrawSegLine(OBJ_CH_PFX + "MSS_LIQ", tSw, g_setup.liqLevel, tEnd, g_setup.liqLevel, InpClrSweep, 1); ObjectSetInteger(ActChart(), OBJ_CH_PFX + "MSS_LIQ", OBJPROP_STYLE, STYLE_DASH); DrawZoneRect(OBJ_CH_PFX + "MSS_BRK", tBr, tEnd, g_setup.breakerTop, g_setup.breakerBottom, InpClrBreaker, 90, "Breaker block — limit entry"); DrawSegLine(OBJ_CH_PFX + "MSS_ENT", tBr, g_setup.entry, tEnd, g_setup.entry, clrWhite, 2); if(g_setup.hasFvg && g_setup.fvgShift > 0) { const datetime tF = iTime(sym, tf, g_setup.fvgShift); DrawZoneRect(OBJ_CH_PFX + "MSS_FVG", tF, tEnd, g_setup.fvgTop, g_setup.fvgBottom, InpClrFvg, 110, "FVG"); } DrawZoneTag(OBJ_CH_PFX + "MSS_LBL", tEnd, g_setup.entry, g_setup.isBuy ? " BUY LMT " : " SELL LMT ", InpClrBreaker); } //+------------------------------------------------------------------+ string LayerBlock(const string title, const int swingRange, const TrendSnapshot &snap) { string s = StringFormat("%s (sw %d): %s", title, swingRange, TrendText(snap.trend)); if(snap.hasH0 && snap.hasH1 && snap.hasL0 && snap.hasL1) s += StringFormat("\n H0=%.2f H1=%.2f L0=%.2f L1=%.2f", snap.h0.price, snap.h1.price, snap.l0.price, snap.l1.price); return s; } //+------------------------------------------------------------------+ string StateBlock() { string s = StringFormat("STATE: %s", StateText(g_state)); if(g_state == ICT_STATE_PULLBACK || g_state == ICT_STATE_WAITING_TRIGGER) s += StringFormat(" | Bias %s", g_biasBuy ? "BUY" : "SELL"); if(g_zone.valid) { s += StringFormat("\n Zone H=%.2f L=%.2f Eq=%.2f (%.0f%%)", g_zone.waveHigh, g_zone.waveLow, g_zone.equilibrium, InpZoneEqPct); if(g_zone.legCounterTrend) s += " [P/D phá Big H0/L0]"; else if(g_zone.legExtended) s += " [P/D kéo thuận chiều]"; else s += " [H0-L0]"; s += StringFormat(" | %s", g_biasBuy ? "Discount" : "Premium"); } if(g_setup.valid) { s += "\n " + g_setup.summary; double rr = CalcRiskReward(g_setup.isBuy, g_setup.entry, g_setup.sl, g_setup.tp); if(rr > 0.0) { s += StringFormat(" | R:R=1:%.2f", rr); if(InpUseMinRR && rr < InpMinRR - 1e-8) s += StringFormat(" (< 1:%.1f)", InpMinRR); } } s += "\n " + SessionStatusText(); return s; } //+------------------------------------------------------------------+ void DrawPanel(const string body) { const long ch = ActChart(); const string name = OBJ_CH_PFX + "PANEL"; if(ObjectFind(ch, name) < 0) ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelX); ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, InpPanelY); ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(8, InpFontSize + 1)); ObjectSetString(ch, name, OBJPROP_FONT, "Consolas"); ObjectSetInteger(ch, name, OBJPROP_COLOR, StateColor(g_state)); ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); ObjectSetString(ch, name, OBJPROP_TEXT, body); } //+------------------------------------------------------------------+ void StatsEnsureLabel(const long ch, const string name, const int yDist) { if(ObjectFind(ch, name) < 0) ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_LOWER); ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpStatCornerX); ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, MathMax(0, yDist)); ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpStatFontSize)); ObjectSetString(ch, name, OBJPROP_FONT, "Consolas"); ObjectSetInteger(ch, name, OBJPROP_COLOR, InpStatColor); } //+------------------------------------------------------------------+ void StatsUpdate() { if(!InpShowStats) { ObjectDelete(ActChart(), STAT_LINE1); ObjectDelete(ActChart(), STAT_LINE2); return; } const long ch = ActChart(); const int gap = (int)(MathMax(7, InpStatFontSize) * 2.2) + 12; StatsEnsureLabel(ch, STAT_LINE1, InpStatCornerY); StatsEnsureLabel(ch, STAT_LINE2, InpStatCornerY + gap); const long closed = g_statWins + g_statLoss; const double wr = (closed > 0) ? 100.0 * (double)g_statWins / (double)closed : 0.0; const string l1 = StringFormat("MyICT | 1R=%.1f%% | SL/TP ATR×", InpRiskPercent); string l2; if(closed > 0) l2 = StringFormat("Đóng %I64d | Thắng %I64d | Thua %I64d | Winrate %.1f%%", g_statTotal, g_statWins, g_statLoss, wr); else l2 = StringFormat("Đóng %I64d | Thắng %I64d | Thua %I64d | Winrate: —", g_statTotal, g_statWins, g_statLoss); ObjectSetString(ch, STAT_LINE1, OBJPROP_TEXT, l1); ObjectSetString(ch, STAT_LINE2, OBJPROP_TEXT, l2); } //+------------------------------------------------------------------+ void StatsCountExitDeal(const ulong dealTicket) { if(dealTicket == 0 || !HistoryDealSelect(dealTicket)) return; if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol) return; if((ulong)HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != InpMagic) return; if(HistoryDealGetInteger(dealTicket, DEAL_ENTRY) != DEAL_ENTRY_OUT) return; g_statTotal++; const ENUM_DEAL_REASON dr = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON); if(dr == DEAL_REASON_TP) g_statWins++; else if(dr == DEAL_REASON_SL) g_statLoss++; else { const double net = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) + HistoryDealGetDouble(dealTicket, DEAL_SWAP) + HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); if(net > 0.0) g_statWins++; else if(net < 0.0) g_statLoss++; } } //+------------------------------------------------------------------+ void StatsRebuildFromHistory() { g_statWins = g_statLoss = g_statTotal = 0; if(!HistorySelect(0, TimeCurrent())) return; const int n = HistoryDealsTotal(); for(int i = 0; i < n; i++) StatsCountExitDeal(HistoryDealGetTicket(i)); } //+------------------------------------------------------------------+ void RedrawChart(const string sym, const ENUM_TIMEFRAMES tf) { DeleteObjectsByPrefix(OBJ_CH_PFX); string panel = StringFormat("MyICT | %s %s\n", sym, EnumToString(tf)); panel += StateBlock() + "\n"; if(InpUseSessionFilter && !IsTradeSessionAllowed()) panel += " >> Session CLOSED — không đặt lệnh mới\n"; if(g_state == ICT_STATE_WAITING_TRIGGER && g_setup.valid) { const double rrPrev = CalcRiskReward(g_setup.isBuy, g_setup.entry, g_setup.sl, g_setup.tp); if(InpUseMinRR && rrPrev > 0.0 && rrPrev < InpMinRR - 1e-8) panel += StringFormat(" >> MSS OK nhưng R:R 1:%.2f < 1:%.1f — bỏ qua\n", rrPrev, InpMinRR); else panel += " >> MSS setup OK — chờ limit breaker\n"; } else if(g_state == ICT_STATE_LIMIT_ORDERED) panel += " >> Limit breaker đang chờ khớp\n"; panel += LayerBlock("BigTrend", InpBigSwingRange, g_big) + "\n"; panel += LayerBlock("SmallTrend", InpSmallSwingRange, g_small); DrawPanel(panel); DrawLayer("BIG", g_big, InpClrBigHigh, InpClrBigLow, 2); DrawLayer("SML", g_small, InpClrSmallHigh, InpClrSmallLow, 1); DrawZones(sym, tf); if(g_state == ICT_STATE_LIMIT_ORDERED) { // giữ setup đã đặt limit } else if(g_state == ICT_STATE_WAITING_TRIGGER || g_state == ICT_STATE_PULLBACK) { IctMssSetup tmp; if(DetectMssSetup(sym, tf, tmp)) g_setup = tmp; else if(g_state != ICT_STATE_WAITING_TRIGGER) g_setup.valid = false; } else g_setup.valid = false; DrawMssSetup(sym, tf); StatsUpdate(); ChartRedraw(ActChart()); } //+------------------------------------------------------------------+ void OnNewBar(const string sym) { const ENUM_TIMEFRAMES tf = ChartTf(); AnalyzeLayer(sym, tf, InpBigSwingRange, g_big); AnalyzeLayer(sym, tf, InpSmallSwingRange, g_small); if(g_big.hasH0 && g_big.hasL0) CalcBigWaveZone(sym, tf); UpdateStateMachine(sym, tf); RedrawChart(sym, tf); Comment(StringFormat("MyICT | %s\n%s\nBig: %s | Small: %s", StateText(g_state), g_biasBuy && (g_state == ICT_STATE_PULLBACK || g_state == ICT_STATE_WAITING_TRIGGER) ? "Bias BUY" : (!g_biasBuy && IsPullbackStructure() ? "Bias SELL" : ""), TrendText(g_big.trend), TrendText(g_small.trend))); } //+------------------------------------------------------------------+ int OnInit() { g_lastBar = 0; g_lastTriggerBar = 0; g_limitPlacedBar = 0; ZeroMemory(g_setup); g_state = ICT_STATE_NO_TREND; g_statePrev = ICT_STATE_NO_TREND; g_hAtr = iATR(_Symbol, ChartTf(), InpATRPeriod); if(g_hAtr == INVALID_HANDLE) { Print("[MyICT] Không tạo ATR"); return INIT_FAILED; } g_trade.SetExpertMagicNumber(InpMagic); g_trade.SetDeviationInPoints(InpSlippagePoints); SetTradeFilling(); StatsRebuildFromHistory(); OnNewBar(_Symbol); Print("[MyICT] Init state=", StateText(g_state)); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { DeleteObjectsByPrefix(OBJ_CH_PFX); ObjectDelete(ActChart(), STAT_LINE1); ObjectDelete(ActChart(), STAT_LINE2); if(g_hAtr != INVALID_HANDLE) IndicatorRelease(g_hAtr); Comment(""); } //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return; const ulong deal = trans.deal; if(deal == 0 || !HistoryDealSelect(deal)) return; if(HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol) return; if((ulong)HistoryDealGetInteger(deal, DEAL_MAGIC) != InpMagic) return; const long entry = HistoryDealGetInteger(deal, DEAL_ENTRY); if(entry != DEAL_ENTRY_OUT) return; StatsCountExitDeal(deal); g_state = ICT_STATE_NONE; Dbg("OnTradeTransaction → NONE (đóng lệnh)"); RedrawChart(_Symbol, ChartTf()); } //+------------------------------------------------------------------+ void RefreshZoneDraw(const string sym, const ENUM_TIMEFRAMES tf) { if(!g_big.hasH0 || !g_big.hasL0) return; CalcBigWaveZone(sym, tf); if(InpDrawZones && g_zone.valid) { DrawZones(sym, tf); ChartRedraw(ActChart()); } } //+------------------------------------------------------------------+ void OnTick() { const string sym = _Symbol; const ENUM_TIMEFRAMES tf = ChartTf(); const datetime t0 = iTime(sym, tf, 0); if(t0 == 0) return; if(t0 != g_lastBar) { g_lastBar = t0; OnNewBar(sym); return; } RefreshZoneDraw(sym, tf); ManagePendingLimits(sym, tf); } //+------------------------------------------------------------------+ ENUM_ICT_STATE GetIctState() { return g_state; } ENUM_ICT_TREND GetBigTrend() { return g_big.trend; } ENUM_ICT_TREND GetSmallTrend() { return g_small.trend; }