//+------------------------------------------------------------------+
//|                                 HiguchiDimensionRegimeTrend.mq5   |
//|                                                          Algobot  |
//|                                    https://www.algobot.live       |
//+------------------------------------------------------------------+
//  Regime-gated trend follower driven by the HIGUCHI FRACTAL DIMENSION
//  of the recent close curve.
//
//  FD -> 1.0 : path is smooth / near-straight -> persistent TREND
//  FD -> 2.0 : path is jagged / space-filling  -> CHOP / mean-reverting noise
//
//  Trades:
//    * Regime gate : only act when FD <= TrendThreshold (smooth => trending).
//    * Direction   : baseline EMA. A fresh close-cross of the EMA with the EMA
//                    sloping the same way defines the entry.
//        Long  : trending + close crosses ABOVE EMA + EMA rising
//        Short : trending + close crosses BELOW EMA + EMA falling
//    * Stop-and-reverse : an opposite qualified signal closes & flips.
//    * Risk        : ATR-based stop and take-profit (distinct, symmetric).
//
//  Single timeframe: everything is read on the chart's own timeframe.
//+------------------------------------------------------------------+
#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    FdLookback     = 40;      // Window of closes fed to the Higuchi estimator
input int    Kmax           = 6;       // Max re-sampling stride kmax for the length/scale curve
input double TrendThreshold = 1.45;    // Trade only when FD <= this (smoother => trending)
input int    EmaPeriod      = 21;      // Baseline EMA whose cross gives the trigger
input int    AtrPeriod      = 14;      // ATR window for stop / target sizing
input double StopAtrMult    = 2.0;     // Stop distance = this many ATRs
input double TargetAtrMult  = 3.0;     // Take-profit distance = this many ATRs
input double Lots           = 0.10;    // Order volume
input long   Magic          = 940217;  // Magic number

//--- indicator handles ----------------------------------------------
int g_emaHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;

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

    g_emaHandle = iMA(_Symbol, _Period, EmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);

    if(g_emaHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
    {
        Print("HiguchiDimensionRegimeTrend: failed to create indicator handles");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Act once per bar, on the close of the just-completed bar.        |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // Warm-up: mirror the C# requirement len >= max(FdLookback+1, EmaPeriod+2, AtrPeriod+1).
    int warmup = (int)MathMax(FdLookback + 1, MathMax(EmaPeriod + 2, AtrPeriod + 1));
    if(Bars(_Symbol, _Period) < warmup + 2) return;

    //--- EMA at the just-completed bar (shift 1) and the one before (shift 2) ---
    double ema[];
    ArraySetAsSeries(ema, true);
    if(CopyBuffer(g_emaHandle, 0, 0, 3, ema) < 3) return;

    //--- ATR at the just-completed bar (shift 1) ---
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, 3, atrBuf) < 3) return;

    // Completed-bar closes: newest completed = shift 1, previous = shift 2.
    double closeNow  = iClose(_Symbol, _Period, 1);
    double closePrev = iClose(_Symbol, _Period, 2);
    double emaNow    = ema[1];
    double emaPrev   = ema[2];

    //--- Direction trigger: fresh close-cross of a same-sloping EMA ---
    bool slopeUp   = emaNow > emaPrev;
    bool slopeDown = emaNow < emaPrev;
    bool crossUp   = (closePrev <= emaPrev) && (closeNow > emaNow) && slopeUp;
    bool crossDown = (closePrev >= emaPrev) && (closeNow < emaNow) && slopeDown;

    //--- Regime gate: Higuchi fractal dimension of the recent close curve ---
    // Last FdLookback completed closes, ending at shift 1 (oldest at index 0).
    double fdCloses[];
    ArrayResize(fdCloses, FdLookback);
    for(int i = 0; i < FdLookback; i++)
        fdCloses[i] = iClose(_Symbol, _Period, FdLookback - i);   // shift FdLookback .. 1

    double fd = HiguchiFractalDimension(fdCloses, FdLookback, Kmax);
    bool trending = (fd <= TrendThreshold);

    int signal = 0;
    if(trending && crossUp)        signal = 1;
    else if(trending && crossDown) signal = -1;
    if(signal == 0) return;

    //--- ATR for risk ---
    double atr = atrBuf[1];
    if(atr <= 0) return;

    //--- Stop-and-reverse execution ---
    bool haveLong  = HasPositionSide(Magic, POSITION_TYPE_BUY);
    bool haveShort = HasPositionSide(Magic, POSITION_TYPE_SELL);

    if(signal == 1)
    {
        CloseSide(Magic, POSITION_TYPE_SELL);
        if(!haveLong) TryEnter(true, atr, fd);
    }
    else // signal == -1
    {
        CloseSide(Magic, POSITION_TYPE_BUY);
        if(!haveShort) TryEnter(false, atr, fd);
    }
}

//+------------------------------------------------------------------+
//| Enter with ATR-based SL/TP.                                      |
//+------------------------------------------------------------------+
void TryEnter(bool isLong, double atr, double fd)
{
    double stop   = StopAtrMult * atr;
    double target = TargetAtrMult * atr;
    if(stop <= 0) return;

    double price, sl, tp;

    if(isLong)
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        sl    = NormalizeDouble(price - stop,   _Digits);
        tp    = NormalizeDouble(price + target, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "HDRT-long");
    }
    else
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        sl    = NormalizeDouble(price + stop,   _Digits);
        tp    = NormalizeDouble(price - target, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "HDRT-short");
    }

    PrintFormat("HiguchiDimensionRegimeTrend %s @ %s SL %s TP %s FD %.3f ATR %s",
                (isLong ? "LONG" : "SHORT"),
                DoubleToString(price, _Digits),
                DoubleToString(sl, _Digits),
                DoubleToString(tp, _Digits),
                fd,
                DoubleToString(atr, _Digits));
}

//+------------------------------------------------------------------+
//| Higuchi (1988) fractal dimension of a 1-D series.                |
//|  For each stride k=1..kmax and each offset m=1..k build a        |
//|  sub-curve, sum |step| displacements, length-normalise, then fit |
//|  ln(L(k)) against ln(1/k). Regression slope is the FD, [1,2].    |
//|  Uses absolute differences => series orientation is irrelevant.  |
//+------------------------------------------------------------------+
double HiguchiFractalDimension(const double &x[], int nSer, int kmax)
{
    if(nSer < 4) return 1.5;
    if(kmax < 2) kmax = 2;
    if(kmax > nSer - 1) kmax = nSer - 1;

    double sx = 0, sy = 0, sxx = 0, sxy = 0;
    int cnt = 0;

    for(int k = 1; k <= kmax; k++)
    {
        double sumLm = 0;
        int valid = 0;

        for(int m = 1; m <= k; m++)
        {
            int numItems = (nSer - m) / k;   // floor
            if(numItems < 1) continue;

            double lm = 0;
            for(int i = 1; i <= numItems; i++)
            {
                int a = m + i * k - 1;          // 0-based
                int b = m + (i - 1) * k - 1;
                lm += MathAbs(x[a] - x[b]);
            }
            // length normalisation factor from Higuchi's construction
            double norm = (double)(nSer - 1) / (numItems * k);
            lm = lm * norm / k;
            sumLm += lm;
            valid++;
        }

        if(valid == 0) continue;

        double lk = sumLm / valid;
        if(lk <= 0) continue;

        double xi = MathLog(1.0 / k);
        double yi = MathLog(lk);
        sx += xi; sy += yi; sxx += xi * xi; sxy += xi * yi;
        cnt++;
    }

    if(cnt < 2) return 1.5;

    double denom = cnt * sxx - sx * sx;
    if(MathAbs(denom) < 1e-12) return 1.5;

    double slope = (cnt * sxy - sx * sy) / denom;   // slope == fractal dimension
    if(!MathIsValidNumber(slope)) return 1.5;

    if(slope < 1.0) slope = 1.0;
    if(slope > 2.0) slope = 2.0;
    return slope;
}

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

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

void CloseSide(long magic, ENUM_POSITION_TYPE side)
{
    for(int i = PositionsTotal()-1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic &&
           (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == side)
            trade.PositionClose(t);
    }
}
//+------------------------------------------------------------------+
