//+------------------------------------------------------------------+
//|                                   AsymmetricCaptureMomentum.mq5   |
//|                                                          Algobot  |
//|                                     https://www.algobot.live      |
//+------------------------------------------------------------------+
//  ASYMMETRIC CAPTURE MOMENTUM
//  ----------------------------------------------------------------------------
//  Split bar-to-bar returns into up-steps and down-steps and compare their
//  AVERAGE MAGNITUDE (not count). The side that owns the move advances price in
//  large bursts while the counter-side only nibbles it back.
//
//      r_i = (C_i - C_{i-1}) / C_{i-1}
//      G+  = mean( r_i  for r_i > 0 )        // avg up-step magnitude
//      G-  = mean( -r_i for r_i < 0 )        // avg down-step magnitude
//      A   = (G+ - G-) / (G+ + G-)  in [-1, +1]   // "capture asymmetry"
//
//  Self-adapting trigger  T = ThresholdK * stdev(A history).
//  A fresh crossing of +/-T plus a same-sign "release confirmation" (the last
//  return agrees in sign with A) arms an entry. Opposite crossings flip.
//  Risk: custom SMA-of-TR ATR, asymmetric reward multiple.
//+------------------------------------------------------------------+
#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    Window        = 30;      // Window  (10..100 step 5)
input double ThresholdK    = 1.0;     // ThresholdK (0.3..3.0 step 0.1)
input int    AtrPeriod     = 14;      // AtrPeriod (7..40 step 1)
input double StopAtrMult   = 2.0;     // StopAtrMult (0.5..5.0 step 0.5)
input double TpRewardRatio = 1.5;     // TpRewardRatio (0.5..4.0 step 0.5)
input double Lots          = 0.10;    // Lots (0.01..1.0 step 0.05)
input long   Magic         = 990210;  // Magic number

//--- stateful history (index 0 = oldest, last = newest) matching the C# Lists
double   g_closes[];      // completed-bar closes
double   g_barsHigh[];    // completed-bar highs  (for custom ATR)
double   g_barsLow[];     // completed-bar lows
double   g_barsClose[];   // completed-bar closes (for custom ATR)
double   g_returns[];     // relative returns
double   g_asymHist[];    // history of A

datetime g_lastBarTime = 0;
double   g_prevAsym    = 0.0;
bool     g_haveAsym    = false;
bool     g_firstBar    = true;

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

    ArrayResize(g_closes,    0);
    ArrayResize(g_barsHigh,  0);
    ArrayResize(g_barsLow,   0);
    ArrayResize(g_barsClose, 0);
    ArrayResize(g_returns,   0);
    ArrayResize(g_asymHist,  0);

    g_lastBarTime = 0;
    g_prevAsym    = 0.0;
    g_haveAsym    = false;
    g_firstBar    = true;

    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // No built-in indicator handles are created: ATR is computed manually
    // (SMA of TR) to exactly match the C# self-contained ComputeAtr().
}

//+------------------------------------------------------------------+
void OnTick()
{
    if(Bars(_Symbol, _Period) < 3) return;

    // --- act once per completed bar ---
    datetime forming = iTime(_Symbol, _Period, 0);   // current forming bar
    if(forming == g_lastBarTime) return;             // same bar -> wait
    bool firstBar = (g_lastBarTime == 0);
    g_lastBarTime = forming;
    if(firstBar) return;                             // need a reference first

    ProcessClosedBar();
}

//+------------------------------------------------------------------+
//| Process the just-closed bar (shift 1)                            |
//+------------------------------------------------------------------+
void ProcessClosedBar()
{
    double closedClose = iClose(_Symbol, _Period, 1);
    double closedHigh  = iHigh(_Symbol,  _Period, 1);
    double closedLow   = iLow(_Symbol,   _Period, 1);

    // --- add bar ---
    PushBack(g_barsHigh,  closedHigh);
    PushBack(g_barsLow,   closedLow);
    PushBack(g_barsClose, closedClose);

    // --- compute relative return using the previous stored close ---
    int nc = ArraySize(g_closes);
    if(nc > 0)
    {
        double prev = g_closes[nc - 1];
        if(prev > 0.0) PushBack(g_returns, (closedClose - prev) / prev);
    }
    PushBack(g_closes, closedClose);

    // --- trim histories ---
    int keep = Window + 5;
    if(AtrPeriod + 5 > keep) keep = AtrPeriod + 5;
    TrimFront(g_closes,    keep);
    TrimFront(g_barsHigh,  keep);
    TrimFront(g_barsLow,   keep);
    TrimFront(g_barsClose, keep);
    TrimFront(g_returns,   keep);
    TrimFront(g_asymHist,  4 * Window);

    int nr = ArraySize(g_returns);
    if(nr < Window)                       return;
    if(ArraySize(g_barsClose) < AtrPeriod + 1) return;

    // --- capture asymmetry over the last W returns ---
    double sumUp = 0.0, sumDn = 0.0;
    int    nUp = 0, nDn = 0;
    for(int i = nr - Window; i < nr; i++)
    {
        double r = g_returns[i];
        if(r > 0.0)      { sumUp += r;  nUp++; }
        else if(r < 0.0) { sumDn += -r; nDn++; }
    }
    double gPlus  = (nUp > 0) ? sumUp / nUp : 0.0;
    double gMinus = (nDn > 0) ? sumDn / nDn : 0.0;
    double denom  = gPlus + gMinus;
    if(denom <= 0.0) return;                          // dead-flat window
    double asym = (gPlus - gMinus) / denom;           // in [-1, +1]

    PushBack(g_asymHist, asym);
    double thr = AdaptiveThreshold();

    // --- signal: fresh crossing + release confirmation ---
    double lastR = g_returns[nr - 1];
    int signal = 0;
    if(g_haveAsym)
    {
        if(g_prevAsym <= thr  && asym > thr  && lastR > 0.0)      signal = +1;
        else if(g_prevAsym >= -thr && asym < -thr && lastR < 0.0) signal = -1;
    }
    g_prevAsym = asym;
    g_haveAsym = true;
    if(signal == 0) return;

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

    // --- flip on opposite signal, then enter if flat ---
    bool haveLong, haveShort;
    int cnt = CountPositions(Magic, haveLong, haveShort);
    if((signal > 0 && haveShort) || (signal < 0 && haveLong))
    {
        CloseAll(Magic);
        cnt = CountPositions(Magic, haveLong, haveShort);
    }

    if(cnt == 0)
        EnterTrade(signal, atr, asym);
}

//+------------------------------------------------------------------+
//| Enter a trade with ATR-scaled asymmetric SL/TP                   |
//+------------------------------------------------------------------+
void EnterTrade(int dir, double atr, double asym)
{
    double risk   = StopAtrMult * atr;
    double reward = risk * TpRewardRatio;

    if(dir > 0)
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl = NormalizeDouble(price - risk,   _Digits);
        double tp = NormalizeDouble(price + reward, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "ACM-long");
        PrintFormat("ACM long  A=%.3f atr=%.5f", asym, atr);
    }
    else
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl = NormalizeDouble(price + risk,   _Digits);
        double tp = NormalizeDouble(price - reward, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "ACM-short");
        PrintFormat("ACM short A=%.3f atr=%.5f", asym, atr);
    }
}

//+------------------------------------------------------------------+
//| Adaptive trigger: k * stdev(A history), clamped to a sane band   |
//+------------------------------------------------------------------+
double AdaptiveThreshold()
{
    int n = ArraySize(g_asymHist);
    if(n < 5) return 0.15;                            // fallback until warmed

    double mean = 0.0;
    for(int i = 0; i < n; i++) mean += g_asymHist[i];
    mean /= n;

    double var = 0.0;
    for(int i = 0; i < n; i++) { double d = g_asymHist[i] - mean; var += d * d; }
    var /= n;                                         // population variance

    double thr = ThresholdK * MathSqrt(var);
    if(thr < 0.05) thr = 0.05;
    if(thr > 0.90) thr = 0.90;
    return thr;
}

//+------------------------------------------------------------------+
//| Self-contained Wilder-style ATR (simple mean of True Range)      |
//+------------------------------------------------------------------+
double ComputeAtr(int period)
{
    int n = ArraySize(g_barsClose);
    if(n < period + 1) return 0.0;

    double sum = 0.0;
    for(int i = n - period; i < n; i++)
    {
        double curH  = g_barsHigh[i];
        double curL  = g_barsLow[i];
        double prevC = g_barsClose[i - 1];

        double tr = curH - curL;
        double a  = MathAbs(curH - prevC);
        double b  = MathAbs(curL - prevC);
        if(a > tr) tr = a;
        if(b > tr) tr = b;
        sum += tr;
    }
    return sum / period;
}

//+------------------------------------------------------------------+
//| Position helpers                                                 |
//+------------------------------------------------------------------+
int CountPositions(long magic, bool &haveLong, bool &haveShort)
{
    int cnt = 0;
    haveLong  = false;
    haveShort = false;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            cnt++;
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) haveLong = true;
            else                                                       haveShort = true;
        }
    }
    return cnt;
}

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);
    }
}

//+------------------------------------------------------------------+
//| Dynamic-array helpers (index 0 = oldest, mirrors C# List)        |
//+------------------------------------------------------------------+
void PushBack(double &arr[], double v)
{
    int n = ArraySize(arr);
    ArrayResize(arr, n + 1);
    arr[n] = v;
}

void TrimFront(double &arr[], int maxN)
{
    int n = ArraySize(arr);
    if(n <= maxN) return;
    int remove = n - maxN;
    for(int i = 0; i < maxN; i++) arr[i] = arr[i + remove];
    ArrayResize(arr, maxN);
}
//+------------------------------------------------------------------+
