From e78e1c82ec6c652a2b2aad2084d70b4db231643e Mon Sep 17 00:00:00 2001 From: Bell Date: Sat, 16 May 2026 22:42:41 +0700 Subject: [PATCH] MSS update --- Experts/MyICT.mq5 | 869 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 758 insertions(+), 111 deletions(-) diff --git a/Experts/MyICT.mq5 b/Experts/MyICT.mq5 index de634c2..c74f4ad 100644 --- a/Experts/MyICT.mq5 +++ b/Experts/MyICT.mq5 @@ -3,8 +3,8 @@ //| Big/Small structure + State machine (NO_TREND → PULLBACK → …) | //+------------------------------------------------------------------+ #property copyright "MyICT" -#property version "1.05" -#property description "ICT trigger: 2L/2H small + discount/premium | R% | stats" +#property version "1.10" +#property description "ICT MSS: sweep + BOS + FVG → limit @ breaker | R% | stats" #include @@ -29,7 +29,7 @@ enum ENUM_ICT_STATE //+------------------------------------------------------------------+ input group "══ Swing (nến trước/sau pivot) ══" -input int InpBigSwingRange = 24; +input int InpBigSwingRange = 12; input int InpSmallSwingRange = 2; input int InpSwingLookback = 400; @@ -37,14 +37,34 @@ 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 dưới/trên 2 đáy/đỉnh small + ATR× -input double InpTpAtrBuffer = 0.25; // TP trước H0/L0 big −/+ ATR× -input double InpDoubleTolAtrMult = 0.15; // 2 đáy/đỉnh “bằng nhau” (×ATR) +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; @@ -67,7 +87,11 @@ 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; @@ -96,11 +120,34 @@ struct TrendSnapshot struct BigWaveZone { bool valid; + bool legExtended; // sóng kéo tới giá mới (không chỉ H0-L0 cũ) double waveHigh; double waveLow; double equilibrium; - double discountTop; // buy: giá <= discountTop = trong discount - double premiumBottom; // sell: giá >= premiumBottom = trong premium + 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_"; @@ -111,11 +158,13 @@ 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; @@ -154,6 +203,88 @@ void DeleteObjectsByPrefix(const string prefix) } } +//+------------------------------------------------------------------+ +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) { @@ -167,50 +298,297 @@ double GetAtr(const string sym, const ENUM_TIMEFRAMES tf, const int shift) } //+------------------------------------------------------------------+ -double DoubleTolerance(const string sym, const double atr) +double SweepTolerance(const string sym, const double atr) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); - return MathMax(pt * 10.0, atr * InpDoubleTolAtrMult); + return MathMax(pt * InpSweepTolPoints, atr * InpSweepTolAtr); } //+------------------------------------------------------------------+ -bool IsSmallDoubleBottom(const double l0, const double l1, const double tol) +double BosTolerance(const string sym, const double atr) { - if(MathAbs(l0 - l1) <= tol) - return true; - if(l0 > l1 + _Point) - return true; + 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 IsSmallDoubleTop(const double h0, const double h1, const double tol) +bool FindBullishFvg(const string sym, const ENUM_TIMEFRAMES tf, + const int shiftFrom, const int shiftTo, + double &fTop, double &fBot, int &fShift) { - if(MathAbs(h0 - h1) <= tol) - return true; - if(h0 < h1 - _Point) - return true; + 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 TriggerPatternOk() +bool WasLiquiditySweepHigh(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const double liq, + const double sweepTol, double &sweepHigh) { - const double atr = GetAtr(_Symbol, ChartTf(), 1); + 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 tol = DoubleTolerance(_Symbol, atr); - if(g_biasBuy) + 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++) { - if(!g_small.hasL0 || !g_small.hasL1) - return false; - return IsSmallDoubleBottom(g_small.l0.price, g_small.l1.price, tol); - } + const double cMss = iClose(sym, tf, mssSh); + if(cMss >= broken - bosTol) + continue; - if(!g_small.hasH0 || !g_small.hasH1) + 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; - return IsSmallDoubleTop(g_small.h0.price, g_small.h1.price, tol); + + 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; } //+------------------------------------------------------------------+ @@ -247,6 +625,28 @@ double VolumeForRisk(const string sym, const bool isBuy, 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) @@ -271,49 +671,88 @@ bool StopsValid(const string sym, const bool isBuy, } //+------------------------------------------------------------------+ -bool CalcTriggerStops(const bool isBuy, const double atr, - double &sl, double &tp, string &reason) +void CancelMyPendingLimits(const string sym) { - const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - const double buf = atr * InpSlAtrMult; - const double tpBuf = atr * InpTpAtrBuffer; - - if(isBuy) + for(int i = OrdersTotal() - 1; i >= 0; i--) { - if(!g_small.hasL0 || !g_small.hasL1 || !g_big.hasH0) - { - reason = "thiếu swing small L hoặc Big H0"; - return false; - } - const double low2 = MathMin(g_small.l0.price, g_small.l1.price); - sl = NormalizeDouble(low2 - buf, dig); - tp = NormalizeDouble(g_big.h0.price - tpBuf, dig); - reason = "2L small + TP trước Big H0"; + 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); } - else - { - if(!g_small.hasH0 || !g_small.hasH1 || !g_big.hasL0) - { - reason = "thiếu swing small H hoặc Big L0"; - return false; - } - const double high2 = MathMax(g_small.h0.price, g_small.h1.price); - sl = NormalizeDouble(high2 + buf, dig); - tp = NormalizeDouble(g_big.l0.price + tpBuf, dig); - reason = "2H small + TP trước Big L0"; - } - return true; } //+------------------------------------------------------------------+ -bool TryExecuteTrigger(const string sym, const ENUM_TIMEFRAMES tf) +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) @@ -331,55 +770,57 @@ bool TryExecuteTrigger(const string sym, const ENUM_TIMEFRAMES tf) return false; } - if(!TriggerPatternOk()) - return false; - - const double atr = GetAtr(sym, tf, 1); - if(atr <= 0.0) - return false; - - double sl = 0.0, tp = 0.0; - string reason = ""; - if(!CalcTriggerStops(g_biasBuy, atr, sl, tp, reason)) + IctMssSetup setup; + if(!DetectMssSetup(sym, tf, setup)) { - Dbg("Trigger skip: " + reason); + 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; - const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); - const double entry = NormalizeDouble(g_biasBuy ? tk.ask : tk.bid, dig); - - if(g_biasBuy) + if(setup.isBuy) { - if(tp <= entry || sl >= entry) + if(tp <= entry || sl >= entry || entry >= tk.ask - _Point) { - Dbg("Trigger skip Buy: TP/SL không hợp lệ"); + Dbg("MSS Buy skip: entry/TP/SL"); return false; } } else { - if(tp >= entry || sl <= entry) + if(tp >= entry || sl <= entry || entry <= tk.bid + _Point) { - Dbg("Trigger skip Sell: TP/SL không hợp lệ"); + Dbg("MSS Sell skip: entry/TP/SL"); return false; } } - if(!StopsValid(sym, g_biasBuy, entry, sl, tp)) + if(!StopsValid(sym, setup.isBuy, entry, sl, tp)) { - Dbg("Trigger skip: STOPS_LEVEL"); + Dbg("MSS skip: STOPS_LEVEL"); return false; } - double vol = NormalizeVolume(sym, VolumeForRisk(sym, g_biasBuy, entry, sl)); + 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("Trigger skip: volume=0"); + Dbg("MSS skip: volume=0"); return false; } @@ -387,21 +828,23 @@ bool TryExecuteTrigger(const string sym, const ENUM_TIMEFRAMES tf) g_trade.SetDeviationInPoints(InpSlippagePoints); SetTradeFilling(); - const string cmt = g_biasBuy ? "MyICT_BUY" : "MyICT_SELL"; - const bool ok = g_biasBuy - ? g_trade.Buy(vol, sym, 0.0, sl, tp, cmt) - : g_trade.Sell(vol, sym, 0.0, sl, tp, cmt); + 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] Trigger fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment()); + Print("[MyICT] MSS limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment()); return false; } g_lastTriggerBar = tBar; - g_state = ICT_STATE_ON_TRADE; - PrintFormat("[MyICT] TRIGGER %s | %s | vol=%.2f entry=%.5f SL=%.5f TP=%.5f", - cmt, reason, vol, entry, sl, tp); + 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; } @@ -580,6 +1023,22 @@ 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() { @@ -599,14 +1058,86 @@ bool IsPullbackStructure() } //+------------------------------------------------------------------+ -void CalcBigWaveZone() +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.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; // gồm nến đang hình thành (trước chỉ tới shift=1 → trễ) + + 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 + giá phá dưới Big L0 → H0 → đáy mới (logic; draw dùng cùng g_zone) + 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 tăng + giá phá trên Big 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); + } + if(g_zone.waveHigh < g_zone.waveLow) { const double t = g_zone.waveHigh; @@ -615,14 +1146,18 @@ void CalcBigWaveZone() } const double range = g_zone.waveHigh - g_zone.waveLow; - if(range <= 0.0) + 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.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 && InpDebug) + Dbg(StringFormat("Zone extended | H=%.5f L=%.5f Eq=%.5f liveL=%.5f", + g_zone.waveHigh, g_zone.waveLow, g_zone.equilibrium, liveLow)); } //+------------------------------------------------------------------+ @@ -699,9 +1234,11 @@ ENUM_ICT_STATE ComputeStructureState(const string sym, const ENUM_TIMEFRAMES tf) if(!IsPullbackStructure()) return ICT_STATE_NO_TREND; - CalcBigWaveZone(); + CalcBigWaveZone(sym, tf); - const double refPrice = iClose(sym, tf, 1); + 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) { @@ -726,6 +1263,8 @@ void UpdateStateMachine(const string sym, const ENUM_TIMEFRAMES tf) return; } + ManagePendingLimits(sym, tf); + if(HasMyPendingLimit(sym)) { g_state = ICT_STATE_LIMIT_ORDERED; @@ -747,7 +1286,7 @@ void UpdateStateMachine(const string sym, const ENUM_TIMEFRAMES tf) g_state = ComputeStructureState(sym, tf); if(g_state == ICT_STATE_WAITING_TRIGGER) - TryExecuteTrigger(sym, tf); + TryPlaceMssLimit(sym, tf); if(g_state != g_statePrev && InpDebug) Dbg(StringFormat("State %s → %s", StateText(g_statePrev), StateText(g_state))); @@ -883,8 +1422,15 @@ void DrawZones(const string sym, const ENUM_TIMEFRAMES tf) if(!InpDrawZones || !g_zone.valid) return; - const datetime t2 = iTime(sym, tf, 0); - const datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1)); + const datetime t2 = iTime(sym, tf, 0) + (datetime)PeriodSeconds(tf); + datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1)); + 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; @@ -895,15 +1441,16 @@ void DrawZones(const string sym, const ENUM_TIMEFRAMES tf) const uchar premAlpha = (activePullback && !g_biasBuy) ? InpZoneActiveAlpha : InpZoneFillAlpha; + const string extTip = g_zone.legExtended ? " (kéo theo giá)" : ""; DrawZoneRect(OBJ_CH_PFX + "Z_DISC", t1, t2, g_zone.discountTop, g_zone.waveLow, InpClrDiscount, discAlpha, - "Discount — nửa dưới sóng Big (Buy)"); + "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 (Sell)"); + "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); @@ -916,6 +1463,41 @@ void DrawZones(const string sym, const ENUM_TIMEFRAMES tf) 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) { @@ -933,9 +1515,24 @@ string StateBlock() 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 Eq=%.2f (%.0f%%) | %s", - g_zone.equilibrium, InpZoneEqPct, - g_biasBuy ? "Discount" : "Premium"); + { + s += StringFormat("\n Zone H=%.2f L=%.2f Eq=%.2f (%.0f%%)", + g_zone.waveHigh, g_zone.waveLow, g_zone.equilibrium, InpZoneEqPct); + s += g_zone.legExtended ? " [sóng kéo theo giá]" : " [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; } @@ -1051,8 +1648,18 @@ void RedrawChart(const string sym, const ENUM_TIMEFRAMES tf) string panel = StringFormat("MyICT | %s %s\n", sym, EnumToString(tf)); panel += StateBlock() + "\n"; - if(g_state == ICT_STATE_WAITING_TRIGGER && TriggerPatternOk()) - panel += " >> Trigger pattern OK (2L/2H)\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); @@ -1060,6 +1667,23 @@ void RedrawChart(const string sym, const ENUM_TIMEFRAMES tf) 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()); } @@ -1073,7 +1697,7 @@ void OnNewBar(const string sym) AnalyzeLayer(sym, tf, InpSmallSwingRange, g_small); if(g_big.hasH0 && g_big.hasL0) - CalcBigWaveZone(); + CalcBigWaveZone(sym, tf); UpdateStateMachine(sym, tf); RedrawChart(sym, tf); @@ -1091,6 +1715,8 @@ 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; @@ -1148,16 +1774,37 @@ void OnTradeTransaction(const MqlTradeTransaction &trans, 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 || t0 == g_lastBar) + if(t0 == 0) return; - g_lastBar = t0; - OnNewBar(sym); + + if(t0 != g_lastBar) + { + g_lastBar = t0; + OnNewBar(sym); + return; + } + + RefreshZoneDraw(sym, tf); + ManagePendingLimits(sym, tf); } //+------------------------------------------------------------------+