Files
Bell-PriceActionWithEma-EA/Experts/MyICT.mq5
T

1859 lines
61 KiB
Plaintext
Raw Normal View History

//+------------------------------------------------------------------+
//| MyICT.mq5 |
2026-05-16 21:42:46 +07:00
//| Big/Small structure + State machine (NO_TREND → PULLBACK → …) |
//+------------------------------------------------------------------+
#property copyright "MyICT"
2026-05-16 23:01:34 +07:00
#property version "1.11"
2026-05-16 22:42:41 +07:00
#property description "ICT MSS: sweep + BOS + FVG → limit @ breaker | R% | stats"
2026-05-16 21:42:46 +07:00
#include <Trade/Trade.mqh>
//+------------------------------------------------------------------+
enum ENUM_ICT_TREND
{
ICT_TREND_UNKNOWN = 0,
ICT_TREND_UP = 1,
ICT_TREND_DOWN = 2,
ICT_TREND_NEUTRAL = 3
};
2026-05-16 21:42:46 +07:00
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) ══"
2026-05-17 00:01:39 +07:00
input int InpBigSwingRange = 24;
2026-05-16 21:42:46 +07:00
input int InpSmallSwingRange = 2;
input int InpSwingLookback = 400;
2026-05-16 21:42:46 +07:00
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)
2026-05-16 22:42:41 +07:00
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)
2026-05-16 21:51:51 +07:00
input group "══ Trigger & quản lý lệnh ══"
input bool InpTradeEnabled = true;
2026-05-16 21:42:46 +07:00
input ulong InpMagic = 20260620;
2026-05-16 21:51:51 +07:00
input double InpRiskPercent = 1.0; // 1R = % balance
input int InpATRPeriod = 14;
2026-05-16 22:42:41 +07:00
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)
2026-05-16 21:51:51 +07:00
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;
2026-05-16 21:42:46 +07:00
input group "══ Hiển thị chart ══"
input bool InpDrawSwings = true;
input bool InpDrawTrendLines = true;
2026-05-16 21:42:46 +07:00
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;
2026-05-16 21:42:46 +07:00
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
2026-05-16 22:42:41 +07:00
input color InpClrBreaker = C'120,60,180';
input color InpClrFvg = C'40,140,90';
input color InpClrSweep = clrDeepSkyBlue;
2026-05-16 21:42:46 +07:00
input color InpClrZoneLabel = clrWhite;
2026-05-16 22:42:41 +07:00
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;
};
2026-05-16 21:42:46 +07:00
struct BigWaveZone
{
bool valid;
2026-05-16 23:01:34 +07:00
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)
2026-05-16 21:42:46 +07:00
double waveHigh;
double waveLow;
double equilibrium;
2026-05-16 22:42:41 +07:00
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;
2026-05-16 21:42:46 +07:00
};
2026-05-16 21:51:51 +07:00
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";
2026-05-16 21:42:46 +07:00
TrendSnapshot g_big;
TrendSnapshot g_small;
BigWaveZone g_zone;
2026-05-16 22:42:41 +07:00
IctMssSetup g_setup;
2026-05-16 21:42:46 +07:00
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;
2026-05-16 21:51:51 +07:00
datetime g_lastTriggerBar = 0;
2026-05-16 22:42:41 +07:00
datetime g_limitPlacedBar = 0;
2026-05-16 21:51:51 +07:00
int g_hAtr = INVALID_HANDLE;
long g_statWins = 0;
long g_statLoss = 0;
long g_statTotal = 0;
2026-05-16 21:42:46 +07:00
CTrade g_trade;
//+------------------------------------------------------------------+
long ActChart() { return ChartID(); }
2026-05-16 21:42:46 +07:00
ENUM_TIMEFRAMES ChartTf() { return (ENUM_TIMEFRAMES)Period(); }
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
color WithAlpha(const color clr, const uchar alpha)
{
2026-05-16 21:42:46 +07:00
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);
}
}
2026-05-16 22:42:41 +07:00
//+------------------------------------------------------------------+
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;
}
2026-05-16 21:51:51 +07:00
//+------------------------------------------------------------------+
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];
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
double SweepTolerance(const string sym, const double atr)
2026-05-16 21:51:51 +07:00
{
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
2026-05-16 22:42:41 +07:00
return MathMax(pt * InpSweepTolPoints, atr * InpSweepTolAtr);
2026-05-16 21:51:51 +07:00
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
double BosTolerance(const string sym, const double atr)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
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;
}
}
2026-05-16 21:51:51 +07:00
return false;
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
bool FindBullishFvg(const string sym, const ENUM_TIMEFRAMES tf,
const int shiftFrom, const int shiftTo,
double &fTop, double &fBot, int &fShift)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
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;
}
}
2026-05-16 21:51:51 +07:00
return false;
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
bool WasLiquiditySweepHigh(const string sym, const ENUM_TIMEFRAMES tf,
const int shift, const double liq,
const double sweepTol, double &sweepHigh)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
if(atr <= 0.0)
return false;
2026-05-16 22:42:41 +07:00
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++)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
const double cMss = iClose(sym, tf, mssSh);
if(cMss >= broken - bosTol)
continue;
2026-05-16 21:51:51 +07:00
2026-05-16 22:42:41 +07:00
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)
2026-05-16 21:51:51 +07:00
return false;
2026-05-16 22:42:41 +07:00
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;
2026-05-16 21:51:51 +07:00
}
//+------------------------------------------------------------------+
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;
}
2026-05-16 22:42:41 +07:00
//+------------------------------------------------------------------+
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);
}
2026-05-16 21:51:51 +07:00
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
void CancelMyPendingLimits(const string sym)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
for(int i = OrdersTotal() - 1; i >= 0; i--)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
}
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
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)
2026-05-16 21:51:51 +07:00
{
if(!InpTradeEnabled)
return false;
2026-05-16 22:42:41 +07:00
if(!IsTradeSessionAllowed())
{
Dbg("MSS skip: ngoài phiên London/NY");
return false;
}
2026-05-16 21:51:51 +07:00
if(g_state != ICT_STATE_WAITING_TRIGGER)
return false;
if(InpOnePosition && HasMyPosition(sym))
return false;
2026-05-16 22:42:41 +07:00
if(HasMyPendingLimit(sym))
return false;
2026-05-16 21:51:51 +07:00
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;
}
2026-05-16 22:42:41 +07:00
IctMssSetup setup;
if(!DetectMssSetup(sym, tf, setup))
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
g_setup.valid = false;
2026-05-16 21:51:51 +07:00
return false;
}
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
MqlTick tk;
if(!SymbolInfoTick(sym, tk))
return false;
2026-05-16 22:42:41 +07:00
if(setup.isBuy)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
if(tp <= entry || sl >= entry || entry >= tk.ask - _Point)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
Dbg("MSS Buy skip: entry/TP/SL");
2026-05-16 21:51:51 +07:00
return false;
}
}
else
{
2026-05-16 22:42:41 +07:00
if(tp >= entry || sl <= entry || entry <= tk.bid + _Point)
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
Dbg("MSS Sell skip: entry/TP/SL");
2026-05-16 21:51:51 +07:00
return false;
}
}
2026-05-16 22:42:41 +07:00
if(!StopsValid(sym, setup.isBuy, entry, sl, tp))
2026-05-16 21:51:51 +07:00
{
2026-05-16 22:42:41 +07:00
Dbg("MSS skip: STOPS_LEVEL");
2026-05-16 21:51:51 +07:00
return false;
}
2026-05-16 22:42:41 +07:00
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));
2026-05-16 21:51:51 +07:00
if(vol <= 0.0)
{
2026-05-16 22:42:41 +07:00
Dbg("MSS skip: volume=0");
2026-05-16 21:51:51 +07:00
return false;
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
SetTradeFilling();
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
if(!ok)
{
2026-05-16 22:42:41 +07:00
Print("[MyICT] MSS limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
2026-05-16 21:51:51 +07:00
return false;
}
g_lastTriggerBar = tBar;
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
return true;
}
//+------------------------------------------------------------------+
string TrendText(const ENUM_ICT_TREND t)
{
switch(t)
{
2026-05-16 21:42:46 +07:00
case ICT_TREND_UP: return "UP (HH+HL)";
case ICT_TREND_DOWN: return "DOWN (LH+LL)";
case ICT_TREND_NEUTRAL: return "NEUTRAL";
2026-05-16 21:42:46 +07:00
default: return "—";
}
}
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
string StateText(const ENUM_ICT_STATE s)
{
2026-05-16 21:42:46 +07:00
switch(s)
{
2026-05-16 21:42:46 +07:00
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;
2026-05-16 21:42:46 +07:00
if(count < 1) return;
s0 = pts[count - 1];
has0 = true;
2026-05-16 21:42:46 +07:00
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;
}
2026-05-16 21:42:46 +07:00
//+------------------------------------------------------------------+
bool BigTrendIsClear()
{
return (g_big.trend == ICT_TREND_UP || g_big.trend == ICT_TREND_DOWN);
}
2026-05-16 22:42:41 +07:00
//+------------------------------------------------------------------+
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);
}
2026-05-16 21:42:46 +07:00
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
2026-05-16 22:42:41 +07:00
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)
2026-05-16 21:42:46 +07:00
{
ZeroMemory(g_zone);
if(!g_big.hasH0 || !g_big.hasL0)
return;
2026-05-16 22:42:41 +07:00
g_zone.legExtended = false;
2026-05-16 23:01:34 +07:00
g_zone.legCounterTrend = false;
2026-05-16 21:42:46 +07:00
g_zone.waveHigh = g_big.h0.price;
g_zone.waveLow = g_big.l0.price;
2026-05-16 22:42:41 +07:00
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);
2026-05-16 23:01:34 +07:00
const int shNow = 0;
2026-05-16 22:42:41 +07:00
const double liveLow = MathMin(iLow(sym, tf, 0), iLow(sym, tf, 1));
const double liveHigh = MathMax(iHigh(sym, tf, 0), iHigh(sym, tf, 1));
2026-05-16 23:01:34 +07:00
// Big GIẢM — thuận: phá dưới L0 → H0 → đáy mới
2026-05-16 22:42:41 +07:00
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);
}
2026-05-16 23:01:34 +07:00
// 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
2026-05-16 22:42:41 +07:00
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);
}
2026-05-16 23:01:34 +07:00
// 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);
}
2026-05-16 22:42:41 +07:00
2026-05-16 21:42:46 +07:00
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;
2026-05-16 22:42:41 +07:00
if(range <= _Point * 2)
2026-05-16 21:42:46 +07:00
return;
const double pct = MathMax(1.0, MathMin(99.0, InpZoneEqPct)) / 100.0;
2026-05-16 22:42:41 +07:00
g_zone.equilibrium = g_zone.waveLow + range * pct;
g_zone.discountTop = g_zone.equilibrium;
2026-05-16 21:42:46 +07:00
g_zone.premiumBottom = g_zone.equilibrium;
g_zone.valid = true;
2026-05-16 22:42:41 +07:00
2026-05-16 23:01:34 +07:00
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));
2026-05-16 21:42:46 +07:00
}
//+------------------------------------------------------------------+
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;
2026-05-16 22:42:41 +07:00
CalcBigWaveZone(sym, tf);
2026-05-16 21:42:46 +07:00
2026-05-16 22:42:41 +07:00
const double refPrice = g_biasBuy
? MathMin(iClose(sym, tf, 0), iClose(sym, tf, 1))
: MathMax(iClose(sym, tf, 0), iClose(sym, tf, 1));
2026-05-16 21:42:46 +07:00
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;
}
2026-05-16 22:42:41 +07:00
ManagePendingLimits(sym, tf);
2026-05-16 21:42:46 +07:00
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)
2026-05-16 22:42:41 +07:00
TryPlaceMssLimit(sym, tf);
2026-05-16 21:42:46 +07:00
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();
2026-05-16 21:51:51 +07:00
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)
2026-05-16 21:42:46 +07:00
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);
}
2026-05-16 21:42:46 +07:00
//+------------------------------------------------------------------+
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)
2026-05-16 21:42:46 +07:00
DrawArrowTag(tag + "_H0", snap.h0.time, snap.h0.price, true, tag + " H0", clrHi, lineWidth);
if(snap.hasH1)
2026-05-16 21:42:46 +07:00
DrawArrowTag(tag + "_H1", snap.h1.time, snap.h1.price, true, tag + " H1", clrHi, lineWidth);
if(snap.hasL0)
2026-05-16 21:42:46 +07:00
DrawArrowTag(tag + "_L0", snap.l0.time, snap.l0.price, false, tag + " L0", clrLo, lineWidth);
if(snap.hasL1)
2026-05-16 21:42:46 +07:00
DrawArrowTag(tag + "_L1", snap.l1.time, snap.l1.price, false, tag + " L1", clrLo, lineWidth);
if(!InpDrawTrendLines)
return;
if(snap.hasH0 && snap.hasH1)
2026-05-16 21:51:51 +07:00
DrawSegLine(OBJ_CH_PFX + tag + "_LNH", snap.h1.time, snap.h1.price,
2026-05-16 21:42:46 +07:00
snap.h0.time, snap.h0.price, clrHi, lineWidth);
if(snap.hasL0 && snap.hasL1)
2026-05-16 21:51:51 +07:00
DrawSegLine(OBJ_CH_PFX + tag + "_LNL", snap.l1.time, snap.l1.price,
2026-05-16 21:42:46 +07:00
snap.l0.time, snap.l0.price, clrLo, lineWidth);
}
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
void DrawZones(const string sym, const ENUM_TIMEFRAMES tf)
{
2026-05-16 21:42:46 +07:00
if(!InpDrawZones || !g_zone.valid)
return;
2026-05-16 22:42:41 +07:00
const datetime t2 = iTime(sym, tf, 0) + (datetime)PeriodSeconds(tf);
datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1));
2026-05-16 23:01:34 +07:00
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)
2026-05-16 22:42:41 +07:00
{
if(BigIsBearish() && g_big.hasH0)
t1 = g_big.h0.time;
else if(BigIsBullish() && g_big.hasL0)
t1 = g_big.l0.time;
}
2026-05-16 21:42:46 +07:00
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;
2026-05-16 23:01:34 +07:00
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)";
2026-05-16 21:51:51 +07:00
DrawZoneRect(OBJ_CH_PFX + "Z_DISC", t1, t2,
2026-05-16 21:42:46 +07:00
g_zone.discountTop, g_zone.waveLow,
InpClrDiscount, discAlpha,
2026-05-16 22:42:41 +07:00
"Discount — nửa dưới sóng Big" + extTip);
2026-05-16 21:42:46 +07:00
2026-05-16 21:51:51 +07:00
DrawZoneRect(OBJ_CH_PFX + "Z_PREM", t1, t2,
2026-05-16 21:42:46 +07:00
g_zone.waveHigh, g_zone.premiumBottom,
InpClrPremium, premAlpha,
2026-05-16 22:42:41 +07:00
"Premium — nửa trên sóng Big" + extTip);
2026-05-16 21:42:46 +07:00
2026-05-16 21:51:51 +07:00
DrawSegLine(OBJ_CH_PFX + "Z_EQ", t1, g_zone.equilibrium, t2, g_zone.equilibrium,
2026-05-16 21:42:46 +07:00
clrGold, 1);
2026-05-16 21:51:51 +07:00
ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_WIDTH, 2);
2026-05-16 21:42:46 +07:00
const double midDisc = (g_zone.waveLow + g_zone.discountTop) * 0.5;
const double midPrem = (g_zone.premiumBottom + g_zone.waveHigh) * 0.5;
2026-05-16 21:51:51 +07:00
DrawZoneTag(OBJ_CH_PFX + "LBL_DISC", t2, midDisc, " DISCOUNT ", InpClrZoneLabel);
DrawZoneTag(OBJ_CH_PFX + "LBL_PREM", t2, midPrem, " PREMIUM ", InpClrZoneLabel);
2026-05-16 21:42:46 +07:00
}
2026-05-16 22:42:41 +07:00
//+------------------------------------------------------------------+
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);
}
2026-05-16 21:42:46 +07:00
//+------------------------------------------------------------------+
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)
2026-05-16 21:42:46 +07:00
s += StringFormat("\n H0=%.2f H1=%.2f L0=%.2f L1=%.2f",
snap.h0.price, snap.h1.price, snap.l0.price, snap.l1.price);
2026-05-16 21:42:46 +07:00
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)
2026-05-16 22:42:41 +07:00
{
s += StringFormat("\n Zone H=%.2f L=%.2f Eq=%.2f (%.0f%%)",
g_zone.waveHigh, g_zone.waveLow, g_zone.equilibrium, InpZoneEqPct);
2026-05-16 23:01:34 +07:00
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]";
2026-05-16 22:42:41 +07:00
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();
2026-05-16 21:51:51 +07:00
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");
2026-05-16 21:42:46 +07:00
ObjectSetInteger(ch, name, OBJPROP_COLOR, StateColor(g_state));
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetString(ch, name, OBJPROP_TEXT, body);
}
2026-05-16 21:51:51 +07:00
//+------------------------------------------------------------------+
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));
}
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
void RedrawChart(const string sym, const ENUM_TIMEFRAMES tf)
{
2026-05-16 21:51:51 +07:00
DeleteObjectsByPrefix(OBJ_CH_PFX);
2026-05-16 21:42:46 +07:00
string panel = StringFormat("MyICT | %s %s\n", sym, EnumToString(tf));
panel += StateBlock() + "\n";
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:42:46 +07:00
DrawZones(sym, tf);
2026-05-16 22:42:41 +07:00
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);
2026-05-16 21:51:51 +07:00
StatsUpdate();
ChartRedraw(ActChart());
}
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
void OnNewBar(const string sym)
{
const ENUM_TIMEFRAMES tf = ChartTf();
AnalyzeLayer(sym, tf, InpBigSwingRange, g_big);
AnalyzeLayer(sym, tf, InpSmallSwingRange, g_small);
2026-05-16 21:42:46 +07:00
if(g_big.hasH0 && g_big.hasL0)
2026-05-16 22:42:41 +07:00
CalcBigWaveZone(sym, tf);
2026-05-16 21:42:46 +07:00
UpdateStateMachine(sym, tf);
RedrawChart(sym, tf);
2026-05-16 21:42:46 +07:00
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;
2026-05-16 21:51:51 +07:00
g_lastTriggerBar = 0;
2026-05-16 22:42:41 +07:00
g_limitPlacedBar = 0;
ZeroMemory(g_setup);
2026-05-16 21:42:46 +07:00
g_state = ICT_STATE_NO_TREND;
g_statePrev = ICT_STATE_NO_TREND;
2026-05-16 21:51:51 +07:00
g_hAtr = iATR(_Symbol, ChartTf(), InpATRPeriod);
if(g_hAtr == INVALID_HANDLE)
{
Print("[MyICT] Không tạo ATR");
return INIT_FAILED;
}
2026-05-16 21:42:46 +07:00
g_trade.SetExpertMagicNumber(InpMagic);
2026-05-16 21:51:51 +07:00
g_trade.SetDeviationInPoints(InpSlippagePoints);
2026-05-16 21:42:46 +07:00
SetTradeFilling();
2026-05-16 21:51:51 +07:00
StatsRebuildFromHistory();
2026-05-16 21:42:46 +07:00
OnNewBar(_Symbol);
Print("[MyICT] Init state=", StateText(g_state));
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
2026-05-16 21:51:51 +07:00
DeleteObjectsByPrefix(OBJ_CH_PFX);
ObjectDelete(ActChart(), STAT_LINE1);
ObjectDelete(ActChart(), STAT_LINE2);
if(g_hAtr != INVALID_HANDLE)
IndicatorRelease(g_hAtr);
Comment("");
}
2026-05-16 21:42:46 +07:00
//+------------------------------------------------------------------+
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;
2026-05-16 21:51:51 +07:00
StatsCountExitDeal(deal);
2026-05-16 21:42:46 +07:00
g_state = ICT_STATE_NONE;
Dbg("OnTradeTransaction → NONE (đóng lệnh)");
RedrawChart(_Symbol, ChartTf());
}
2026-05-16 22:42:41 +07:00
//+------------------------------------------------------------------+
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);
2026-05-16 22:42:41 +07:00
if(t0 == 0)
return;
2026-05-16 22:42:41 +07:00
if(t0 != g_lastBar)
{
g_lastBar = t0;
OnNewBar(sym);
return;
}
RefreshZoneDraw(sym, tf);
ManagePendingLimits(sym, tf);
}
//+------------------------------------------------------------------+
2026-05-16 21:42:46 +07:00
ENUM_ICT_STATE GetIctState() { return g_state; }
ENUM_ICT_TREND GetBigTrend() { return g_big.trend; }
ENUM_ICT_TREND GetSmallTrend() { return g_small.trend; }