//+------------------------------------------------------------------+
//|                                  DirectionalCoherenceEmergence.mq5 |
//|                                                          Algobot   |
//|                                      https://www.algobot.live      |
//+------------------------------------------------------------------+
//  DirectionalCoherenceEmergence
//  ----------------------------------------------------------------------------
//  Order parameter of a bar-return sign sequence.
//
//  Treat each closed bar as producing a return r_i = Close_i - Close_{i-1} and
//  keep only its sign s_i in { -1, +1 }. Over a window of N such signs, count
//  the number of ALTERNATIONS A (places where the sign flips). For a memoryless
//  market the expected number of alternations is (N-1)/2.
//
//     C = 1 - A / ((N-1)/2) = 1 - 2A/(N-1)
//
//     C -> +1 : ordered (directional regime present)
//     C ->  0 : no structure
//     C -> -1 : anti-persistent / choppy
//
//  The tradable event is the EMERGENCE of order - C rising through a self-
//  calibrated barrier theta = mean(C) + K*std(C) measured over a trailing window
//  of the coherence signal itself. We enter WITH the emerging order (direction
//  given by the net displacement of the window) and exit when C decays below 0.
//
//  Stop = ATR * StopAtrMult. Target STRETCHES with the strength of order at
//  entry:  TP = ATR * TpAtrMult * (1 + max(0, C_entry)).
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters + Magic) --------------------
input int    CoherenceWindow = 20;      // Number of bar-return signs used to measure run coherence
input int    AdaptLookback   = 60;      // Trailing window for the coherence signal's own mean/std
input double ThresholdK      = 0.6;     // Barrier = mean(C) + K*std(C)
input int    AtrPeriod       = 14;      // Volatility budget for the stop
input double StopAtrMult     = 1.6;     // Stop distance = ATR * this
input double TpAtrMult       = 2.4;     // Base target multiple (stretched by coherence at entry)
input double Lots            = 0.10;    // Trade volume
input long   Magic           = 730114;  // Magic number

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

//--- persistent state (mirrors the C# instance fields) -------------
double   g_closes[];          // closed-bar closes, newest-last (== C# _closes)
double   g_coh[];             // coherence history, newest-last  (== C# _coh)
double   g_prevCoherence = 0.0;
bool     g_havePrev      = false;

#define MAX_KEEP 700          // == C# MaxKeep

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

    g_atrHandle = iATR(_Symbol, _Period, AtrPeriod);
    if(g_atrHandle == INVALID_HANDLE)
    {
        Print("Failed to create ATR handle");
        return INIT_FAILED;
    }

    ArrayResize(g_closes, 0);
    ArrayResize(g_coh, 0);
    g_prevCoherence = 0.0;
    g_havePrev      = false;

    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Tick - act once per closed bar                                   |
//+------------------------------------------------------------------+
void OnTick()
{
    // Detect a freshly-opened bar (== C# forming-bar-time change), then
    // consume the now-final bar at shift 1.
    if(!IsNewBar()) return;
    if(Bars(_Symbol, _Period) < 2) return;

    // Append the just-closed bar's close (== C# _closes.Add(closed.Close)).
    AppendTrim(g_closes, iClose(_Symbol, _Period, 1));

    int m = ArraySize(g_closes);
    if(m < CoherenceWindow + 1) return;   // need window+1 closes for N signed returns

    double net = 0.0;
    double coherence = ComputeCoherence(CoherenceWindow, net);

    // Adaptive barrier from the coherence signal's OWN trailing distribution.
    bool   haveBarrier = (ArraySize(g_coh) >= AdaptLookback);
    double theta = 0.0;
    if(haveBarrier)
    {
        double mean, sd;
        Stats(g_coh, AdaptLookback, mean, sd);
        theta = mean + ThresholdK * sd;
    }

    // ---- Position management on the just-closed bar -----------------
    if(HasPosition(Magic))
    {
        // Order decayed back into randomness / anti-persistence: bank out.
        if(coherence < 0.0)
            CloseAll(Magic);
    }
    else if(haveBarrier && g_havePrev)
    {
        // EMERGENCE: coherence rises up through the self-calibrated barrier.
        bool emerged = (g_prevCoherence <= theta) && (coherence > theta);
        if(emerged && net != 0.0)
        {
            double atr = GetAtr();
            if(atr > 0.0)
            {
                double stretch = 1.0 + (coherence > 0.0 ? coherence : 0.0);
                double slDist  = atr * StopAtrMult;
                double tpDist  = atr * TpAtrMult * stretch;

                if(net > 0.0) OpenTrade(ORDER_TYPE_BUY,  slDist, tpDist);
                else          OpenTrade(ORDER_TYPE_SELL, slDist, tpDist);
            }
        }
    }

    // Record signal state for the next bar.
    AppendTrim(g_coh, coherence);
    g_prevCoherence = coherence;
    g_havePrev      = true;
}

//+------------------------------------------------------------------+
//| Directional Coherence over the last N signed bar returns.        |
//| Also returns net displacement of the window (sign = direction).  |
//| g_closes is newest-last, index 0 = oldest (== C# semantics).     |
//+------------------------------------------------------------------+
double ComputeCoherence(int n, double &net)
{
    int m = ArraySize(g_closes);
    net = g_closes[m - 1] - g_closes[m - 1 - n];

    int alternations = 0;
    int prevSign = 0;
    for(int i = m - n; i < m; i++)
    {
        double r = g_closes[i] - g_closes[i - 1];
        int s = (r > 0.0) ? 1 : ((r < 0.0) ? -1 : 0);
        if(s == 0) s = (prevSign != 0) ? prevSign : 1;   // flat bar = continuation, no flip
        if(prevSign != 0 && s != prevSign) alternations++;
        prevSign = s;
    }

    double denom = (n - 1) / 2.0;
    return (denom > 0.0) ? (1.0 - alternations / denom) : 0.0;
}

//+------------------------------------------------------------------+
//| ATR of the just-closed bar (shift 1).                            |
//+------------------------------------------------------------------+
double GetAtr()
{
    double buf[];
    ArraySetAsSeries(buf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, 3, buf) < 3) return 0.0;
    return buf[1];   // index 0 = forming bar, index 1 = just-closed bar
}

//+------------------------------------------------------------------+
//| Trailing mean/std of the last `lookback` values (population std).|
//+------------------------------------------------------------------+
void Stats(const double &series[], int lookback, double &mean, double &sd)
{
    int m = ArraySize(series);
    int start = m - lookback;

    double sum = 0.0;
    for(int i = start; i < m; i++) sum += series[i];
    mean = sum / lookback;

    double v = 0.0;
    for(int i = start; i < m; i++)
    {
        double d = series[i] - mean;
        v += d * d;
    }
    sd = MathSqrt(v / lookback);
}

//+------------------------------------------------------------------+
//| Open a trade with distance-based SL/TP (== C# OpenTrade).        |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE type, double slDist, double tpDist)
{
    double price = (type == ORDER_TYPE_BUY)
                   ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                   : SymbolInfoDouble(_Symbol, SYMBOL_BID);

    double sl = (type == ORDER_TYPE_BUY) ? price - slDist : price + slDist;
    double tp = (type == ORDER_TYPE_BUY) ? price + tpDist : price - tpDist;

    sl = NormalizeDouble(sl, _Digits);
    tp = NormalizeDouble(tp, _Digits);

    if(type == ORDER_TYPE_BUY) trade.Buy(Lots,  _Symbol, 0.0, sl, tp, "DCE");
    else                       trade.Sell(Lots, _Symbol, 0.0, sl, tp, "DCE");
}

//+------------------------------------------------------------------+
//| Append value (newest-last) and trim to MAX_KEEP (== C# Trim).    |
//+------------------------------------------------------------------+
void AppendTrim(double &arr[], double val)
{
    int n = ArraySize(arr);
    ArrayResize(arr, n + 1);
    arr[n] = val;

    n = ArraySize(arr);
    if(n > MAX_KEEP)
    {
        int excess = n - MAX_KEEP;                 // drop oldest `excess` elements
        for(int i = 0; i < n - excess; i++) arr[i] = arr[i + excess];
        ArrayResize(arr, n - excess);
    }
}

//+------------------------------------------------------------------+
//| New-bar detection                                                |
//+------------------------------------------------------------------+
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 magic on this symbol?                 |
//+------------------------------------------------------------------+
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 all positions for this magic on this symbol                |
//+------------------------------------------------------------------+
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);
    }
}
//+------------------------------------------------------------------+
