1167 lines
38 KiB
Plaintext
1167 lines
38 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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; }
|