//+------------------------------------------------------------------+
//|                                    SymbolicEntropyRegimeTrend.mq5 |
//|                                                          Algobot  |
//|                                       https://www.algobot.live    |
//+------------------------------------------------------------------+
//  SymbolicEntropyRegimeTrend  (native MQL5 port of the Algobot C# strategy)
//
//  Treats the instrument as a stochastic source emitting one SYMBOL per bar,
//  where the symbol encodes the sign AND magnitude of that bar's volatility-
//  normalized log return.
//    1. r_i = ln(C_i / C_{i-1})                       (log return)
//    2. z_i = r_i / sigma,  sigma = std(r) over Window (self-scaling)
//    3. symbolize z into 4 buckets by sign & magnitude vs tau
//    4. H = -sum p_s*log2(p_s)  ->  Order O = 1 - H/log2(4)  in [0,1]
//    5. Drift D = mean(z_i)                            (net direction)
//
//  ENTRY  long : O >= OrderThreshold and D > +DriftMin
//         short: O >= OrderThreshold and D < -DriftMin
//  EXIT   ATR stop / target, PLUS a regime-dissolution exit that flattens
//         the trade once O collapses below ExitOrderThreshold.
//
//  Uses no engine indicators; ATR is a manual mean-true-range, exactly as in
//  the C# source.
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (order & defaults mirror DescribeParameters) -----------------
input int    Window              = 40;        // symbol-source window (bars)
input double StrongMoveThreshold = 1.0;       // tau: strong-move z threshold
input double OrderThreshold      = 0.20;      // min order O to enter
input double DriftMin            = 0.05;      // min |drift| to enter
input double ExitOrderThreshold  = 0.10;      // O below this dissolves the trade
input int    AtrPeriod           = 14;        // ATR period (manual mean TR)
input double StopAtrMult         = 2.0;       // SL = mult * ATR
input double TpAtrMult           = 3.0;       // TP = mult * ATR
input double Lots                = 0.10;      // fixed lot size
input long   Magic               = 20260710;  // magic number

const double Ln2 = 0.69314718055994530942;    // ln(2)

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(Magic);
    // No engine-indicator handles: entropy, drift and ATR are all computed
    // manually, exactly as the C# strategy does (it "uses no indicators").
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // no handles to release
}

//+------------------------------------------------------------------+
//| OnTick — fire once per completed bar                            |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // need enough completed history for returns (Window+1) and ATR (AtrPeriod+1)
    int need = MathMax(Window, AtrPeriod) + 2;
    if(Bars(_Symbol, _Period) < need) return;

    //---- volatility-normalized log returns over the window --------------
    //  return i uses shift (Window-i) vs (Window-i+1), matching the C# order
    //  where the newest completed return (shift 1 vs 2) is the last element.
    double rets[];
    ArrayResize(rets, Window);
    double sum = 0.0;
    for(int i = 0; i < Window; i++)
    {
        int    shift = Window - i;                          // Window .. 1
        double c1    = iClose(_Symbol, _Period, shift);
        double c0    = iClose(_Symbol, _Period, shift + 1);
        double r     = MathLog(c1 / c0);
        rets[i]      = r;
        sum         += r;
    }
    double mean = sum / Window;

    double varSum = 0.0;
    for(int i = 0; i < Window; i++)
    {
        double d = rets[i] - mean;
        varSum  += d * d;
    }
    double sigma = MathSqrt(varSum / Window);
    if(sigma <= 1e-12) return;                              // flat window, no info

    double drift = mean / sigma;                            // D = mean(z_i)

    //---- symbolic entropy over a 4-letter alphabet ---------------------
    int counts[4];
    counts[0] = counts[1] = counts[2] = counts[3] = 0;
    for(int i = 0; i < Window; i++)
    {
        double z = rets[i] / sigma;
        int    s;
        if(z >= StrongMoveThreshold)        s = 3;   // strong up
        else if(z >= 0.0)                   s = 2;   // mild up
        else if(z > -StrongMoveThreshold)   s = 1;   // mild down
        else                                s = 0;   // strong down
        counts[s]++;
    }
    double h = 0.0;
    for(int s = 0; s < 4; s++)
    {
        if(counts[s] == 0) continue;
        double p = (double)counts[s] / Window;
        h -= p * (MathLog(p) / Ln2);
    }
    double order = 1.0 - h / 2.0;                           // log2(4) = 2 -> O in [0,1]

    double atr = ComputeAtr(AtrPeriod);
    if(atr <= 0.0) return;

    //---- manage an existing position -----------------------------------
    if(HasPosition(Magic))
    {
        if(order < ExitOrderThreshold)                      // ordered regime dissolved
            CloseAll(Magic);
        return;                                             // never stack entries
    }

    //---- entries: only inside an ordered regime, along the drift --------
    if(order < OrderThreshold) return;

    if(drift > DriftMin)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = NormalizeDouble(entry - StopAtrMult * atr, _Digits);
        double tp    = NormalizeDouble(entry + TpAtrMult   * atr, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "SymbolicEntropyRegime");
    }
    else if(drift < -DriftMin)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = NormalizeDouble(entry + StopAtrMult * atr, _Digits);
        double tp    = NormalizeDouble(entry - TpAtrMult   * atr, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "SymbolicEntropyRegime");
    }
}

//+------------------------------------------------------------------+
//| Manual ATR (simple mean true range) — matches the C# ComputeAtr. |
//| Covers the last `period` completed bars (shifts 1..period).      |
//+------------------------------------------------------------------+
double ComputeAtr(int period)
{
    double sum = 0.0;
    for(int shift = 1; shift <= period; shift++)
    {
        double hi = iHigh(_Symbol,  _Period, shift);
        double lo = iLow(_Symbol,   _Period, shift);
        double pc = iClose(_Symbol, _Period, shift + 1);    // previous close
        double tr = MathMax(hi - lo,
                    MathMax(MathAbs(hi - pc), MathAbs(lo - pc)));
        sum += tr;
    }
    return(sum / period);
}

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

//+------------------------------------------------------------------+
//| Any open position for this symbol + magic?                       |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
//| Close every position for this symbol + magic                     |
//+------------------------------------------------------------------+
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);
    }
}
//+------------------------------------------------------------------+
