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

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

//--- inputs ------------------------------------------------------------------
input int    ValuePeriod  = 30;    // Lookback window for the range-weighted acceptance-value line and its slope
input int    AtrPeriod    = 14;    // ATR period used to scale the deviation trigger and the stop distance
input double DeviationAtr = 2.0;   // How many ATRs price must be stretched from the value line to arm a fade
input double FlatDriftAtr = 1.5;   // Max window drift (in ATRs) that still counts as "balance" (ranging gate)
input double StopAtr      = 1.5;   // Protective stop distance beyond entry, in ATRs
input double Lots         = 0.10;  // Trade volume
input long   Magic        = 8143;  // Magic number

//-----------------------------------------------------------------------------
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    return(INIT_SUCCEEDED);
}

//-----------------------------------------------------------------------------
void OnDeinit(const int reason)
{
    // No indicator handles: ATR and the acceptance line are computed manually.
}

//-----------------------------------------------------------------------------
void OnTick()
{
    // Evaluate once per freshly closed bar (shift 0 is the still-forming bar).
    if(!IsNewBar()) return;

    int need = MathMax(ValuePeriod + 2, AtrPeriod + 2);
    if(Bars(_Symbol, _Period) < need) return;

    // One position at a time for this magic - SL/TP manage the exit.
    if(HasPosition(Magic)) return;

    // ---- Average bar range across the value window (for the acceptance weighting) ----
    double sumRange = 0.0;
    for(int s = 1; s <= ValuePeriod; s++)
        sumRange += iHigh(_Symbol, _Period, s) - iLow(_Symbol, _Period, s);
    double avgRange = sumRange / ValuePeriod;
    if(avgRange <= 0.0) return;
    double avgRange2 = avgRange * avgRange;

    // ---- Range-weighted acceptance value + least-squares drift, one pass ----
    // Quiet bars (small range) dominate the value; wide excursion bars are down-weighted.
    // Index x runs 0 (oldest, shift ValuePeriod) .. ValuePeriod-1 (newest, shift 1).
    double sumW = 0.0, sumWTp = 0.0;
    double sX = 0.0, sY = 0.0, sXX = 0.0, sXY = 0.0;
    int n = ValuePeriod;
    for(int s = 1; s <= ValuePeriod; s++)
    {
        double h = iHigh(_Symbol,  _Period, s);
        double l = iLow(_Symbol,   _Period, s);
        double c = iClose(_Symbol, _Period, s);
        double range = h - l;
        double tp = (h + l + c) / 3.0;
        double w = avgRange2 / (range * range + avgRange2);
        sumW   += w;
        sumWTp += w * tp;

        double x = n - s;          // 0 = oldest, n-1 = newest
        sX  += x;
        sY  += c;
        sXX += x * x;
        sXY += x * c;
    }
    if(sumW <= 0.0) return;
    double value = sumWTp / sumW;

    double denom = n * sXX - sX * sX;
    if(denom == 0.0) return;
    double slope = (n * sXY - sX * sY) / denom;   // close change per bar (newer = +)
    double windowDrift = MathAbs(slope) * (n - 1);

    // ---- ATR (manual, from true ranges of closed bars) ----
    double atr = ComputeAtr(AtrPeriod);
    if(atr <= 0.0) return;

    // Ranging gate: skip trending windows - reversion needs balance, not trend.
    if(windowDrift > FlatDriftAtr * atr) return;

    double lastOpen  = iOpen(_Symbol,  _Period, 1);
    double lastClose = iClose(_Symbol, _Period, 1);   // most recently closed bar
    double priorClose = iClose(_Symbol, _Period, 2);
    double deviation = lastClose - value;
    double trigger = DeviationAtr * atr;

    // ---- Stretched BELOW value + bullish turn -> fade LONG ----
    bool bullishTurn = lastClose > lastOpen && lastClose > priorClose;
    if(deviation <= -trigger && bullishTurn)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double tp    = value;                    // revert to the acceptance line
        double sl    = entry - StopAtr * atr;
        if(tp - entry > 0.0 && entry - sl > 0.0)
        {
            if(trade.Buy(Lots, _Symbol, 0.0, sl, tp, "AcceptanceReversionLong"))
                PrintFormat("Value-reversion long @ %.5f SL %.5f TP %.5f (dev %.2f ATR)",
                            entry, sl, tp, deviation / atr);
        }
        return;
    }

    // ---- Stretched ABOVE value + bearish turn -> fade SHORT ----
    bool bearishTurn = lastClose < lastOpen && lastClose < priorClose;
    if(deviation >= trigger && bearishTurn)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double tp    = value;
        double sl    = entry + StopAtr * atr;
        if(entry - tp > 0.0 && sl - entry > 0.0)
        {
            if(trade.Sell(Lots, _Symbol, 0.0, sl, tp, "AcceptanceReversionShort"))
                PrintFormat("Value-reversion short @ %.5f SL %.5f TP %.5f (dev %.2f ATR)",
                            entry, sl, tp, deviation / atr);
        }
    }
}

//-----------------------------------------------------------------------------
// Wilder-style simple ATR from closed bars: mean true range over `period`.
double ComputeAtr(int period)
{
    double sum = 0.0;
    for(int s = 1; s <= period; s++)
    {
        double curHigh = iHigh(_Symbol,  _Period, s);
        double curLow  = iLow(_Symbol,   _Period, s);
        double prevClose = iClose(_Symbol, _Period, s + 1);
        double tr = MathMax(curHigh - curLow,
                    MathMax(MathAbs(curHigh - prevClose),
                            MathAbs(curLow  - prevClose)));
        sum += tr;
    }
    return sum / period;
}

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

//-----------------------------------------------------------------------------
bool HasPosition(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) return true;
    }
    return false;
}
