//+------------------------------------------------------------------+
//|                                       TemporalAsymmetryTrend.mq5  |
//|                                                          Algobot  |
//|                                     https://www.algobot.live      |
//+------------------------------------------------------------------+
//  TemporalAsymmetryTrend
//  -------------------------------------------------------------------
//  Trades the "arrow of time" in the close-to-close return series.
//  A market in equilibrium is time-reversible, so every odd, time-
//  antisymmetric statistic of its increments vanishes. A driven
//  (out-of-equilibrium) market has an irreversible price path; the
//  sign of that irreversibility gives the trade direction.
//
//  Over the last N returns r_i = close_i - close_{i-1} (chronological):
//
//      A    = mean_i [ r_i * r_{i-1} * (r_i - r_{i-1}) ]   (i = 1..N-1)
//      Ahat = A / sigma^3      (sigma = population std of the returns)
//
//   * Long  when Ahat >  Threshold
//   * Short when Ahat < -Threshold
//   * Exit  when Ahat flips sign against an open position (drive reverses)
//   * SL / TP scale with current ATR.
//
//  Single timeframe: everything uses the chart's symbol / period.
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mirror DescribeParameters / GetInput defaults) ---------
input int    Window    = 40;    // N returns in the time-asymmetry window
input double Threshold = 0.20;  // |Ahat| a signal must clear to count as "driven"
input int    AtrPeriod = 14;    // ATR length for stop / target distances
input double AtrSlMult = 2.0;   // stop-loss   = entry -/+ AtrSlMult * ATR
input double AtrTpMult = 3.0;   // take-profit = entry +/- AtrTpMult * ATR
input double Lots      = 0.10;  // order volume
input long   Magic     = 5207;  // magic number

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

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

    g_atr = iATR(_Symbol, _Period, AtrPeriod);
    if(g_atr == INVALID_HANDLE)
    {
        Print("TemporalAsymmetryTrend: failed to create ATR handle");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
void OnTick()
{
    // Act once, when the prior bar has finished (matches new-bar detection).
    if(!IsNewBar()) return;

    // Need N+1 closes for N returns, plus ATR history (+ the forming bar).
    int needed = MathMax(Window + 1, AtrPeriod + 1) + 1;
    if(Bars(_Symbol, _Period) < needed + 2) return;

    // ---- Time-reversal asymmetry coefficient Ahat over the last N returns ----
    double aHat = 0.0;
    if(!ComputeAhat(Window, aHat)) return;   // flat window (sigma==0) or NaN/Inf -> skip

    // ---- Manage an existing position: exit when the drive reverses ----
    if(ManageOpenPositions(aHat))
        return;   // one position per magic; do not stack.

    // ---- No position: enter only on a significant, directed drive ----
    bool goLong  = aHat >  Threshold;
    bool goShort = aHat < -Threshold;
    if(!goLong && !goShort) return;

    // ---- ATR (of the completed bar, shift 1, to match the C# window) ----
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atr, 0, 1, 1, atrBuf) < 1) return;
    double atr = atrBuf[0];
    if(atr <= 0.0) return;

    TryEnter(goLong, atr, aHat);
}

//+------------------------------------------------------------------+
//| Ahat = A / sigma^3 over the last `window` close-to-close returns. |
//| Returns false if the window is flat (sigma ~ 0) or Ahat invalid.  |
//| Chronological returns: r[k] newer as k grows; r[window-1] is the  |
//| return of the last completed bar (close[1]-close[2]).             |
//+------------------------------------------------------------------+
bool ComputeAhat(const int window, double &ahat)
{
    double r[];
    ArrayResize(r, window);

    double mean = 0.0;
    for(int k = 0; k < window; k++)
    {
        int shiftNew = window - k;       // newer close of this return
        int shiftOld = window + 1 - k;   // older close of this return
        double ret = iClose(_Symbol, _Period, shiftNew)
                   - iClose(_Symbol, _Period, shiftOld);
        r[k]  = ret;
        mean += ret;
    }
    mean /= window;

    // Volatility scale (population std of the returns).
    double var = 0.0;
    for(int k = 0; k < window; k++)
    {
        double d = r[k] - mean;
        var += d * d;
    }
    var /= window;
    double sigma = MathSqrt(var);
    if(sigma <= 1e-12) return false;

    // Lagged cubic asymmetry A = mean_i [ r_i * r_{i-1} * (r_i - r_{i-1}) ].
    double a = 0.0;
    for(int k = 1; k < window; k++)
    {
        double cur  = r[k];
        double prev = r[k - 1];
        a += cur * prev * (cur - prev);
    }
    a /= (window - 1);

    ahat = a / (sigma * sigma * sigma);
    if(!MathIsValidNumber(ahat)) return false;   // NaN / Inf guard
    return true;
}

//+------------------------------------------------------------------+
//| Close positions of this magic whose drive has reversed.          |
//| Returns true if any position (of this magic/symbol) exists,      |
//| signalling the caller to stop (no stacking).                     |
//+------------------------------------------------------------------+
bool ManageOpenPositions(const double aHat)
{
    bool hasAny = false;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(!PositionSelectByTicket(t)) continue;
        if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
        if(PositionGetInteger(POSITION_MAGIC)  != Magic)  continue;

        hasAny = true;
        long ptype = PositionGetInteger(POSITION_TYPE);
        bool driveReversed =
            (ptype == POSITION_TYPE_BUY  && aHat < 0.0) ||
            (ptype == POSITION_TYPE_SELL && aHat > 0.0);
        if(driveReversed)
        {
            bool cr = trade.PositionClose(t);
            PrintFormat("TemporalAsymmetryTrend EXIT %s ticket %I64u Ahat %.3f -> %s",
                        (ptype == POSITION_TYPE_BUY ? "Buy" : "Sell"),
                        t, aHat, (cr ? "ok" : "fail"));
        }
    }
    return hasAny;
}

//+------------------------------------------------------------------+
//| Open a new position with ATR-scaled SL / TP.                     |
//+------------------------------------------------------------------+
void TryEnter(const bool isLong, const double atr, const double aHat)
{
    if(AtrSlMult * atr <= 0.0) return;

    int    digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double price, sl, tp;
    bool   ok;
    string cmt;

    if(isLong)
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        sl    = NormalizeDouble(price - AtrSlMult * atr, digits);
        tp    = NormalizeDouble(price + AtrTpMult * atr, digits);
        cmt   = "TAT-long";
        ok    = trade.Buy(Lots, _Symbol, 0.0, sl, tp, cmt);
    }
    else
    {
        price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        sl    = NormalizeDouble(price + AtrSlMult * atr, digits);
        tp    = NormalizeDouble(price - AtrTpMult * atr, digits);
        cmt   = "TAT-short";
        ok    = trade.Sell(Lots, _Symbol, 0.0, sl, tp, cmt);
    }

    PrintFormat("TemporalAsymmetryTrend %s @ %.5f SL %.5f TP %.5f Ahat %.3f ATR %.5f -> %s",
                (isLong ? "LONG" : "SHORT"), price, sl, tp, aHat, atr,
                (ok ? "ok" : "fail"));
}

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