From e36d1c3681c7401b97467258a22f3b40da428074 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Mon, 29 Dec 2025 10:53:34 +0330 Subject: [PATCH] add XZone EA fromCopilot AI ... --- Experts/XZoneEa.mq5 | 525 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 525 insertions(+) create mode 100644 Experts/XZoneEa.mq5 diff --git a/Experts/XZoneEa.mq5 b/Experts/XZoneEa.mq5 new file mode 100644 index 00000000..cbf151b7 --- /dev/null +++ b/Experts/XZoneEa.mq5 @@ -0,0 +1,525 @@ +//+------------------------------------------------------------------+ +//| 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(); +}