Files
MQL5Data/Experts/XZoneEa.mq5
T

526 lines
16 KiB
Plaintext

//+------------------------------------------------------------------+
//| XRR_Pullback.mq5 |
//| Author: Hadi (for XAUUSD,M15) |
//+------------------------------------------------------------------+
#property strict
#property version "0.1"
#property description "M15 pullback EA: Major zone (HH/LL), minor same-direction zone, pullback entry, SL under minor, TP at 2R, 1% risk."
//------------------------------- Inputs -----------------------------
input string Inp_Symbol = "XAUUSD";
input ENUM_TIMEFRAMES Inp_WorkTF = PERIOD_M15;
// Zone detection params
input int Inp_MajorLookbackBars = 400; // bars to scan for HH/LL major zone
input int Inp_MinorWindowBars = 80; // window to find first same-direction minor zone post-major
input int Inp_ZonePadPoints = 50; // padding around zones (points)
// Confirmation / filters
input bool Inp_UseHTFConfluence = true;
input ENUM_TIMEFRAMES Inp_HTF = PERIOD_H1; // confluence timeframe
input int Inp_HTFShiftBars = 3; // HTF confirmation window
input bool Inp_UseEngulfingConfirm = true;
input bool Inp_UseRSIDivergence = false;
input int Inp_RSI_Period = 14;
input bool Inp_UseVolumeSpike = false;
input double Inp_VolSpikeFactor = 1.5; // vs 20-bar average
// Risk management
input double Inp_RiskPercent = 1.0; // 1% per trade
input bool Inp_UseATRforSL = false;
input int Inp_ATR_Period = 14;
input double Inp_ATR_Mult = 1.5;
input double Inp_MinSLPoints = 200; // floor SL (points)
input double Inp_R_MultipleTP = 2.0; // fixed R:R=1:2
// Risk-free (optional)
input bool Inp_MoveToBEatTP1 = true;
input double Inp_TP1_R = 1.0; // move to BE at 1R
input double Inp_BE_OffsetPoints = 10; // lock-in small +offset
// Sessions
input bool Inp_UseSessionFilter = true;
input int Inp_LondonOpenHour = 8; // broker time
input int Inp_LondonCloseHour = 17;
input int Inp_NYOpenHour = 13;
input int Inp_NYCloseHour = 21;
input bool Inp_AllowNYOverlapOnly = false;
// Entry placement
input bool Inp_UseLimitAtZone = true; // limit order at minor zone
input int Inp_MaxSlippagePoints = 100;
input int Inp_MinRetestWickPoints = 30;
// Safety / engine
input int Inp_MinBarsLoaded = 600;
input int Inp_SignalCoolOffBars = 20; // avoid rapid re-entries
input bool Inp_OnePositionOnly = true;
//------------------------------- Structures -------------------------
struct Zone
{
bool isValid;
double priceLow;
double priceHigh;
int barIndex; // starting bar index of zone
bool isSupply; // true: resistance (HH), false: support (LL)
};
struct MinorZone
{
bool isValid;
double priceLow;
double priceHigh;
int barIndex;
bool isSupply; // same direction as major
};
datetime lastSignalTimeBuy = 0;
datetime lastSignalTimeSell = 0;
//
int atrHandler = INVALID_HANDLE;
//------------------------------- Helpers ----------------------------
int DigitsAdjust()
{
return (int)SymbolInfoInteger(Inp_Symbol, SYMBOL_DIGITS);
}
double PointAdjust()
{
return SymbolInfoDouble(Inp_Symbol, SYMBOL_POINT);
}
double TickValue()
{
double v;
SymbolInfoDouble(Inp_Symbol, SYMBOL_TRADE_TICK_VALUE, v);
return v;
}
double TickSize()
{
double s;
SymbolInfoDouble(Inp_Symbol, SYMBOL_TRADE_TICK_SIZE, s);
return s;
}
bool IsSessionAllowed()
{
if (!Inp_UseSessionFilter)
return true;
MqlDateTime t;
TimeToStruct(TimeCurrent(), t);
int hour = t.hour;
bool london = (hour >= Inp_LondonOpenHour && hour < Inp_LondonCloseHour);
bool ny = (hour >= Inp_NYOpenHour && hour < Inp_NYCloseHour);
if (Inp_AllowNYOverlapOnly)
return (london && ny);
return (london || ny);
}
bool EngulfingBearish(int shift)
{
double o0 = iOpen(Inp_Symbol, Inp_WorkTF, shift), c0 = iClose(Inp_Symbol, Inp_WorkTF, shift);
double o1 = iOpen(Inp_Symbol, Inp_WorkTF, shift + 1), c1 = iClose(Inp_Symbol, Inp_WorkTF, shift + 1);
return (c0 < o0 && c1 > o1 && o0 < c1 && c0 < o1 && (o0 - o1) > 0 && (c1 - c0) > 0 && (o1 < c0)); // rough engulfing
}
bool EngulfingBullish(int shift)
{
double o0 = iOpen(Inp_Symbol, Inp_WorkTF, shift), c0 = iClose(Inp_Symbol, Inp_WorkTF, shift);
double o1 = iOpen(Inp_Symbol, Inp_WorkTF, shift + 1), c1 = iClose(Inp_Symbol, Inp_WorkTF, shift + 1);
return (c0 > o0 && c1 < o1 && o0 > c1 && c0 > o1 && (o0 - o1) > 0 && (c0 - c1) > 0 && (o1 > c0)); // rough engulfing inverse
}
bool VolumeSpike(int shift)
{
if (!Inp_UseVolumeSpike)
return true;
double avg = 0;
int n = 20;
for (int i = shift + 1; i < shift + 1 + n; i++)
avg += (double)iVolume(Inp_Symbol, Inp_WorkTF, i);
avg /= n;
double v0 = (double)iVolume(Inp_Symbol, Inp_WorkTF, shift);
return (v0 >= avg * Inp_VolSpikeFactor);
}
bool HTFConfluence(bool wantSupply)
{
if (!Inp_UseHTFConfluence)
return true;
// Simple HTF direction: last HTF candle close vs open
double o = iOpen(Inp_Symbol, Inp_HTF, Inp_HTFShiftBars), c = iClose(Inp_Symbol, Inp_HTF, Inp_HTFShiftBars);
bool htfDown = (c < o), htfUp = (c > o);
return wantSupply ? htfDown : htfUp;
}
//------------------------------- Zone detection ---------------------
Zone FindMajorZone()
{
Zone z;
z.isValid = false;
z.isSupply = false;
z.priceLow = 0;
z.priceHigh = 0;
z.barIndex = -1;
int bars = iBars(Inp_Symbol, Inp_WorkTF);
if (bars < Inp_MinBarsLoaded)
return z;
// Highest high / lowest low over lookback
double hh = -DBL_MAX, ll = DBL_MAX;
int hhBar = -1, llBar = -1;
for (int i = 1; i <= Inp_MajorLookbackBars; i++)
{
double h = iHigh(Inp_Symbol, Inp_WorkTF, i), l = iLow(Inp_Symbol, Inp_WorkTF, i);
if (h > hh)
{
hh = h;
hhBar = i;
}
if (l < ll)
{
ll = l;
llBar = i;
}
}
// Decide which one is closest to current action: use most recent extreme
if (hhBar < llBar)
{
z.isSupply = true;
z.isValid = true;
z.barIndex = hhBar;
z.priceLow = hh - Inp_ZonePadPoints * PointAdjust();
z.priceHigh = hh + Inp_ZonePadPoints * PointAdjust();
}
else
{
z.isSupply = false;
z.isValid = true;
z.barIndex = llBar;
z.priceLow = ll - Inp_ZonePadPoints * PointAdjust();
z.priceHigh = ll + Inp_ZonePadPoints * PointAdjust();
}
return z;
}
MinorZone FindFirstMinorSameDirection(const Zone &major)
{
MinorZone mz;
mz.isValid = false;
mz.isSupply = major.isSupply;
mz.priceLow = 0;
mz.priceHigh = 0;
mz.barIndex = -1;
if (!major.isValid)
return mz;
// Scan forward from major's bar toward current for first reaction zone aligned with major
int start = major.barIndex - 1;
int end = MathMax(1, start - Inp_MinorWindowBars);
// Heuristic: supply minor = bearish impulse followed by base; demand minor = bullish impulse followed by base
for (int i = start; i >= end; i--)
{
double o = iOpen(Inp_Symbol, Inp_WorkTF, i), c = iClose(Inp_Symbol, Inp_WorkTF, i);
double h = iHigh(Inp_Symbol, Inp_WorkTF, i), l = iLow(Inp_Symbol, Inp_WorkTF, i);
bool bearImpulse = (c < o && (o - c) > (h - l) * 0.5);
bool bullImpulse = (c > o && (c - o) > (h - l) * 0.5);
if (major.isSupply && bearImpulse)
{
// Base zone from next candle tight range
double h1 = iHigh(Inp_Symbol, Inp_WorkTF, i - 1), l1 = iLow(Inp_Symbol, Inp_WorkTF, i - 1);
mz.isValid = true;
mz.barIndex = i - 1;
mz.priceLow = l1 - Inp_ZonePadPoints * PointAdjust();
mz.priceHigh = h1 + Inp_ZonePadPoints * PointAdjust();
break;
}
if (!major.isSupply && bullImpulse)
{
double h1 = iHigh(Inp_Symbol, Inp_WorkTF, i - 1), l1 = iLow(Inp_Symbol, Inp_WorkTF, i - 1);
mz.isValid = true;
mz.barIndex = i - 1;
mz.priceLow = l1 - Inp_ZonePadPoints * PointAdjust();
mz.priceHigh = h1 + Inp_ZonePadPoints * PointAdjust();
break;
}
}
return mz;
}
//------------------------------- Signal logic -----------------------
bool PriceRetestedZone(const MinorZone &mz, bool supply, int shift = 0)
{
if (!mz.isValid)
return false;
double high = iHigh(Inp_Symbol, Inp_WorkTF, shift), low = iLow(Inp_Symbol, Inp_WorkTF, shift);
// For supply: retest when high touches zone; for demand: retest when low touches zone
if (supply)
return (high >= mz.priceLow && high <= mz.priceHigh && (high - iClose(Inp_Symbol, Inp_WorkTF, shift)) * 1.0 / PointAdjust() >= Inp_MinRetestWickPoints);
else
return (low <= mz.priceHigh && low >= mz.priceLow && (iClose(Inp_Symbol, Inp_WorkTF, shift) - low) * 1.0 / PointAdjust() >= Inp_MinRetestWickPoints);
}
bool ConfirmAtZone(bool supply)
{
int shift = 0;
if (Inp_UseEngulfingConfirm)
{
if (supply)
{
if (!EngulfingBearish(shift))
return false;
}
else
{
if (!EngulfingBullish(shift))
return false;
}
}
if (!VolumeSpike(shift))
return false;
if (!HTFConfluence(supply))
return false;
// Optional: RSI divergence could be added here if enabled.
return true;
}
//------------------------------- Risk & orders -----------------------
double CalcPositionSize(double sl_points)
{
double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (Inp_RiskPercent / 100.0);
double tickValue = TickValue();
double tickSize = TickSize();
// Convert SL points to quoted ticks
double ticks = sl_points * PointAdjust() / tickSize;
if (ticks <= 0.0)
return 0.0;
double lots = riskMoney / (ticks * tickValue);
// Normalize to symbol step
double minLot = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_MIN);
double lotStep = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_STEP);
double maxLot = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_MAX);
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(lots, maxLot));
return lots;
}
bool PlaceEntry(const MinorZone &mz, bool supply)
{
double bid = SymbolInfoDouble(Inp_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(Inp_Symbol, SYMBOL_ASK);
double point = PointAdjust();
// SL beneath/above minor zone (or ATR-based)
double sl, entry, tp;
double sl_points;
if (supply)
{
entry = Inp_UseLimitAtZone ? mz.priceLow : bid; // sell near zone top/low
double baseSL = mz.priceHigh + Inp_ZonePadPoints * point;
if (Inp_UseATRforSL)
{
//
double atrs[];
CopyBuffer(atrHandler, MAIN_LINE, 0, 1, atrs);
double atr = atrs[0];
baseSL = MathMax(baseSL, entry + (atr * Inp_ATR_Mult));
}
sl = baseSL;
sl_points = (sl - entry) / point;
sl_points = MathMax(sl_points, Inp_MinSLPoints);
tp = entry - (sl_points * point * Inp_R_MultipleTP);
// Position sizing
double lots = CalcPositionSize(sl_points);
if (lots <= 0)
return false;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = Inp_Symbol;
req.type = ORDER_TYPE_SELL;
req.volume = lots;
req.price = Inp_UseLimitAtZone ? entry : bid;
req.sl = sl;
req.tp = tp;
req.deviation = Inp_MaxSlippagePoints;
bool ok = OrderSend(req, res);
if (ok)
{
lastSignalTimeSell = TimeCurrent();
}
return ok;
}
else
{
entry = Inp_UseLimitAtZone ? mz.priceHigh : ask; // buy near zone top/high
double baseSL = mz.priceLow - Inp_ZonePadPoints * point;
if (Inp_UseATRforSL)
{
//
double atrs[];
CopyBuffer(atrHandler, MAIN_LINE, 0, 1, atrs);
double atr = atrs[0];
baseSL = MathMin(baseSL, entry - (atr * Inp_ATR_Mult));
}
sl = baseSL;
sl_points = (entry - sl) / point;
sl_points = MathMax(sl_points, Inp_MinSLPoints);
tp = entry + (sl_points * point * Inp_R_MultipleTP);
double lots = CalcPositionSize(sl_points);
if (lots <= 0)
return false;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = Inp_Symbol;
req.type = ORDER_TYPE_BUY;
req.volume = lots;
req.price = Inp_UseLimitAtZone ? entry : ask;
req.sl = sl;
req.tp = tp;
req.deviation = Inp_MaxSlippagePoints;
bool ok = OrderSend(req, res);
if (ok)
{
lastSignalTimeBuy = TimeCurrent();
}
return ok;
}
}
//------------------------------- Position management ----------------
void ManageOpenPositions()
{
int total = PositionsTotal();
double point = PointAdjust();
for (int i = 0; i < total; i++)
{
string sym = PositionGetSymbol(i);
if (sym != Inp_Symbol)
continue;
if (!PositionSelect(sym))
continue;
long type = (long)PositionGetInteger(POSITION_TYPE);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double current = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK);
// Risk-free move to BE at TP1 (= 1R)
if (Inp_MoveToBEatTP1 && tp > 0 && sl > 0)
{
double rDist = MathAbs(tp - price) / Inp_R_MultipleTP;
bool hitTP1 = (type == POSITION_TYPE_BUY ? (current >= price + rDist) : (current <= price - rDist));
if (hitTP1)
{
double newSL = (type == POSITION_TYPE_BUY ? price + Inp_BE_OffsetPoints * point : price - Inp_BE_OffsetPoints * point);
if ((type == POSITION_TYPE_BUY && newSL > sl) || (type == POSITION_TYPE_SELL && newSL < sl))
{
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.symbol = sym;
req.position = (ulong)PositionGetInteger(POSITION_TICKET);
req.sl = newSL;
req.tp = tp;
OrderSend(req, res);
}
}
}
}
}
//------------------------------- Engine ------------------------------
bool CoolOffOk(bool supply)
{
datetime last = supply ? lastSignalTimeSell : lastSignalTimeBuy;
if (last == 0)
return true;
// Convert bars to seconds approximate using last M15 duration
int seconds = Inp_SignalCoolOffBars * 15 * 60;
return (TimeCurrent() - last) >= seconds;
}
void TrySignal()
{
if (!IsSessionAllowed())
return;
Zone major = FindMajorZone();
if (!major.isValid)
return;
MinorZone minor = FindFirstMinorSameDirection(major);
if (!minor.isValid)
return;
// Only act on retest + confirmation
bool retest = PriceRetestedZone(minor, major.isSupply, 0);
if (!retest)
return;
if (!ConfirmAtZone(major.isSupply))
return;
if (Inp_OnePositionOnly && PositionsTotal() > 0)
return;
if (!CoolOffOk(major.isSupply))
return;
PlaceEntry(minor, major.isSupply);
}
//------------------------------- Standard events --------------------
int OnInit()
{
//
if (Symbol() != Inp_Symbol)
{
SymbolSelect(Inp_Symbol, true);
}
//
atrHandler = iATR(Inp_Symbol, Inp_WorkTF, Inp_ATR_Period);
return (INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
}
void OnTick()
{
static datetime lastBarTime = 0;
MqlRates rates[];
int copied = CopyRates(Inp_Symbol, Inp_WorkTF, 0, Inp_MinBarsLoaded, rates);
if (copied < Inp_MinBarsLoaded)
return;
datetime curBarTime = rates[0].time;
if (curBarTime != lastBarTime)
{
// New bar on M15: evaluate signal
TrySignal();
lastBarTime = curBarTime;
}
// Manage open positions continuously
ManageOpenPositions();
}