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

// SemivarianceReversionFade
// -------------------------
// Asymmetric mean-reversion. Around an EMA "fair value" baseline we hang an
// ASYMMETRIC envelope whose lower rail is scaled by the DOWNSIDE semideviation
// of returns and whose upper rail is scaled by the UPSIDE semideviation. Fades
// stretches back to the drifting baseline, with an EMA-slope trend guard and an
// ATR protective stop. Native MQL5 port of the Algobot C# strategy.

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

//--- inputs (names/defaults mirror the C# DescribeParameters) -----------------
input int    MeanPeriod      = 20;     // EMA fair-value baseline / take-profit magnet
input int    DevPeriod       = 40;     // lookback for downside/upside semideviation
input double DownMult        = 2.2;    // lower-rail width x DOWNSIDE semideviation
input double UpMult          = 2.2;    // upper-rail width x UPSIDE   semideviation
input double TrendGuardAtr   = 0.30;   // skip fades when |baseline slope| > this * ATR/bar
input int    AtrPeriod       = 14;     // ATR lookback
input double AtrStopMult     = 1.8;    // protective stop distance (x ATR)
input double MinRewardRisk   = 0.8;    // reject setups with reward/risk below this
input int    MaxSpreadPoints = 80;     // skip new entries when spread (points) exceeds this
input double Lots            = 0.10;   // fixed order volume
input long   Magic           = 5271;   // magic number

//--- indicator handles --------------------------------------------------------
int g_ema = INVALID_HANDLE;
int g_atr = INVALID_HANDLE;

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

    g_ema = iMA(_Symbol, _Period, MeanPeriod, 0, MODE_EMA, PRICE_CLOSE);
    g_atr = iATR(_Symbol, _Period, AtrPeriod);

    if(g_ema == INVALID_HANDLE || g_atr == INVALID_HANDLE)
    {
        Print("SRF: failed to create indicator handles");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//------------------------------------------------------------------------------
void OnDeinit(const int reason)
{
    if(g_ema != INVALID_HANDLE) IndicatorRelease(g_ema);
    if(g_atr != INVALID_HANDLE) IndicatorRelease(g_atr);
}

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

//--- Count positions for this magic/symbol; close winners that reclaimed the
//    (drifting) baseline. Mirrors the C# adaptive-exit + one-per-magic logic.
int CountAndManage(const double baseline, const double closeBar)
{
    int cnt = 0;
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == Magic)
        {
            cnt++;
            long type = PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_BUY && closeBar >= baseline)
                trade.PositionClose(t);
            else if(type == POSITION_TYPE_SELL && closeBar <= baseline)
                trade.PositionClose(t);
        }
    }
    return cnt;
}

//------------------------------------------------------------------------------
void OnTick()
{
    // Act once per newly-closed primary bar.
    if(!IsNewBar()) return;

    // Need enough history for the widest lookback (slope needs 2x the EMA span).
    int need = MathMax(DevPeriod + 1, MathMax(2 * MeanPeriod + 1, AtrPeriod + 1));
    if(Bars(_Symbol, _Period) < need + 2) return;

    //---- EMA "fair value" baseline + its per-bar slope (trend guard) ----------
    // buffer[1] = just-closed bar (the C# "newest closed" = list index count-1).
    double emaBuf[];
    ArraySetAsSeries(emaBuf, true);
    int emaNeed = MeanPeriod + 2;
    if(CopyBuffer(g_ema, 0, 0, emaNeed, emaBuf) < emaNeed) return;

    double baseline     = emaBuf[1];
    double baselinePrev = emaBuf[1 + MeanPeriod];
    double slopePerBar  = (baseline - baselinePrev) / MeanPeriod;

    //---- ATR (protective stop scale) -----------------------------------------
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atr, 0, 0, 3, atrBuf) < 3) return;
    double atr = atrBuf[1];
    if(atr <= 0) return;

    //---- Downside / upside semideviation of the last DevPeriod returns --------
    // Return at shift k = close[k]/close[k+1] - 1, for k = 1..DevPeriod.
    double sumR = 0.0; int m = 0;
    for(int k = 1; k <= DevPeriod; k++)
    {
        double prev = iClose(_Symbol, _Period, k + 1);
        if(prev <= 0) continue;
        sumR += iClose(_Symbol, _Period, k) / prev - 1.0;
        m++;
    }
    if(m <= 1) return;
    double meanR = sumR / m;

    double downSq = 0.0, upSq = 0.0; int downN = 0, upN = 0;
    for(int k = 1; k <= DevPeriod; k++)
    {
        double prev = iClose(_Symbol, _Period, k + 1);
        if(prev <= 0) continue;
        double d = (iClose(_Symbol, _Period, k) / prev - 1.0) - meanR;
        if(d < 0) { downSq += d * d; downN++; }
        else      { upSq   += d * d; upN++;   }
    }
    if(downN == 0 || upN == 0) return;
    double downDev = MathSqrt(downSq / downN);
    double upDev   = MathSqrt(upSq / upN);
    if(downDev <= 0 || upDev <= 0) return;

    //---- Asymmetric semivariance envelope around the baseline -----------------
    double lower = baseline * (1.0 - DownMult * downDev);
    double upper = baseline * (1.0 + UpMult   * upDev);

    // Freshly-closed signal bar = shift 1.
    double cClose = iClose(_Symbol, _Period, 1);
    double cOpen  = iOpen(_Symbol,  _Period, 1);

    //---- Adaptive exit + one-position-per-magic gate --------------------------
    if(CountAndManage(baseline, cClose) > 0) return;

    //---- Spread filter --------------------------------------------------------
    long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
    if(spread > MaxSpreadPoints) return;

    //---- Trend guard: stand aside during a strong directional grind -----------
    if(MathAbs(slopePerBar) > TrendGuardAtr * atr) return;

    bool bullish  = cClose > cOpen;
    bool bearish  = cClose < cOpen;
    double stopDist = AtrStopMult * atr;

    //---- LONG: stretched below the DOWNSIDE rail, already snapping back --------
    if(cClose < lower && bullish)
    {
        double entry  = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double tp     = baseline;                 // mean is the magnet/target
        double sl     = entry - stopDist;
        double reward = tp - entry;
        double risk   = entry - sl;
        if(reward > 0 && risk > 0 && reward / risk >= MinRewardRisk)
        {
            sl = NormalizeDouble(sl, _Digits);
            tp = NormalizeDouble(tp, _Digits);
            bool ok = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "SRF long");
            PrintFormat("SRF LONG close=%.5f lower=%.5f base=%.5f downDev=%.5f tp=%.5f sl=%.5f -> %s",
                        cClose, lower, baseline, downDev, tp, sl, (ok ? "OK" : "FAIL"));
        }
        return;
    }

    //---- SHORT: stretched above the UPSIDE rail, already rolling over ----------
    if(cClose > upper && bearish)
    {
        double entry  = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double tp     = baseline;
        double sl     = entry + stopDist;
        double reward = entry - tp;
        double risk   = sl - entry;
        if(reward > 0 && risk > 0 && reward / risk >= MinRewardRisk)
        {
            sl = NormalizeDouble(sl, _Digits);
            tp = NormalizeDouble(tp, _Digits);
            bool ok = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "SRF short");
            PrintFormat("SRF SHORT close=%.5f upper=%.5f base=%.5f upDev=%.5f tp=%.5f sl=%.5f -> %s",
                        cClose, upper, baseline, upDev, tp, sl, (ok ? "OK" : "FAIL"));
        }
        return;
    }
}
