//+------------------------------------------------------------------+
//|                                          PivotReboundHedge.mq5    |
//|                                                          Algobot  |
//|                                       https://www.algobot.live    |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

// PivotReboundHedge
// =================
// Pure price-action reversal scalper with a built-in breakout hedge.
// NO indicators of any kind. Every decision is made from raw candle geometry
// and classic floor-pivot levels rebuilt every RefPeriod bars.
//
//  1) Pivots (rebuilt every RefPeriod bars from the just-closed window):
//        P  = (H + L + C) / 3
//        R1 = 2P - L      S1 = 2P - H
//        R2 = P + (H-L)   S2 = P - (H-L)
//  2) Entry: engulfing rebound that pierces a pivot with the wick but closes
//     the body back across it (support reclaim -> long, resistance -> short).
//  3) Hedge: if a reclaimed pivot fails by HedgeBreakFraction of the ref range,
//     fire an opposite-side hedge (HedgeMultiplier x Lots). One hedge per leg.

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

//--- inputs (mirror DescribeParameters) ---
input int    RefPeriod          = 24;     // bars per pivot reference window
input double RewardRatio        = 1.5;    // TP as a multiple of structural risk
input double StopBufferFraction = 0.30;   // SL buffer beyond pierce wick (frac of candle range)
input double MinBodyRatio       = 1.00;   // signal body >= this multiple of prior body
input double HedgeBreakFraction = 0.25;   // breakout distance beyond failed pivot (frac of ref range)
input double HedgeMultiplier    = 1.50;   // hedge lot multiple vs Lots (0 disables hedging)
input double Lots               = 0.10;   // base volume
input long   Magic              = 7301;   // magic number

//--- pivot state ---
double g_levels[5];        // {S2, S1, P, R1, R2}
bool   g_hasPivots   = false;
double g_refRange    = 0.0;
int    g_barsSinceSnap = 0;
int    g_barsAdded   = 0;  // closed bars seen since init (mirrors _bars.Count)

//--- per-position tracking (mirrors _triggerLevel + _hedged dictionaries) ---
ulong  g_trkTicket[];      // original RB leg tickets
double g_trkLevel[];       // pivot level each RB leg reclaimed
bool   g_trkHedged[];      // whether that leg has already spawned its hedge

//+------------------------------------------------------------------+
//| Init                                                             |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    trade.SetTypeFillingBySymbol(_Symbol);

    g_hasPivots     = false;
    g_refRange      = 0.0;
    g_barsSinceSnap = 0;
    g_barsAdded     = 0;
    ArrayInitialize(g_levels, 0.0);
    ArrayResize(g_trkTicket, 0);
    ArrayResize(g_trkLevel,  0);
    ArrayResize(g_trkHedged, 0);
    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) { }

//+------------------------------------------------------------------+
//| Tick                                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // --- Reactive layer (every tick): keep maps tidy and watch for breakouts.
    PruneMaps();
    ManageHedges();

    // --- Bar layer: act once per closed bar.
    if(!IsNewBar()) return;
    if(Bars(_Symbol, _Period) < 3) return;

    g_barsAdded++;                  // mirrors _bars.Add(closed)
    g_barsSinceSnap++;
    if(g_barsAdded >= RefPeriod && (!g_hasPivots || g_barsSinceSnap >= RefPeriod))
        Recompute();

    TryEnter();
}

//+------------------------------------------------------------------+
//| New-bar detector                                                 |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| Pivot construction (window = closed shifts 1..RefPeriod)         |
//+------------------------------------------------------------------+
void Recompute()
{
    double hi = -DBL_MAX, lo = DBL_MAX;
    for(int s = 1; s <= RefPeriod; s++)
    {
        double h = iHigh(_Symbol, _Period, s);
        double l = iLow(_Symbol,  _Period, s);
        if(h > hi) hi = h;
        if(l < lo) lo = l;
    }
    double cl    = iClose(_Symbol, _Period, 1);   // close of the just-closed bar
    double range = hi - lo;
    if(range <= 0.0) return;

    double p = (hi + lo + cl) / 3.0;
    g_levels[0] = p - range;     // S2
    g_levels[1] = 2 * p - hi;    // S1
    g_levels[2] = p;             // P
    g_levels[3] = 2 * p - lo;    // R1
    g_levels[4] = p + range;     // R2

    g_refRange      = range;
    g_hasPivots     = true;
    g_barsSinceSnap = 0;
}

//+------------------------------------------------------------------+
//| Entries: engulfing rebound off a pivot                           |
//+------------------------------------------------------------------+
void TryEnter()
{
    if(!g_hasPivots || g_refRange <= 0.0) return;

    double curHigh  = iHigh(_Symbol,  _Period, 1);
    double curLow   = iLow(_Symbol,   _Period, 1);
    double curOpen  = iOpen(_Symbol,  _Period, 1);
    double curClose = iClose(_Symbol, _Period, 1);

    double range = curHigh - curLow;
    if(range <= 0.0 || g_barsAdded < 2) return;

    double prevOpen  = iOpen(_Symbol,  _Period, 2);
    double prevClose = iClose(_Symbol, _Period, 2);

    double curBody  = MathAbs(curClose - curOpen);
    double prevBody = MathAbs(prevClose - prevOpen);
    bool bodyOk = (prevBody <= 0.0) ? (curBody > 0.0) : (curBody >= MinBodyRatio * prevBody);
    if(!bodyOk) return;

    // LONG: bullish engulfing reclaiming a pivot as support.
    bool bullEngulf = curClose > curOpen && prevClose < prevOpen &&
                      curClose >= prevOpen && curOpen <= prevClose;
    if(bullEngulf && !HasOpen(POSITION_TYPE_BUY, "RB"))
    {
        double level;
        if(NearestSupportReclaim(curLow, curClose, level))   // highest pivot wick pierced & body reclaimed
        {
            double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double sl    = MathMin(curLow, level) - StopBufferFraction * range;
            FinalizeLong(entry, sl, Lots, "RB-L", level, false);
            return;
        }
    }

    // SHORT: bearish engulfing reclaiming a pivot as resistance.
    bool bearEngulf = curClose < curOpen && prevClose > prevOpen &&
                      curClose <= prevOpen && curOpen >= prevClose;
    if(bearEngulf && !HasOpen(POSITION_TYPE_SELL, "RB"))
    {
        double level;
        if(NearestResistanceReclaim(curHigh, curClose, level))  // lowest pivot wick pierced & body closed below
        {
            double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double sl    = MathMax(curHigh, level) + StopBufferFraction * range;
            FinalizeShort(entry, sl, Lots, "RB-S", level, false);
            return;
        }
    }
}

// Highest pivot whose level the lower wick pierced and the body reclaimed.
bool NearestSupportReclaim(double low, double close, double &outLevel)
{
    bool found = false; double best = 0.0;
    for(int i = 0; i < 5; i++)
    {
        double L = g_levels[i];
        if(low <= L && L < close && (!found || L > best)) { best = L; found = true; }
    }
    outLevel = best;
    return found;
}

// Lowest pivot whose level the upper wick pierced and the body closed below.
bool NearestResistanceReclaim(double high, double close, double &outLevel)
{
    bool found = false; double best = 0.0;
    for(int i = 0; i < 5; i++)
    {
        double L = g_levels[i];
        if(high >= L && L > close && (!found || L < best)) { best = L; found = true; }
    }
    outLevel = best;
    return found;
}

//+------------------------------------------------------------------+
//| Hedging: ride the breakout when a reclaimed pivot fails          |
//+------------------------------------------------------------------+
void ManageHedges()
{
    if(HedgeMultiplier <= 0.0 || !g_hasPivots || g_refRange <= 0.0) return;

    int n = ArraySize(g_trkTicket);
    if(n == 0) return;

    double dist     = HedgeBreakFraction * g_refRange;
    double hedgeVol = NormVol(Lots * HedgeMultiplier);
    if(hedgeVol <= 0.0) return;

    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    for(int i = 0; i < n; i++)
    {
        if(g_trkHedged[i]) continue;
        ulong tk = g_trkTicket[i];
        if(!PositionSelectByTicket(tk)) continue;                       // closed (prune removes)
        if(PositionGetString(POSITION_SYMBOL)  != _Symbol) continue;
        if(PositionGetInteger(POSITION_MAGIC)  != Magic)   continue;

        double L = g_trkLevel[i];
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

        if(type == POSITION_TYPE_BUY)
        {
            // Long bought a support bounce; support fails on a break below it.
            if(bid <= L - dist)
            {
                g_trkHedged[i] = true;                 // one hedge per original
                double entry = bid;
                double sl    = L + dist;               // invalidated back above the broken level
                FinalizeShort(entry, sl, hedgeVol, "HG", 0.0, true);
            }
        }
        else // POSITION_TYPE_SELL
        {
            // Short sold a resistance bounce; resistance fails on a break above it.
            if(ask >= L + dist)
            {
                g_trkHedged[i] = true;
                double entry = ask;
                double sl    = L - dist;               // invalidated back below the broken level
                FinalizeLong(entry, sl, hedgeVol, "HG", 0.0, true);
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Order construction (with broker min-stop clamping)               |
//+------------------------------------------------------------------+
void FinalizeLong(double entry, double sl, double vol, string comment, double level, bool isHedge)
{
    long   stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    double minDist   = (stopLevel + 1) * _Point;
    if(entry - sl < minDist) sl = entry - minDist;     // respect broker stop distance
    double risk = entry - sl;
    if(risk <= 0.0) return;
    double tp = entry + MathMax(RewardRatio * risk, minDist);

    vol = NormVol(vol);
    if(vol <= 0.0) return;

    if(trade.Buy(vol, _Symbol, 0.0, Norm(sl), Norm(tp), comment))
    {
        if(!isHedge)
        {
            ulong ticket = trade.ResultOrder();        // = position ticket (hedging mode)
            if(ticket > 0) TrackAdd(ticket, level);
        }
        PrintFormat("PRH %s BUY @ %.5f SL %.5f TP %.5f vol=%.2f", comment, entry, sl, tp, vol);
    }
}

void FinalizeShort(double entry, double sl, double vol, string comment, double level, bool isHedge)
{
    long   stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
    double minDist   = (stopLevel + 1) * _Point;
    if(sl - entry < minDist) sl = entry + minDist;
    double risk = sl - entry;
    if(risk <= 0.0) return;
    double tp = entry - MathMax(RewardRatio * risk, minDist);

    vol = NormVol(vol);
    if(vol <= 0.0) return;

    if(trade.Sell(vol, _Symbol, 0.0, Norm(sl), Norm(tp), comment))
    {
        if(!isHedge)
        {
            ulong ticket = trade.ResultOrder();
            if(ticket > 0) TrackAdd(ticket, level);
        }
        PrintFormat("PRH %s SELL @ %.5f SL %.5f TP %.5f vol=%.2f", comment, entry, sl, tp, vol);
    }
}

//+------------------------------------------------------------------+
//| Bookkeeping helpers                                              |
//+------------------------------------------------------------------+
// Forget tracked tickets that have closed (frees the side and bounds memory).
void PruneMaps()
{
    int n = ArraySize(g_trkTicket);
    if(n == 0) return;
    for(int i = n - 1; i >= 0; i--)
    {
        ulong tk = g_trkTicket[i];
        bool alive = PositionSelectByTicket(tk)
                     && PositionGetString(POSITION_SYMBOL)  == _Symbol
                     && PositionGetInteger(POSITION_MAGIC)  == Magic;
        if(!alive) TrackRemove(i);
    }
}

// Any open position on _Symbol+Magic of the given side whose comment starts with prefix.
bool HasOpen(ENUM_POSITION_TYPE side, string prefix)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(!PositionSelectByTicket(t)) continue;
        if(PositionGetString(POSITION_SYMBOL)  != _Symbol) continue;
        if(PositionGetInteger(POSITION_MAGIC)  != Magic)   continue;
        if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != side) continue;
        if(StringFind(PositionGetString(POSITION_COMMENT), prefix) == 0) return true;
    }
    return false;
}

void TrackAdd(ulong ticket, double level)
{
    int n = ArraySize(g_trkTicket);
    ArrayResize(g_trkTicket, n + 1);
    ArrayResize(g_trkLevel,  n + 1);
    ArrayResize(g_trkHedged, n + 1);
    g_trkTicket[n] = ticket;
    g_trkLevel[n]  = level;
    g_trkHedged[n] = false;
}

// Swap-with-last removal (order is irrelevant for these maps).
void TrackRemove(int idx)
{
    int last = ArraySize(g_trkTicket) - 1;
    if(idx < 0 || last < 0) return;
    g_trkTicket[idx] = g_trkTicket[last];
    g_trkLevel[idx]  = g_trkLevel[last];
    g_trkHedged[idx] = g_trkHedged[last];
    ArrayResize(g_trkTicket, last);
    ArrayResize(g_trkLevel,  last);
    ArrayResize(g_trkHedged, last);
}

double NormVol(double raw)
{
    double vstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    if(vstep <= 0.0) vstep = 0.01;
    double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double v = MathRound(raw / vstep) * vstep;
    v = NormalizeDouble(v, 2);
    if(v < vmin) v = vmin;
    if(v > vmax) v = vmax;
    return v;
}

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