#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

// PivotEngulfingRecovery
// ----------------------
// Pure price-action system (NO indicators). Structure is mapped with confirmed
// SWING PIVOTS (fractal highs/lows). Engulfing candles trade two edges against
// that structure (reversal + breakout). A volume-weighted recovery hedge flips
// the net book to the prevailing direction on an adverse run and the basket is
// flattened by its break-even (+recover) or a disaster stop (-maxAdverse).

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

//--- inputs ---
input int    PivotLeft      = 3;        // bars left of pivot candidate
input int    PivotRight     = 3;        // bars right of pivot candidate
input int    RangePeriod    = 14;       // N-bar range proxy window
input double ZoneFrac       = 0.50;     // swing-zone tolerance (x range)
input double SlRangeMult    = 1.50;     // scalp stop  (x range)
input double TpRangeMult    = 1.20;     // scalp target (x range)
input double HedgeTrigger   = 1.00;     // adverse run to hedge (x range)
input double HedgeMult      = 2.00;     // recovery leg size multiplier
input double RecoverMult    = 0.50;     // basket recover beyond BE (x range)
input double MaxAdverseMult = 3.00;     // basket disaster stop (x range)
input double Lots           = 0.10;     // primary lot size
input long   Magic          = 880800;   // primary magic

//--- state ---
long   HedgeMagic = 0;
double g_pivHigh  = 0.0;
double g_pivLow   = 0.0;
bool   g_hasHigh  = false;
bool   g_hasLow   = false;

int OnInit()
{
    HedgeMagic = Magic + 1;
    trade.SetExpertMagicNumber(Magic);
    g_pivHigh = 0.0; g_pivLow = 0.0;
    g_hasHigh = false; g_hasLow = false;
    return INIT_SUCCEEDED;
}

void OnTick()
{
    int need = (int)MathMax(PivotLeft + PivotRight + 2, RangePeriod + 2);
    if(Bars(_Symbol, _Period) <= need) return;

    // Basket / hedge management runs EVERY tick so adverse runs are caught promptly.
    ManageBasket();

    // Entries only evaluate once per newly-closed bar.
    if(!IsNewBar()) return;

    UpdatePivots();
    if(!g_hasHigh || !g_hasLow) return;

    // Only seek a fresh primary when fully flat (no primary and no hedge).
    if(CountPositions(Magic) > 0 || CountPositions(HedgeMagic) > 0) return;

    double b1Open  = iOpen(_Symbol, _Period, 1);
    double b1Close = iClose(_Symbol, _Period, 1);
    double b1High  = iHigh(_Symbol, _Period, 1);
    double b1Low   = iLow(_Symbol, _Period, 1);

    double rng = RangeProxy();
    if(rng <= 0) return;
    double zone = ZoneFrac * rng;

    bool bull = BullEngulf(1, 2);
    bool bear = BearEngulf(1, 2);

    double body = MathAbs(b1Close - b1Open);
    double bRng = b1High - b1Low;
    bool strong = (bRng > 0 && body >= 0.5 * bRng);

    // Reversal: engulfing that pierces and reclaims the swing level.
    bool revLong  = bull && (b1Low  <= g_pivLow  + zone);
    bool revShort = bear && (b1High >= g_pivHigh - zone);
    // Breakout: strong engulfing that closes decisively beyond the swing level.
    bool brkLong  = bull && strong && (b1Close > g_pivHigh + zone);
    bool brkShort = bear && strong && (b1Close < g_pivLow  - zone);

    bool goLong  = revLong  || brkLong;
    bool goShort = revShort || brkShort;
    if(goLong && goShort) return; // ambiguous bar - stand aside

    if(goLong)
        OpenTrade(true,  Magic, Lots, SlRangeMult * rng, TpRangeMult * rng);
    else if(goShort)
        OpenTrade(false, Magic, Lots, SlRangeMult * rng, TpRangeMult * rng);
}

void OnDeinit(const int reason) { }

//============================ basket / hedge management ====================

void ManageBasket()
{
    int primCount = CountPositions(Magic);
    int hedgCount = CountPositions(HedgeMagic);
    if(primCount == 0 && hedgCount == 0) return;

    double rng = RangeProxy();
    if(rng <= 0) return;

    // Phase 1 - primary still un-hedged: open the recovery leg on an adverse run.
    if(hedgCount == 0 && primCount > 0)
    {
        long   side; double priceOpen; ulong ticket;
        if(!GetFirstPosition(Magic, side, priceOpen, ticket)) return;

        double trig = HedgeTrigger * rng;
        double bid  = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double ask  = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        bool adverse = (side == POSITION_TYPE_BUY)
                       ? (priceOpen - bid >= trig)
                       : (ask - priceOpen >= trig);
        if(adverse)
        {
            // Larger leg opposite the primary -> net book flips to the trend side.
            bool hedgeLong = (side == POSITION_TYPE_SELL);
            OpenTrade(hedgeLong, HedgeMagic, Lots * HedgeMult, 0, 0);
            trade.PositionModify(ticket, 0.0, 0.0); // drop the scalp stops; the basket governs now
        }
        return;
    }

    // Phase 2 - hedged: manage the basket by its volume-weighted break-even.
    // NetP(x) = x * netVol + k, with netVol = sum(buyVol) - sum(sellVol).
    double netVol = 0.0, k = 0.0;
    AccumulateBasket(Magic,      netVol, k);
    AccumulateBasket(HedgeMagic, netVol, k);
    if(MathAbs(netVol) < 1e-9) return; // degenerate (HedgeMult >= 1.2 prevents this)

    double be      = -k / netVol;          // net profit is zero at this price
    double recover = RecoverMult * rng;
    double maxAdv  = MaxAdverseMult * rng;

    if(netVol > 0) // net long: profit rises with price
    {
        double px = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        if(px >= be + recover || px <= be - maxAdv) CloseBasket();
    }
    else           // net short: profit rises as price falls
    {
        double px = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        if(px <= be - recover || px >= be + maxAdv) CloseBasket();
    }
}

void AccumulateBasket(long magic, double &netVol, double &k)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            double vol = PositionGetDouble(POSITION_VOLUME);
            double po  = PositionGetDouble(POSITION_PRICE_OPEN);
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            { netVol += vol; k -= po * vol; }
            else
            { netVol -= vol; k += po * vol; }
        }
    }
}

void CloseBasket()
{
    CloseAll(Magic);
    CloseAll(HedgeMagic);
}

//============================ order helper =================================

void OpenTrade(bool isLong, long magic, double vol, double slDist, double tpDist)
{
    trade.SetExpertMagicNumber(magic);
    vol = NormalizeVolume(vol);

    if(isLong)
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl = (slDist > 0) ? NormalizeDouble(price - slDist, _Digits) : 0.0;
        double tp = (tpDist > 0) ? NormalizeDouble(price + tpDist, _Digits) : 0.0;
        trade.Buy(vol, _Symbol, 0.0, sl, tp, "PER");
    }
    else
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl = (slDist > 0) ? NormalizeDouble(price + slDist, _Digits) : 0.0;
        double tp = (tpDist > 0) ? NormalizeDouble(price - tpDist, _Digits) : 0.0;
        trade.Sell(vol, _Symbol, 0.0, sl, tp, "PER");
    }
}

//============================ price-action helpers =========================

// Streaming swing-pivot detector: the candidate sits PivotRight bars back and
// must be the strict extreme versus PivotLeft older + PivotRight newer bars.
void UpdatePivots()
{
    int c = PivotRight;
    double candHigh = iHigh(_Symbol, _Period, c);
    double candLow  = iLow(_Symbol, _Period, c);

    bool isHigh = true, isLow = true;
    for(int i = c + 1; i <= c + PivotLeft; i++) // older neighbours
    {
        double h = iHigh(_Symbol, _Period, i);
        double l = iLow(_Symbol, _Period, i);
        if(h >= candHigh) isHigh = false;
        if(l <= candLow)  isLow  = false;
    }
    for(int j = 0; j < c; j++)                  // newer neighbours
    {
        double h = iHigh(_Symbol, _Period, j);
        double l = iLow(_Symbol, _Period, j);
        if(h >= candHigh) isHigh = false;
        if(l <= candLow)  isLow  = false;
    }

    if(isHigh) { g_pivHigh = candHigh; g_hasHigh = true; }
    if(isLow)  { g_pivLow  = candLow;  g_hasLow  = true; }
}

// Average bar range over the recent window - a pure price-action volatility
// proxy (no indicator) used to size stops, targets and the basket.
double RangeProxy()
{
    double sum = 0.0;
    for(int i = 1; i <= RangePeriod; i++)
        sum += iHigh(_Symbol, _Period, i) - iLow(_Symbol, _Period, i);
    return sum / RangePeriod;
}

bool BullEngulf(int s1, int s2)
{
    double c1 = iClose(_Symbol, _Period, s1), o1 = iOpen(_Symbol, _Period, s1);
    double c2 = iClose(_Symbol, _Period, s2), o2 = iOpen(_Symbol, _Period, s2);
    return (c1 > o1 && c2 < o2 && c1 >= o2 && o1 <= c2);
}

bool BearEngulf(int s1, int s2)
{
    double c1 = iClose(_Symbol, _Period, s1), o1 = iOpen(_Symbol, _Period, s1);
    double c2 = iClose(_Symbol, _Period, s2), o2 = iOpen(_Symbol, _Period, s2);
    return (c1 < o1 && c2 > o2 && o1 >= c2 && c1 <= o2);
}

//============================ position / utility helpers ===================

bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

int CountPositions(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;
}

bool GetFirstPosition(long magic, long &side, double &priceOpen, ulong &ticket)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            side      = PositionGetInteger(POSITION_TYPE);
            priceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
            ticket    = t;
            return true;
        }
    }
    return false;
}

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);
    }
}

double NormalizeVolume(double vol)
{
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    double minv = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxv = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    if(step > 0) vol = MathRound(vol / step) * step;
    if(vol < minv) vol = minv;
    if(vol > maxv) vol = maxv;
    return vol;
}
