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

1167 lines
38 KiB
Plaintext
Raw 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.05"
#property description "ICT trigger: 2L/2H small + discount/premium | 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 "══ Trigger & quản lý lệnh ══"
input bool InpTradeEnabled = true;
input ulong InpMagic = 20260620;
input double InpRiskPercent = 1.0; // 1R = % balance
input int InpATRPeriod = 14;
input double InpSlAtrMult = 0.25; // SL dưới/trên 2 đáy/đỉnh small + ATR×
input double InpTpAtrBuffer = 0.25; // TP trước H0/L0 big /+ ATR×
input double InpDoubleTolAtrMult = 0.15; // 2 đáy/đỉnh “bằng nhau” (×ATR)
input int InpSlippagePoints = 30;
input bool InpOnePosition = true;
input group "══ Thống kê (góc dưới-trái) ══"
input bool InpShowStats = true;
input int InpStatFontSize = 9;
input int InpStatCornerX = 8;
input int InpStatCornerY = 8;
input color InpStatColor = clrSilver;
input group "══ Hiển thị chart ══"
input bool InpDrawSwings = true;
input bool InpDrawTrendLines = true;
input bool InpDrawZones = true;
input uchar InpZoneFillAlpha = 68; // 0=đặc, 255=trong suốt (nhỏ=hơn=mờ vừa)
input uchar InpZoneActiveAlpha = 50; // vùng bias active (đậm hơn)
input color InpClrBigHigh = clrDodgerBlue;
input color InpClrBigLow = clrDeepSkyBlue;
input color InpClrSmallHigh = clrOrange;
input color InpClrSmallLow = clrGold;
input color InpClrDiscount = C'0,110,45'; // xanh đậm (dễ thấy trên nền đen)
input color InpClrPremium = C'150,45,55'; // đỏ burgundy
input color InpClrZoneLabel = clrWhite;
input color InpClrUp = clrLime;
input color InpClrDown = clrTomato;
input color InpClrNeutral = clrSilver;
input int InpFontSize = 9;
input int InpPanelX = 12;
input int InpPanelY = 24;
input group "══ Debug ══"
input bool InpDebug = false;
//+------------------------------------------------------------------+
struct SwingPoint
{
double price;
datetime time;
int shift;
};
struct TrendSnapshot
{
ENUM_ICT_TREND trend;
SwingPoint h0, h1, l0, l1;
bool hasH0, hasH1, hasL0, hasL1;
};
struct BigWaveZone
{
bool valid;
double waveHigh;
double waveLow;
double equilibrium;
double discountTop; // buy: giá <= discountTop = trong discount
double premiumBottom; // sell: giá >= premiumBottom = trong premium
};
const string OBJ_CH_PFX = "MYICTC_";
const string STAT_PREFIX = "MYICTS_";
const string STAT_LINE1 = STAT_PREFIX + "L1";
const string STAT_LINE2 = STAT_PREFIX + "L2";
TrendSnapshot g_big;
TrendSnapshot g_small;
BigWaveZone g_zone;
ENUM_ICT_STATE g_state = ICT_STATE_NO_TREND;
ENUM_ICT_STATE g_statePrev = ICT_STATE_NO_TREND;
bool g_biasBuy = false;
datetime g_lastBar = 0;
datetime g_lastTriggerBar = 0;
int g_hAtr = INVALID_HANDLE;
long g_statWins = 0;
long g_statLoss = 0;
long g_statTotal = 0;
CTrade g_trade;
//+------------------------------------------------------------------+
long ActChart() { return ChartID(); }
ENUM_TIMEFRAMES ChartTf() { return (ENUM_TIMEFRAMES)Period(); }
//+------------------------------------------------------------------+
color WithAlpha(const color clr, const uchar alpha)
{
return (color)((long)clr | ((long)alpha << 24));
}
//+------------------------------------------------------------------+
void Dbg(const string msg)
{
if(InpDebug)
Print("[MyICT] ", msg);
}
//+------------------------------------------------------------------+
void DeleteObjectsByPrefix(const string prefix)
{
const long ch = ActChart();
for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--)
{
const string name = ObjectName(ch, i, 0, -1);
if(StringFind(name, prefix) == 0)
ObjectDelete(ch, name);
}
}
//+------------------------------------------------------------------+
double GetAtr(const string sym, const ENUM_TIMEFRAMES tf, const int shift)
{
if(g_hAtr == INVALID_HANDLE)
return 0.0;
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(g_hAtr, 0, shift, 1, b) < 1)
return 0.0;
return b[0];
}
//+------------------------------------------------------------------+
double DoubleTolerance(const string sym, const double atr)
{
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
return MathMax(pt * 10.0, atr * InpDoubleTolAtrMult);
}
//+------------------------------------------------------------------+
bool IsSmallDoubleBottom(const double l0, const double l1, const double tol)
{
if(MathAbs(l0 - l1) <= tol)
return true;
if(l0 > l1 + _Point)
return true;
return false;
}
//+------------------------------------------------------------------+
bool IsSmallDoubleTop(const double h0, const double h1, const double tol)
{
if(MathAbs(h0 - h1) <= tol)
return true;
if(h0 < h1 - _Point)
return true;
return false;
}
//+------------------------------------------------------------------+
bool TriggerPatternOk()
{
const double atr = GetAtr(_Symbol, ChartTf(), 1);
if(atr <= 0.0)
return false;
const double tol = DoubleTolerance(_Symbol, atr);
if(g_biasBuy)
{
if(!g_small.hasL0 || !g_small.hasL1)
return false;
return IsSmallDoubleBottom(g_small.l0.price, g_small.l1.price, tol);
}
if(!g_small.hasH0 || !g_small.hasH1)
return false;
return IsSmallDoubleTop(g_small.h0.price, g_small.h1.price, tol);
}
//+------------------------------------------------------------------+
double NormalizeVolume(const string sym, double v)
{
const double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
const double vmin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
const double vmax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
if(step <= 0.0)
return 0.0;
v = MathFloor(v / step) * step;
if(v < vmin - 1e-12)
return 0.0;
if(v > vmax)
v = vmax;
return NormalizeDouble(v, 8);
}
//+------------------------------------------------------------------+
double VolumeForRisk(const string sym, const bool isBuy,
const double entry, const double sl)
{
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
if(MathAbs(entry - sl) < pt)
return 0.0;
const double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
double profit = 0.0;
if(!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,
sym, 1.0, entry, sl, profit))
return 0.0;
const double lossPerLot = MathAbs(profit);
if(lossPerLot < DBL_EPSILON)
return 0.0;
return riskMoney / lossPerLot;
}
//+------------------------------------------------------------------+
bool StopsValid(const string sym, const bool isBuy,
const double entry, const double sl, const double tp)
{
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
const int stops = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
const int freeze = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL);
const double md = MathMax(stops, freeze) * pt;
if(md <= 0.0)
return true;
if(isBuy)
{
if(entry - sl < md - pt) return false;
if(tp - entry < md - pt) return false;
}
else
{
if(sl - entry < md - pt) return false;
if(entry - tp < md - pt) return false;
}
return true;
}
//+------------------------------------------------------------------+
bool CalcTriggerStops(const bool isBuy, const double atr,
double &sl, double &tp, string &reason)
{
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double buf = atr * InpSlAtrMult;
const double tpBuf = atr * InpTpAtrBuffer;
if(isBuy)
{
if(!g_small.hasL0 || !g_small.hasL1 || !g_big.hasH0)
{
reason = "thiếu swing small L hoặc Big H0";
return false;
}
const double low2 = MathMin(g_small.l0.price, g_small.l1.price);
sl = NormalizeDouble(low2 - buf, dig);
tp = NormalizeDouble(g_big.h0.price - tpBuf, dig);
reason = "2L small + TP trước Big H0";
}
else
{
if(!g_small.hasH0 || !g_small.hasH1 || !g_big.hasL0)
{
reason = "thiếu swing small H hoặc Big L0";
return false;
}
const double high2 = MathMax(g_small.h0.price, g_small.h1.price);
sl = NormalizeDouble(high2 + buf, dig);
tp = NormalizeDouble(g_big.l0.price + tpBuf, dig);
reason = "2H small + TP trước Big L0";
}
return true;
}
//+------------------------------------------------------------------+
bool TryExecuteTrigger(const string sym, const ENUM_TIMEFRAMES tf)
{
if(!InpTradeEnabled)
return false;
if(g_state != ICT_STATE_WAITING_TRIGGER)
return false;
if(InpOnePosition && HasMyPosition(sym))
return false;
const datetime tBar = iTime(sym, tf, 1);
if(tBar == 0 || tBar == g_lastTriggerBar)
return false;
const double refClose = iClose(sym, tf, 1);
if(g_biasBuy)
{
if(!PriceInDiscount(refClose))
return false;
}
else
{
if(!PriceInPremium(refClose))
return false;
}
if(!TriggerPatternOk())
return false;
const double atr = GetAtr(sym, tf, 1);
if(atr <= 0.0)
return false;
double sl = 0.0, tp = 0.0;
string reason = "";
if(!CalcTriggerStops(g_biasBuy, atr, sl, tp, reason))
{
Dbg("Trigger skip: " + reason);
return false;
}
MqlTick tk;
if(!SymbolInfoTick(sym, tk))
return false;
const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
const double entry = NormalizeDouble(g_biasBuy ? tk.ask : tk.bid, dig);
if(g_biasBuy)
{
if(tp <= entry || sl >= entry)
{
Dbg("Trigger skip Buy: TP/SL không hợp lệ");
return false;
}
}
else
{
if(tp >= entry || sl <= entry)
{
Dbg("Trigger skip Sell: TP/SL không hợp lệ");
return false;
}
}
if(!StopsValid(sym, g_biasBuy, entry, sl, tp))
{
Dbg("Trigger skip: STOPS_LEVEL");
return false;
}
double vol = NormalizeVolume(sym, VolumeForRisk(sym, g_biasBuy, entry, sl));
if(vol <= 0.0)
{
Dbg("Trigger skip: volume=0");
return false;
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
SetTradeFilling();
const string cmt = g_biasBuy ? "MyICT_BUY" : "MyICT_SELL";
const bool ok = g_biasBuy
? g_trade.Buy(vol, sym, 0.0, sl, tp, cmt)
: g_trade.Sell(vol, sym, 0.0, sl, tp, cmt);
if(!ok)
{
Print("[MyICT] Trigger fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
return false;
}
g_lastTriggerBar = tBar;
g_state = ICT_STATE_ON_TRADE;
PrintFormat("[MyICT] TRIGGER %s | %s | vol=%.2f entry=%.5f SL=%.5f TP=%.5f",
cmt, reason, vol, entry, sl, tp);
return true;
}
//+------------------------------------------------------------------+
string TrendText(const ENUM_ICT_TREND t)
{
switch(t)
{
case ICT_TREND_UP: return "UP (HH+HL)";
case ICT_TREND_DOWN: return "DOWN (LH+LL)";
case ICT_TREND_NEUTRAL: return "NEUTRAL";
default: return "—";
}
}
//+------------------------------------------------------------------+
string StateText(const ENUM_ICT_STATE s)
{
switch(s)
{
case ICT_STATE_NO_TREND: return "NO_TREND";
case ICT_STATE_PULLBACK: return "PULLBACK";
case ICT_STATE_WAITING_TRIGGER: return "WAITING_TRIGGER";
case ICT_STATE_LIMIT_ORDERED: return "LIMIT_ORDERED";
case ICT_STATE_ON_TRADE: return "ON_TRADE";
case ICT_STATE_NONE: return "NONE";
default: return "?";
}
}
//+------------------------------------------------------------------+
color StateColor(const ENUM_ICT_STATE s)
{
switch(s)
{
case ICT_STATE_WAITING_TRIGGER: return clrGold;
case ICT_STATE_LIMIT_ORDERED: return clrOrange;
case ICT_STATE_ON_TRADE: return clrLime;
case ICT_STATE_PULLBACK: return clrAqua;
case ICT_STATE_NONE: return clrGray;
default: return InpClrNeutral;
}
}
//+------------------------------------------------------------------+
bool IsSwingHigh(const string sym, const ENUM_TIMEFRAMES tf,
const int shift, const int swingRange)
{
const int str = MathMax(1, swingRange);
const double h = iHigh(sym, tf, shift);
for(int k = 1; k <= str; k++)
{
if(shift + k >= Bars(sym, tf) || shift - k < 0)
return false;
if(h <= iHigh(sym, tf, shift + k) || h <= iHigh(sym, tf, shift - k))
return false;
}
return true;
}
//+------------------------------------------------------------------+
bool IsSwingLow(const string sym, const ENUM_TIMEFRAMES tf,
const int shift, const int swingRange)
{
const int str = MathMax(1, swingRange);
const double l = iLow(sym, tf, shift);
for(int k = 1; k <= str; k++)
{
if(shift + k >= Bars(sym, tf) || shift - k < 0)
return false;
if(l >= iLow(sym, tf, shift + k) || l >= iLow(sym, tf, shift - k))
return false;
}
return true;
}
//+------------------------------------------------------------------+
void SortSwingsByTime(SwingPoint &pts[], const int count)
{
for(int i = 0; i < count - 1; i++)
for(int j = i + 1; j < count; j++)
if(pts[j].time < pts[i].time)
{
const SwingPoint t = pts[i];
pts[i] = pts[j];
pts[j] = t;
}
}
//+------------------------------------------------------------------+
void CollectSwings(const string sym, const ENUM_TIMEFRAMES tf,
const int swingRange, SwingPoint &highs[], SwingPoint &lows[])
{
ArrayResize(highs, 0);
ArrayResize(lows, 0);
const int str = MathMax(1, swingRange);
const int bars = Bars(sym, tf);
const int last = MathMin(InpSwingLookback, bars - str - 1);
if(last < str + 5)
return;
int nH = 0, nL = 0;
for(int sh = last; sh >= 1; sh--)
{
if(IsSwingHigh(sym, tf, sh, swingRange))
{
ArrayResize(highs, nH + 1);
highs[nH].price = iHigh(sym, tf, sh);
highs[nH].time = iTime(sym, tf, sh);
highs[nH].shift = sh;
nH++;
}
if(IsSwingLow(sym, tf, sh, swingRange))
{
ArrayResize(lows, nL + 1);
lows[nL].price = iLow(sym, tf, sh);
lows[nL].time = iTime(sym, tf, sh);
lows[nL].shift = sh;
nL++;
}
}
SortSwingsByTime(highs, nH);
SortSwingsByTime(lows, nL);
}
//+------------------------------------------------------------------+
void PickLastTwo(const SwingPoint &pts[], const int count,
SwingPoint &s0, SwingPoint &s1, bool &has0, bool &has1)
{
has0 = has1 = false;
if(count < 1) return;
s0 = pts[count - 1];
has0 = true;
if(count < 2) return;
s1 = pts[count - 2];
has1 = true;
}
//+------------------------------------------------------------------+
ENUM_ICT_TREND ClassifyTrend(const bool hasH0, const bool hasH1,
const bool hasL0, const bool hasL1,
const SwingPoint &h0, const SwingPoint &h1,
const SwingPoint &l0, const SwingPoint &l1)
{
if(!hasH0 || !hasH1 || !hasL0 || !hasL1)
return ICT_TREND_UNKNOWN;
if(h0.price > h1.price && l0.price > l1.price)
return ICT_TREND_UP;
if(h0.price < h1.price && l0.price < l1.price)
return ICT_TREND_DOWN;
return ICT_TREND_NEUTRAL;
}
//+------------------------------------------------------------------+
bool AnalyzeLayer(const string sym, const ENUM_TIMEFRAMES tf,
const int swingRange, TrendSnapshot &out)
{
ZeroMemory(out);
out.trend = ICT_TREND_UNKNOWN;
SwingPoint highs[], lows[];
CollectSwings(sym, tf, swingRange, highs, lows);
PickLastTwo(highs, ArraySize(highs), out.h0, out.h1, out.hasH0, out.hasH1);
PickLastTwo(lows, ArraySize(lows), out.l0, out.l1, out.hasL0, out.hasL1);
out.trend = ClassifyTrend(out.hasH0, out.hasH1, out.hasL0, out.hasL1,
out.h0, out.h1, out.l0, out.l1);
return true;
}
//+------------------------------------------------------------------+
bool BigTrendIsClear()
{
return (g_big.trend == ICT_TREND_UP || g_big.trend == ICT_TREND_DOWN);
}
//+------------------------------------------------------------------+
bool IsPullbackStructure()
{
if(!BigTrendIsClear())
return false;
if(g_big.trend == ICT_TREND_UP && g_small.trend == ICT_TREND_DOWN)
{
g_biasBuy = true;
return true;
}
if(g_big.trend == ICT_TREND_DOWN && g_small.trend == ICT_TREND_UP)
{
g_biasBuy = false;
return true;
}
return false;
}
//+------------------------------------------------------------------+
void CalcBigWaveZone()
{
ZeroMemory(g_zone);
if(!g_big.hasH0 || !g_big.hasL0)
return;
g_zone.waveHigh = g_big.h0.price;
g_zone.waveLow = g_big.l0.price;
if(g_zone.waveHigh < g_zone.waveLow)
{
const double t = g_zone.waveHigh;
g_zone.waveHigh = g_zone.waveLow;
g_zone.waveLow = t;
}
const double range = g_zone.waveHigh - g_zone.waveLow;
if(range <= 0.0)
return;
const double pct = MathMax(1.0, MathMin(99.0, InpZoneEqPct)) / 100.0;
g_zone.equilibrium = g_zone.waveLow + range * pct;
g_zone.discountTop = g_zone.equilibrium;
g_zone.premiumBottom = g_zone.equilibrium;
g_zone.valid = true;
}
//+------------------------------------------------------------------+
bool PriceInDiscount(const double price)
{
if(!g_zone.valid)
return false;
const double tol = InpZoneTolPoints * _Point;
return (price <= g_zone.discountTop + tol);
}
//+------------------------------------------------------------------+
bool PriceInPremium(const double price)
{
if(!g_zone.valid)
return false;
const double tol = InpZoneTolPoints * _Point;
return (price >= g_zone.premiumBottom - tol);
}
//+------------------------------------------------------------------+
bool HasMyPosition(const string sym)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!PositionSelectByTicket(PositionGetTicket(i)))
continue;
if(PositionGetString(POSITION_SYMBOL) != sym)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
return true;
}
return false;
}
//+------------------------------------------------------------------+
bool HasMyPendingLimit(const string sym)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
const ulong ticket = OrderGetTicket(i);
if(ticket == 0 || !OrderSelect(ticket))
continue;
if(OrderGetString(ORDER_SYMBOL) != sym)
continue;
if((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
continue;
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if(t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT)
return true;
}
return false;
}
//+------------------------------------------------------------------+
void SetTradeFilling()
{
const long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((fm & SYMBOL_FILLING_IOC) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
else if((fm & SYMBOL_FILLING_FOK) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
}
//+------------------------------------------------------------------+
ENUM_ICT_STATE ComputeStructureState(const string sym, const ENUM_TIMEFRAMES tf)
{
if(!BigTrendIsClear())
return ICT_STATE_NO_TREND;
if(!IsPullbackStructure())
return ICT_STATE_NO_TREND;
CalcBigWaveZone();
const double refPrice = iClose(sym, tf, 1);
if(g_biasBuy)
{
if(PriceInDiscount(refPrice))
return ICT_STATE_WAITING_TRIGGER;
return ICT_STATE_PULLBACK;
}
if(PriceInPremium(refPrice))
return ICT_STATE_WAITING_TRIGGER;
return ICT_STATE_PULLBACK;
}
//+------------------------------------------------------------------+
void UpdateStateMachine(const string sym, const ENUM_TIMEFRAMES tf)
{
g_statePrev = g_state;
if(HasMyPosition(sym))
{
g_state = ICT_STATE_ON_TRADE;
return;
}
if(HasMyPendingLimit(sym))
{
g_state = ICT_STATE_LIMIT_ORDERED;
return;
}
if(g_statePrev == ICT_STATE_ON_TRADE || g_statePrev == ICT_STATE_LIMIT_ORDERED)
{
g_state = ICT_STATE_NONE;
Dbg("NONE — lệnh đã đóng (SL/TP hoặc hủy)");
return;
}
if(g_statePrev == ICT_STATE_NONE)
{
// một nến NONE rồi quay lại đánh giá cấu trúc
}
g_state = ComputeStructureState(sym, tf);
if(g_state == ICT_STATE_WAITING_TRIGGER)
TryExecuteTrigger(sym, tf);
if(g_state != g_statePrev && InpDebug)
Dbg(StringFormat("State %s → %s", StateText(g_statePrev), StateText(g_state)));
}
//+------------------------------------------------------------------+
void DrawArrowTag(const string tag, const datetime t, const double price,
const bool isHigh, const string label, const color clr,
const int width)
{
const long ch = ActChart();
const string arr = OBJ_CH_PFX + tag + "_AR";
const string txt = OBJ_CH_PFX + tag + "_TX";
if(ObjectFind(ch, arr) < 0)
ObjectCreate(ch, arr, OBJ_ARROW, 0, t, price);
ObjectMove(ch, arr, 0, t, price);
ObjectSetInteger(ch, arr, OBJPROP_ARROWCODE, isHigh ? 217 : 218);
ObjectSetInteger(ch, arr, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP);
ObjectSetInteger(ch, arr, OBJPROP_COLOR, clr);
ObjectSetInteger(ch, arr, OBJPROP_WIDTH, width);
ObjectSetInteger(ch, arr, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, arr, OBJPROP_HIDDEN, true);
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
const double yOff = pt * (isHigh ? (width > 1 ? 150 : 90) : (width > 1 ? -150 : -90));
if(ObjectFind(ch, txt) < 0)
ObjectCreate(ch, txt, OBJ_TEXT, 0, t, NormalizeDouble(price + yOff, dig));
ObjectMove(ch, txt, 0, t, NormalizeDouble(price + yOff, dig));
ObjectSetString(ch, txt, OBJPROP_TEXT, label);
ObjectSetInteger(ch, txt, OBJPROP_COLOR, clr);
ObjectSetInteger(ch, txt, OBJPROP_FONTSIZE, InpFontSize);
ObjectSetString(ch, txt, OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(ch, txt, OBJPROP_ANCHOR, isHigh ? ANCHOR_LOWER : ANCHOR_UPPER);
ObjectSetInteger(ch, txt, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, txt, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
void DrawSegLine(const string name, const datetime t1, const double p1,
const datetime t2, const double p2, const color clr, const int width)
{
const long ch = ActChart();
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(ObjectFind(ch, name) < 0)
ObjectCreate(ch, name, OBJ_TREND, 0, t1, p1, t2, p2);
ObjectMove(ch, name, 0, t1, NormalizeDouble(p1, dig));
ObjectMove(ch, name, 1, t2, NormalizeDouble(p2, dig));
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
ObjectSetInteger(ch, name, OBJPROP_STYLE, width > 1 ? STYLE_SOLID : STYLE_DOT);
ObjectSetInteger(ch, name, OBJPROP_WIDTH, width);
ObjectSetInteger(ch, name, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
void DrawZoneRect(const string name, const datetime t1, const datetime t2,
const double pTop, const double pBot, const color clr,
const uchar fillAlpha, const string tip)
{
const long ch = ActChart();
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double y1 = NormalizeDouble(MathMax(pTop, pBot), dig);
const double y2 = NormalizeDouble(MathMin(pTop, pBot), dig);
if(ObjectFind(ch, name) < 0)
ObjectCreate(ch, name, OBJ_RECTANGLE, 0, t1, y1, t2, y2);
ObjectMove(ch, name, 0, t1, y1);
ObjectMove(ch, name, 1, t2, y2);
ObjectSetInteger(ch, name, OBJPROP_COLOR, WithAlpha(clr, fillAlpha));
ObjectSetInteger(ch, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(ch, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(ch, name, OBJPROP_FILL, true);
ObjectSetInteger(ch, name, OBJPROP_BACK, true);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
ObjectSetString(ch, name, OBJPROP_TOOLTIP, tip);
}
//+------------------------------------------------------------------+
void DrawZoneTag(const string name, const datetime t, const double price,
const string text, const color clr)
{
const long ch = ActChart();
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(ObjectFind(ch, name) < 0)
ObjectCreate(ch, name, OBJ_TEXT, 0, t, NormalizeDouble(price, dig));
ObjectMove(ch, name, 0, t, NormalizeDouble(price, dig));
ObjectSetString(ch, name, OBJPROP_TEXT, text);
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpFontSize - 1));
ObjectSetString(ch, name, OBJPROP_FONT, "Arial");
ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
}
//+------------------------------------------------------------------+
void DrawLayer(const string tag, const TrendSnapshot &snap,
const color clrHi, const color clrLo, const int lineWidth)
{
if(!InpDrawSwings)
return;
if(snap.hasH0)
DrawArrowTag(tag + "_H0", snap.h0.time, snap.h0.price, true, tag + " H0", clrHi, lineWidth);
if(snap.hasH1)
DrawArrowTag(tag + "_H1", snap.h1.time, snap.h1.price, true, tag + " H1", clrHi, lineWidth);
if(snap.hasL0)
DrawArrowTag(tag + "_L0", snap.l0.time, snap.l0.price, false, tag + " L0", clrLo, lineWidth);
if(snap.hasL1)
DrawArrowTag(tag + "_L1", snap.l1.time, snap.l1.price, false, tag + " L1", clrLo, lineWidth);
if(!InpDrawTrendLines)
return;
if(snap.hasH0 && snap.hasH1)
DrawSegLine(OBJ_CH_PFX + tag + "_LNH", snap.h1.time, snap.h1.price,
snap.h0.time, snap.h0.price, clrHi, lineWidth);
if(snap.hasL0 && snap.hasL1)
DrawSegLine(OBJ_CH_PFX + tag + "_LNL", snap.l1.time, snap.l1.price,
snap.l0.time, snap.l0.price, clrLo, lineWidth);
}
//+------------------------------------------------------------------+
void DrawZones(const string sym, const ENUM_TIMEFRAMES tf)
{
if(!InpDrawZones || !g_zone.valid)
return;
const datetime t2 = iTime(sym, tf, 0);
const datetime t1 = iTime(sym, tf, MathMin(60, Bars(sym, tf) - 1));
if(t1 == 0 || t2 == 0)
return;
const bool activePullback = (g_state == ICT_STATE_PULLBACK
|| g_state == ICT_STATE_WAITING_TRIGGER);
const uchar discAlpha = (activePullback && g_biasBuy)
? InpZoneActiveAlpha : InpZoneFillAlpha;
const uchar premAlpha = (activePullback && !g_biasBuy)
? InpZoneActiveAlpha : InpZoneFillAlpha;
DrawZoneRect(OBJ_CH_PFX + "Z_DISC", t1, t2,
g_zone.discountTop, g_zone.waveLow,
InpClrDiscount, discAlpha,
"Discount — nửa dưới sóng Big (Buy)");
DrawZoneRect(OBJ_CH_PFX + "Z_PREM", t1, t2,
g_zone.waveHigh, g_zone.premiumBottom,
InpClrPremium, premAlpha,
"Premium — nửa trên sóng Big (Sell)");
DrawSegLine(OBJ_CH_PFX + "Z_EQ", t1, g_zone.equilibrium, t2, g_zone.equilibrium,
clrGold, 1);
ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(ActChart(), OBJ_CH_PFX + "Z_EQ", OBJPROP_WIDTH, 2);
const double midDisc = (g_zone.waveLow + g_zone.discountTop) * 0.5;
const double midPrem = (g_zone.premiumBottom + g_zone.waveHigh) * 0.5;
DrawZoneTag(OBJ_CH_PFX + "LBL_DISC", t2, midDisc, " DISCOUNT ", InpClrZoneLabel);
DrawZoneTag(OBJ_CH_PFX + "LBL_PREM", t2, midPrem, " PREMIUM ", InpClrZoneLabel);
}
//+------------------------------------------------------------------+
string LayerBlock(const string title, const int swingRange, const TrendSnapshot &snap)
{
string s = StringFormat("%s (sw %d): %s", title, swingRange, TrendText(snap.trend));
if(snap.hasH0 && snap.hasH1 && snap.hasL0 && snap.hasL1)
s += StringFormat("\n H0=%.2f H1=%.2f L0=%.2f L1=%.2f",
snap.h0.price, snap.h1.price, snap.l0.price, snap.l1.price);
return s;
}
//+------------------------------------------------------------------+
string StateBlock()
{
string s = StringFormat("STATE: %s", StateText(g_state));
if(g_state == ICT_STATE_PULLBACK || g_state == ICT_STATE_WAITING_TRIGGER)
s += StringFormat(" | Bias %s", g_biasBuy ? "BUY" : "SELL");
if(g_zone.valid)
s += StringFormat("\n Eq=%.2f (%.0f%%) | %s",
g_zone.equilibrium, InpZoneEqPct,
g_biasBuy ? "Discount" : "Premium");
return s;
}
//+------------------------------------------------------------------+
void DrawPanel(const string body)
{
const long ch = ActChart();
const string name = OBJ_CH_PFX + "PANEL";
if(ObjectFind(ch, name) < 0)
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelX);
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, InpPanelY);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(8, InpFontSize + 1));
ObjectSetString(ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_COLOR, StateColor(g_state));
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetString(ch, name, OBJPROP_TEXT, body);
}
//+------------------------------------------------------------------+
void StatsEnsureLabel(const long ch, const string name, const int yDist)
{
if(ObjectFind(ch, name) < 0)
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpStatCornerX);
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, MathMax(0, yDist));
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpStatFontSize));
ObjectSetString(ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_COLOR, InpStatColor);
}
//+------------------------------------------------------------------+
void StatsUpdate()
{
if(!InpShowStats)
{
ObjectDelete(ActChart(), STAT_LINE1);
ObjectDelete(ActChart(), STAT_LINE2);
return;
}
const long ch = ActChart();
const int gap = (int)(MathMax(7, InpStatFontSize) * 2.2) + 12;
StatsEnsureLabel(ch, STAT_LINE1, InpStatCornerY);
StatsEnsureLabel(ch, STAT_LINE2, InpStatCornerY + gap);
const long closed = g_statWins + g_statLoss;
const double wr = (closed > 0) ? 100.0 * (double)g_statWins / (double)closed : 0.0;
const string l1 = StringFormat("MyICT | 1R=%.1f%% | SL/TP ATR×", InpRiskPercent);
string l2;
if(closed > 0)
l2 = StringFormat("Đóng %I64d | Thắng %I64d | Thua %I64d | Winrate %.1f%%",
g_statTotal, g_statWins, g_statLoss, wr);
else
l2 = StringFormat("Đóng %I64d | Thắng %I64d | Thua %I64d | Winrate: —",
g_statTotal, g_statWins, g_statLoss);
ObjectSetString(ch, STAT_LINE1, OBJPROP_TEXT, l1);
ObjectSetString(ch, STAT_LINE2, OBJPROP_TEXT, l2);
}
//+------------------------------------------------------------------+
void StatsCountExitDeal(const ulong dealTicket)
{
if(dealTicket == 0 || !HistoryDealSelect(dealTicket))
return;
if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol)
return;
if((ulong)HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != InpMagic)
return;
if(HistoryDealGetInteger(dealTicket, DEAL_ENTRY) != DEAL_ENTRY_OUT)
return;
g_statTotal++;
const ENUM_DEAL_REASON dr = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
if(dr == DEAL_REASON_TP)
g_statWins++;
else if(dr == DEAL_REASON_SL)
g_statLoss++;
else
{
const double net = HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP)
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
if(net > 0.0) g_statWins++;
else if(net < 0.0) g_statLoss++;
}
}
//+------------------------------------------------------------------+
void StatsRebuildFromHistory()
{
g_statWins = g_statLoss = g_statTotal = 0;
if(!HistorySelect(0, TimeCurrent()))
return;
const int n = HistoryDealsTotal();
for(int i = 0; i < n; i++)
StatsCountExitDeal(HistoryDealGetTicket(i));
}
//+------------------------------------------------------------------+
void RedrawChart(const string sym, const ENUM_TIMEFRAMES tf)
{
DeleteObjectsByPrefix(OBJ_CH_PFX);
string panel = StringFormat("MyICT | %s %s\n", sym, EnumToString(tf));
panel += StateBlock() + "\n";
if(g_state == ICT_STATE_WAITING_TRIGGER && TriggerPatternOk())
panel += " >> Trigger pattern OK (2L/2H)\n";
panel += LayerBlock("BigTrend", InpBigSwingRange, g_big) + "\n";
panel += LayerBlock("SmallTrend", InpSmallSwingRange, g_small);
DrawPanel(panel);
DrawLayer("BIG", g_big, InpClrBigHigh, InpClrBigLow, 2);
DrawLayer("SML", g_small, InpClrSmallHigh, InpClrSmallLow, 1);
DrawZones(sym, tf);
StatsUpdate();
ChartRedraw(ActChart());
}
//+------------------------------------------------------------------+
void OnNewBar(const string sym)
{
const ENUM_TIMEFRAMES tf = ChartTf();
AnalyzeLayer(sym, tf, InpBigSwingRange, g_big);
AnalyzeLayer(sym, tf, InpSmallSwingRange, g_small);
if(g_big.hasH0 && g_big.hasL0)
CalcBigWaveZone();
UpdateStateMachine(sym, tf);
RedrawChart(sym, tf);
Comment(StringFormat("MyICT | %s\n%s\nBig: %s | Small: %s",
StateText(g_state),
g_biasBuy && (g_state == ICT_STATE_PULLBACK || g_state == ICT_STATE_WAITING_TRIGGER)
? "Bias BUY" : (!g_biasBuy && IsPullbackStructure() ? "Bias SELL" : ""),
TrendText(g_big.trend),
TrendText(g_small.trend)));
}
//+------------------------------------------------------------------+
int OnInit()
{
g_lastBar = 0;
g_lastTriggerBar = 0;
g_state = ICT_STATE_NO_TREND;
g_statePrev = ICT_STATE_NO_TREND;
g_hAtr = iATR(_Symbol, ChartTf(), InpATRPeriod);
if(g_hAtr == INVALID_HANDLE)
{
Print("[MyICT] Không tạo ATR");
return INIT_FAILED;
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
SetTradeFilling();
StatsRebuildFromHistory();
OnNewBar(_Symbol);
Print("[MyICT] Init state=", StateText(g_state));
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
DeleteObjectsByPrefix(OBJ_CH_PFX);
ObjectDelete(ActChart(), STAT_LINE1);
ObjectDelete(ActChart(), STAT_LINE2);
if(g_hAtr != INVALID_HANDLE)
IndicatorRelease(g_hAtr);
Comment("");
}
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
const ulong deal = trans.deal;
if(deal == 0 || !HistoryDealSelect(deal))
return;
if(HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol)
return;
if((ulong)HistoryDealGetInteger(deal, DEAL_MAGIC) != InpMagic)
return;
const long entry = HistoryDealGetInteger(deal, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT)
return;
StatsCountExitDeal(deal);
g_state = ICT_STATE_NONE;
Dbg("OnTradeTransaction → NONE (đóng lệnh)");
RedrawChart(_Symbol, ChartTf());
}
//+------------------------------------------------------------------+
void OnTick()
{
const string sym = _Symbol;
const ENUM_TIMEFRAMES tf = ChartTf();
const datetime t0 = iTime(sym, tf, 0);
if(t0 == 0 || t0 == g_lastBar)
return;
g_lastBar = t0;
OnNewBar(sym);
}
//+------------------------------------------------------------------+
ENUM_ICT_STATE GetIctState() { return g_state; }
ENUM_ICT_TREND GetBigTrend() { return g_big.trend; }
ENUM_ICT_TREND GetSmallTrend() { return g_small.trend; }