//+------------------------------------------------------------------+
//|                                      EngulfingPivotReversal.mq5   |
//|                                                          Algobot  |
//|                                    https://www.algobot.live       |
//+------------------------------------------------------------------+
// =====================================================================================
//  EngulfingPivotReversal  (native MQL5 port of the Algobot C# strategy)
//  -------------------------------------------------------------------------------------
//  PURE PRICE ACTION -- NO indicators (no MA / RSI / ATR / Bollinger).
//  Floor PIVOTS (PP/R1/S1/R2/S2) form the grid. A reversal scalp is taken when the
//  just-closed candle ENGULFS the prior one at S1 (long) or R1 (short) and closes back
//  on the right side of the level, aiming at PP. If the level instead FAILS, an opposite
//  BREAKOUT HEDGE is deployed toward the next pivot (S2/R2). Once both legs are live the
//  basket is banked the instant net floating P/L >= BasketTpMoney.
//
//  Levels (from the last PivotLookback CLOSED bars): H = highest high, L = lowest low,
//  C = close of the just-closed bar. PP=(H+L+C)/3 ; R1=2PP-L ; S1=2PP-H ;
//  R2=PP+(H-L) ; S2=PP-(H-L). range=H-L sizes every buffer (no pip hard-coding).
// =====================================================================================
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>
CTrade trade;

//--- inputs (names & defaults mirror the C# DescribeParameters) ---
input int    PivotLookback   = 20;     // Closed bars whose H/L/C define the floor pivots
input double ZoneFrac        = 0.20;   // How close to a pivot the wick must reach (fraction of range)
input double HedgeFrac       = 0.25;   // Distance BEYOND the rejected level that confirms it FAILED
input double StopFrac        = 0.25;   // Pad beyond the engulfing extreme (and hedge trigger) for base SL
input double ReclaimFrac     = 0.30;   // Hedge invalidation pad: level reclaimed by this -> hedge wrong
input double MinRewardFrac   = 0.50;   // Minimum reward distance for any TP (fraction of range)
input double BasketTpMoney   = 30.0;   // Close WHOLE basket once net floating profit reaches this (acct ccy)
input int    MaxSpreadPoints = 50;     // Skip new entries while spread (points) exceeds this (0 = off)
input double Lots            = 0.10;   // Base leg volume
input double HedgeLots       = 0.10;   // Hedge leg volume
input long   Magic           = 7311;   // Magic number

//--- live-trade state (mirrors the C# fields) ---
int      g_dir          = 0;     // 0 flat, +1 long base, -1 short base
bool     g_hedged       = false; // has the opposite hedge leg been deployed?
double   g_level        = 0.0;   // the pivot we engulfed from (S1 long, R1 short)
double   g_hedgeTrigger = 0.0;   // price beyond which the level is deemed to have FAILED
double   g_hedgeTpTarget= 0.0;   // the next pivot the hedge breakout aims at (S2 long, R2 short)
double   g_range        = 0.0;   // pivot range (H - L) captured at entry, sizes hedge buffers
datetime g_lastBar      = 0;     // time of the last processed bar (one entry pass per closed bar)

//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber((ulong)Magic);

    g_dir = 0; g_hedged = false;
    g_level = 0.0; g_hedgeTrigger = 0.0; g_hedgeTpTarget = 0.0; g_range = 0.0;

    // Seed with the current (forming) bar so the first NEW bar triggers entry logic.
    g_lastBar = iTime(_Symbol, _Period, 0);
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // No indicator handles to release -- this is a pure price-action EA.
}

//+------------------------------------------------------------------+
void OnTick()
{
    int posCount = CountMyPositions(Magic);

    // ---- A) While exposure is held: basket profit lock + intrabar hedge trigger ----
    if(posCount > 0)
    {
        // Bank the whole basket once it is sufficiently green (the hedge's bail-to-green).
        if(g_hedged)
        {
            double floating = FloatingProfit(Magic);
            if(floating >= BasketTpMoney)
            {
                PrintFormat("EPR FLATTEN (basket TP %.2f) legs=%d", floating, posCount);
                CloseAll(Magic);
                g_dir = 0; g_hedged = false;
                return;
            }
        }

        // Deploy the hedge the moment the rejected level is convincingly FAILED.
        if(g_dir != 0 && !g_hedged && posCount == 1)
        {
            double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            if(g_dir > 0 && bid <= g_hedgeTrigger)       DeployHedge(false); // base long -> sell hedge
            else if(g_dir < 0 && ask >= g_hedgeTrigger)  DeployHedge(true);  // base short -> buy hedge
        }
    }
    else if(g_dir != 0)
    {
        // Everything closed (base TP/SL, hedge TP/SL, or a flatten) -> reset for next setup.
        g_dir = 0; g_hedged = false;
    }

    // ---- B) Structural / entry logic gated to ONE pass per freshly-closed bar ----
    datetime curBarTime = iTime(_Symbol, _Period, 0);
    if(curBarTime == g_lastBar) return;
    g_lastBar = curBarTime;

    // Only hunt fresh setups when completely flat.
    if(CountMyPositions(Magic) > 0) return;
    TrySeekEntry();
}

//+------------------------------------------------------------------+
//| Seek a fresh engulfing-pivot reversal entry (flat only).         |
//+------------------------------------------------------------------+
void TrySeekEntry()
{
    // Need the full pivot window plus the engulfing pair (shift 1 & 2).
    if(Bars(_Symbol, _Period) < PivotLookback + 2) return;
    if(MaxSpreadPoints > 0 && (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return;

    // --- Floor pivots from the last PivotLookback closed bars (shifts 1..PivotLookback) ---
    double H = -DBL_MAX, L = DBL_MAX;
    for(int s = 1; s <= PivotLookback; s++)
    {
        double hi = iHigh(_Symbol, _Period, s);
        double lo = iLow(_Symbol,  _Period, s);
        if(hi > H) H = hi;
        if(lo < L) L = lo;
    }

    // c = the candle that just closed (shift 1); p = the prior candle it must engulf (shift 2).
    double cClose = iClose(_Symbol, _Period, 1);
    double cOpen  = iOpen(_Symbol,  _Period, 1);
    double cHigh  = iHigh(_Symbol,  _Period, 1);
    double cLow   = iLow(_Symbol,   _Period, 1);
    double pClose = iClose(_Symbol, _Period, 2);
    double pOpen  = iOpen(_Symbol,  _Period, 2);

    double pp    = (H + L + cClose) / 3.0;
    double r1    = 2*pp - L;
    double s1    = 2*pp - H;
    double r2    = pp + (H - L);
    double s2    = pp - (H - L);
    double range = H - L;                          // == R1 - S1
    if(range <= 0) return;

    double point     = _Point;
    int    stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    double tol       = ZoneFrac * range;
    double minDist   = (stopLevel + 1) * point;
    double minRwd    = MathMax(MinRewardFrac * range, minDist);
    double stopPad   = MathMax(StopFrac     * range, minDist);
    double hedgePad  = MathMax(HedgeFrac    * range, minDist);

    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    if(ask <= 0 || bid <= 0) return;
    double cBody = MathAbs(cClose - cOpen);
    double pBody = MathAbs(pClose - pOpen);

    // --- Engulfing geometry (raw price action, no indicators) ---
    // Bullish engulfing: prior bearish, current bullish, current body engulfs prior body.
    bool bullEngulf = pClose < pOpen && cClose > cOpen
                      && cClose >= pOpen && cOpen <= pClose
                      && cBody > pBody;
    // Bearish engulfing: prior bullish, current bearish, current body engulfs prior body.
    bool bearEngulf = pClose > pOpen && cClose < cOpen
                      && cClose <= pOpen && cOpen >= pClose
                      && cBody > pBody;

    // ---- LONG: bullish engulfing that reclaimed support S1 ----
    bool touchedS     = cLow <= s1 + tol && cLow >= s1 - range;   // dipped into the S1 zone
    bool closedAboveS = cClose > s1;                              // snapped back above support
    if(bullEngulf && touchedS && closedAboveS)
    {
        double level     = s1;
        double hedgeTrig = level - hedgePad;                      // support FAILED below here
        double tp        = pp;                                    // mean-reversion scalp toward PP
        if(tp < ask + minRwd) tp = ask + minRwd;
        // Base SL sits beyond BOTH the engulfing low and the hedge trigger -> hedge fires first.
        double sl = MathMin(cLow - stopPad, hedgeTrig - minDist);
        if(sl > bid - minDist) sl = bid - minDist;
        OpenBase(true, sl, tp, level, hedgeTrig, s2, range, "S1 bullish engulfing");
        return;
    }

    // ---- SHORT: bearish engulfing that rejected resistance R1 ----
    bool touchedR     = cHigh >= r1 - tol && cHigh <= r1 + range; // poked into the R1 zone
    bool closedBelowR = cClose < r1;                              // snapped back below resistance
    if(bearEngulf && touchedR && closedBelowR)
    {
        double level     = r1;
        double hedgeTrig = level + hedgePad;                      // resistance FAILED above here
        double tp        = pp;
        if(tp > bid - minRwd) tp = bid - minRwd;
        double sl = MathMax(cHigh + stopPad, hedgeTrig + minDist);
        if(sl < ask + minDist) sl = ask + minDist;
        OpenBase(false, sl, tp, level, hedgeTrig, r2, range, "R1 bearish engulfing");
        return;
    }
}

//+------------------------------------------------------------------+
//| Open the base reversal leg and arm the hedge state.              |
//+------------------------------------------------------------------+
void OpenBase(bool buy, double sl, double tp,
              double level, double hedgeTrig, double hedgeTpTarget, double range, string why)
{
    double vol = NormalizeVolume(Lots);
    if(vol <= 0) return;

    double entry = buy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                       : SymbolInfoDouble(_Symbol, SYMBOL_BID);

    bool ok = buy ? trade.Buy (vol, _Symbol, 0.0, Norm(sl), Norm(tp), "EPR " + why)
                  : trade.Sell(vol, _Symbol, 0.0, Norm(sl), Norm(tp), "EPR " + why);

    if(ok)
    {
        g_dir           = buy ? 1 : -1;
        g_hedged        = false;
        g_level         = level;
        g_hedgeTrigger  = hedgeTrig;
        g_hedgeTpTarget = hedgeTpTarget;
        g_range         = range;
    }
    PrintFormat("EPR %s (%s) @ %.5f SL %.5f TP %.5f level %.5f hedge@ %.5f -> %s",
                (buy ? "BUY" : "SELL"), why, entry, sl, tp, level, hedgeTrig,
                (ok ? "OK" : "FAIL"));
}

//+------------------------------------------------------------------+
//| The rejected level FAILED -> lock an opposite breakout leg.      |
//| buy==false : base was long, support failed -> ride down to S2.   |
//| buy==true  : base was short, resistance failed -> ride up to R2. |
//+------------------------------------------------------------------+
void DeployHedge(bool buy)
{
    double vol = NormalizeVolume(HedgeLots);
    if(vol <= 0) return;

    double point     = _Point;
    int    stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    double minDist   = (stopLevel + 1) * point;
    double reclaim   = MathMax(ReclaimFrac   * g_range, minDist);
    double minRwd    = MathMax(MinRewardFrac * g_range, minDist);

    double entry, sl, tp;
    bool   ok;

    if(!buy)   // base was long; support failed -> ride the breakdown to S2
    {
        entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        sl    = g_level + reclaim;                            // breakdown invalid if level reclaimed
        tp    = g_hedgeTpTarget;                              // next pivot down
        if(sl < entry + minDist) sl = entry + minDist;
        if(tp > entry - minRwd)  tp = entry - minRwd;         // guarantee a worthwhile target
        ok = trade.Sell(vol, _Symbol, 0.0, Norm(sl), Norm(tp), "EPR hedge");
    }
    else        // base was short; resistance failed -> ride the break-up to R2
    {
        entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        sl    = g_level - reclaim;
        tp    = g_hedgeTpTarget;                              // next pivot up
        if(sl > entry - minDist) sl = entry - minDist;
        if(tp < entry + minRwd)  tp = entry + minRwd;
        ok = trade.Buy(vol, _Symbol, 0.0, Norm(sl), Norm(tp), "EPR hedge");
    }

    if(ok) g_hedged = true;
    PrintFormat("EPR HEDGE %s @ %.5f SL %.5f TP %.5f (level %.5f failed) -> %s",
                (buy ? "BUY" : "SELL"), entry, sl, tp, g_level, (ok ? "OK" : "FAIL"));
}

//+------------------------------------------------------------------+
//| Count this EA's open positions on the current symbol.            |
//+------------------------------------------------------------------+
int CountMyPositions(long magic)
{
    int n = 0;
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic) n++;
    }
    return n;
}

//+------------------------------------------------------------------+
//| Net floating P/L (profit + swap) of this EA's positions.         |
//+------------------------------------------------------------------+
double FloatingProfit(long magic)
{
    double f = 0.0;
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
            f += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
    }
    return f;
}

//+------------------------------------------------------------------+
//| Close every position belonging to this EA on the current symbol. |
//+------------------------------------------------------------------+
void CloseAll(long magic)
{
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic) trade.PositionClose(t);
    }
}

//+------------------------------------------------------------------+
//| Snap a requested volume to the symbol's step and min/max limits. |
//+------------------------------------------------------------------+
double NormalizeVolume(double lots)
{
    double vstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(vstep <= 0) vstep = 0.01;
    double v = MathRound(lots / vstep) * vstep;
    v = NormalizeDouble(v, 2);
    double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    if(v < vmin) v = vmin;
    if(v > vmax) v = vmax;
    return v;
}

//+------------------------------------------------------------------+
double Norm(double price) { return NormalizeDouble(price, _Digits); }
//+------------------------------------------------------------------+
