//+------------------------------------------------------------------+
//|                                   DispersionScalingRotation.mq5   |
//|                                                        Algobot    |
//|  Anomalous-diffusion (MSD scaling-exponent) regime rotation EA.   |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters) -----------------------------
input int    Window       = 60;     // analysis window (bars) for MSD & z-score
input double ScalingBand  = 0.15;   // neutral half-band around alpha=1
input int    DriftSpan    = 4;      // bars spanned by net displacement (drift)
input double DriftAtrFrac = 0.30;   // drift must exceed this fraction of ATR
input double RevertZScore = 1.20;   // z-score extreme to fade in sub-diffusive regime
input int    AtrPeriod    = 14;     // ATR period
input double AtrSlMult    = 2.0;    // stop = AtrSlMult * ATR
input double Lots         = 0.10;   // fixed lot size
input long   Magic        = 40727;  // magic number

//--- lags used for the log-log MSD scaling fit (powers of two) ------
const int    Lags[4]  = {1, 2, 4, 8};
const int    TauMax   = 8;

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

//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    g_atr = iATR(_Symbol, _Period, AtrPeriod);
    if(g_atr == INVALID_HANDLE)
    {
        Print("Failed to create ATR handle");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Act once per newly-closed bar (keyed off the forming bar time).  |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| Anomalous-diffusion exponent: OLS slope of ln(MSD(tau)) on       |
//| ln(tau) over the last `w` displacements for each tau in Lags.    |
//| close[] is a SERIES array: index 1 = last CLOSED bar.            |
//| Returns false via `valid` if degenerate (the C# NaN cases).     |
//+------------------------------------------------------------------+
double ScalingExponent(const double &close[], int w, bool &valid)
{
    valid = false;
    int    sz = ArraySize(close);
    double sx = 0, sy = 0, sxx = 0, sxy = 0;
    int    m  = 0;

    for(int li = 0; li < 4; li++)
    {
        int    tau = Lags[li];
        double sum = 0;
        int    cnt = 0;
        // C#: for i = n-1 .. n-w ; here shift = 1 .. w (index 1 = last closed)
        for(int k = 0; k < w; k++)
        {
            int shiftA = 1 + k;
            int shiftB = 1 + k + tau;      // == span[i - tau]
            if(shiftB >= sz) break;        // C#: if(i - tau < 0) break;
            double d = MathLog(close[shiftA]) - MathLog(close[shiftB]);
            sum += d * d;
            cnt++;
        }
        if(cnt <= 0) return 0.0;           // -> NaN
        double msd = sum / cnt;
        if(msd <= 0.0) return 0.0;         // flat stretch -> undefined (NaN)

        double x = MathLog((double)tau);
        double y = MathLog(msd);
        sx += x; sy += y; sxx += x * x; sxy += x * y; m++;
    }

    double den = m * sxx - sx * sx;
    if(MathAbs(den) < 1e-12) return 0.0;   // -> NaN
    valid = true;
    return (m * sxy - sx * sy) / den;      // slope = alpha
}

//+------------------------------------------------------------------+
//| Mean and (population) standard deviation of the last `w` closes. |
//+------------------------------------------------------------------+
void MeanSd(const double &close[], int w, double &mean, double &sd)
{
    mean = 0.0;
    for(int k = 0; k < w; k++) mean += close[1 + k];   // shift 1 .. w
    mean /= w;
    double var = 0.0;
    for(int k = 0; k < w; k++)
    {
        double d = close[1 + k] - mean;
        var += d * d;
    }
    var /= w;
    sd = MathSqrt(var);
}

//+------------------------------------------------------------------+
//| Find first open position for this symbol+magic.                  |
//+------------------------------------------------------------------+
bool GetPosition(long magic, ulong &ticket, int &side)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            ticket = t;
            side   = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1 : -1;
            return true;
        }
    }
    return false;
}

//+------------------------------------------------------------------+
double Clamp(double v, double lo, double hi)
{
    return MathMin(MathMax(v, lo), hi);
}

//+------------------------------------------------------------------+
void Enter(bool isLong, double atr, double rr, double alpha)
{
    double price  = isLong ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                           : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double slDist = AtrSlMult * atr;
    double tpDist = slDist * rr;
    double sl     = isLong ? price - slDist : price + slDist;
    double tp     = isLong ? price + tpDist : price - tpDist;
    sl = NormalizeDouble(sl, _Digits);
    tp = NormalizeDouble(tp, _Digits);

    bool ok;
    if(isLong) ok = trade.Buy(Lots,  _Symbol, 0.0, sl, tp, "DSR-long");
    else       ok = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "DSR-short");

    PrintFormat("DispersionScalingRotation %s alpha=%.2f rr=%.2f @ %.5f -> %s",
                (isLong ? "LONG" : "SHORT"), alpha, rr, price,
                (ok ? "OK" : "FAIL " + IntegerToString(trade.ResultRetcode())));
}

//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // Match the C# readiness gate.
    int needed = Window + MathMax(MathMax(DriftSpan, AtrPeriod), TauMax) + 3;
    if(Bars(_Symbol, _Period) < needed + 2) return;

    // Copy closes as a SERIES array (index 0 = forming bar, index 1 = last closed).
    // cap = _window + _atrPeriod + _driftSpan + TauMax + 16 (mirrors the C# ring buffer).
    int cap = Window + AtrPeriod + DriftSpan + TauMax + 16;
    double close[];
    ArraySetAsSeries(close, true);
    if(CopyClose(_Symbol, _Period, 0, cap, close) < needed) return;

    // ATR at the last CLOSED bar (index 1), matching the C# span ending on it.
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atr, 0, 0, 3, atrBuf) < 3) return;
    double atr = atrBuf[1];
    if(atr <= 0.0) return;

    // ---- multi-scale anomalous-diffusion exponent alpha ----
    bool valid;
    double alpha = ScalingExponent(close, Window, valid);
    if(!valid) return;

    bool superDiff = alpha >= 1.0 + ScalingBand;   // ballistic -> trend with drift
    bool subDiff   = alpha <= 1.0 - ScalingBand;   // caged     -> fade extremes

    int    desired = 0;    // +1 long, -1 short, 0 none
    double rr      = 1.0;  // reward-to-risk multiple

    if(superDiff)
    {
        double netMove = close[1] - close[1 + DriftSpan];  // span[n-1] - span[n-1-driftSpan]
        int driftSign  = (netMove > 0) ? 1 : ((netMove < 0) ? -1 : 0);
        if(driftSign != 0 && MathAbs(netMove) >= DriftAtrFrac * atr)
        {
            desired = driftSign;
            // More super-diffusive -> expect a longer ballistic run -> wider TP.
            rr = Clamp(1.2 + 2.0 * (alpha - 1.0), 1.2, 3.0);
        }
    }
    else if(subDiff)
    {
        double mean, sd;
        MeanSd(close, Window, mean, sd);
        if(sd > 0.0)
        {
            double z = (close[1] - mean) / sd;
            if(z >= RevertZScore)       desired = -1;   // stretched up   -> fade short
            else if(z <= -RevertZScore) desired = 1;    // stretched down -> fade long
            if(desired != 0)
                // More sub-diffusive -> snappier cage -> tighter TP.
                rr = Clamp(1.3 - 2.0 * (1.0 - alpha), 0.6, 1.3);
        }
    }

    // ---- manage an open position: adaptive regime-flip exit ----
    ulong ticket;
    int   held;
    if(GetPosition(Magic, ticket, held))
    {
        if(desired != 0 && desired != held)
            trade.PositionClose(ticket);   // diffusion now argues the other way
        return;                            // else let SL/TP work
    }

    if(desired == 0) return;
    Enter(desired > 0, atr, rr, alpha);
}
//+------------------------------------------------------------------+
