//+------------------------------------------------------------------+ //| MyICT.mq5 | //| 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" #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 "══ 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 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 InpClrZoneLabel = clrWhite; 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; double waveHigh; double waveLow; double equilibrium; double discountTop; // buy: giá <= discountTop = trong discount double premiumBottom; // sell: giá >= premiumBottom = trong premium }; 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; 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; 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); } } //+------------------------------------------------------------------+ 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 DoubleTolerance(const string sym, const double atr) { const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); return MathMax(pt * 10.0, atr * InpDoubleTolAtrMult); } //+------------------------------------------------------------------+ bool IsSmallDoubleBottom(const double l0, const double l1, const double tol) { if(MathAbs(l0 - l1) <= tol) return true; if(l0 > l1 + _Point) return true; return false; } //+------------------------------------------------------------------+ bool IsSmallDoubleTop(const double h0, const double h1, const double tol) { if(MathAbs(h0 - h1) <= tol) return true; if(h0 < h1 - _Point) return true; return false; } //+------------------------------------------------------------------+ bool TriggerPatternOk() { const double atr = GetAtr(_Symbol, ChartTf(), 1); if(atr <= 0.0) return false; const double tol = DoubleTolerance(_Symbol, atr); if(g_biasBuy) { if(!g_small.hasL0 || !g_small.hasL1) return false; return IsSmallDoubleBottom(g_small.l0.price, g_small.l1.price, tol); } if(!g_small.hasH0 || !g_small.hasH1) return false; return IsSmallDoubleTop(g_small.h0.price, g_small.h1.price, tol); } //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ bool CalcTriggerStops(const bool isBuy, const double atr, double &sl, double &tp, string &reason) { const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); const double buf = atr * InpSlAtrMult; const double tpBuf = atr * InpTpAtrBuffer; if(isBuy) { 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"; } 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) { if(!InpTradeEnabled) return false; if(g_state != ICT_STATE_WAITING_TRIGGER) return false; if(InpOnePosition && HasMyPosition(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; } 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)) { Dbg("Trigger skip: " + reason); return false; } 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(tp <= entry || sl >= entry) { Dbg("Trigger skip Buy: TP/SL không hợp lệ"); return false; } } else { if(tp >= entry || sl <= entry) { Dbg("Trigger skip Sell: TP/SL không hợp lệ"); return false; } } if(!StopsValid(sym, g_biasBuy, entry, sl, tp)) { Dbg("Trigger skip: STOPS_LEVEL"); return false; } double vol = NormalizeVolume(sym, VolumeForRisk(sym, g_biasBuy, entry, sl)); if(vol <= 0.0) { Dbg("Trigger skip: volume=0"); return false; } g_trade.SetExpertMagicNumber(InpMagic); 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); if(!ok) { Print("[MyICT] Trigger 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); 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 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; } //+------------------------------------------------------------------+ void CalcBigWaveZone() { ZeroMemory(g_zone); if(!g_big.hasH0 || !g_big.hasL0) return; g_zone.waveHigh = g_big.h0.price; g_zone.waveLow = g_big.l0.price; 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 <= 0.0) 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; } //+------------------------------------------------------------------+ 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(); const double refPrice = 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; } 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) TryExecuteTrigger(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); const datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1)); 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; 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)"); 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)"); 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); } //+------------------------------------------------------------------+ 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 Eq=%.2f (%.0f%%) | %s", g_zone.equilibrium, InpZoneEqPct, g_biasBuy ? "Discount" : "Premium"); 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(g_state == ICT_STATE_WAITING_TRIGGER && TriggerPatternOk()) panel += " >> Trigger pattern OK (2L/2H)\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); 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(); 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_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 OnTick() { const string sym = _Symbol; const ENUM_TIMEFRAMES tf = ChartTf(); const datetime t0 = iTime(sym, tf, 0); if(t0 == 0 || t0 == g_lastBar) return; g_lastBar = t0; OnNewBar(sym); } //+------------------------------------------------------------------+ ENUM_ICT_STATE GetIctState() { return g_state; } ENUM_ICT_TREND GetBigTrend() { return g_big.trend; } ENUM_ICT_TREND GetSmallTrend() { return g_small.trend; }