Files
2026-05-17 00:01:39 +07:00

1859 lines
61 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 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 <Trade/Trade.mqh>
//+------------------------------------------------------------------+
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; }