//+------------------------------------------------------------------+
//|                                   DirectionalEnergyAsymmetry.mq5  |
//|                                                          Algobot  |
//|                                   https://www.algobot.live        |
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

//  DirectionalEnergyAsymmetry - original OHLCV-only system.
//
//  Each closed bar is treated as a physical impulse:
//      mass     m = TickVolume
//      velocity v = Close - Open           (signed)
//      energy   e = m * v^2                (>= 0), direction = sign(v)
//
//  Over the last EnergyWindow bars we pool energy into up/down reservoirs:
//      E_up = sum e over up-bars,  E_dn = sum e over down-bars
//      A    = (E_up - E_dn) / (E_up + E_dn)        in [-1, +1]
//
//  A is standardised against its OWN recent distribution (running z-score
//  over AdaptLookback closed-bar A-values):
//      S = (A - mean_L(A)) / std_L(A)
//
//  Long  when S >= +EntryThreshold ; Short when S <= -EntryThreshold
//  Exit  when |S| <= ExitBand (reservoirs re-balanced) or on an opposite-side
//        entry-grade signal. Risk is ATR-based: stop = AtrMult*ATR,
//        take-profit = stop*RewardRatio.

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

//--- inputs (mirror C# DescribeParameters defaults) -----------------
input int    EnergyWindow   = 20;    // energy pooling window (bars)
input int    AdaptLookback  = 120;   // z-score lookback over the A-series
input double EntryThreshold = 1.2;   // entry in adaptive sigma units
input double ExitBand       = 0.3;   // |S| exit band (sigma units)
input int    AtrPeriod      = 14;    // ATR period
input double AtrMult        = 2.0;   // stop = AtrMult * ATR
input double RewardRatio    = 1.6;   // take-profit = stop * RewardRatio
input double Lots           = 0.10;  // fixed lot size
input long   Magic          = 8821;  // 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("DEA: failed to create ATR handle");
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Energy asymmetry A over `window` bars ending at bar `shift`.      |
//| Arrays are series-indexed (0 = forming bar). shift 1 = last close.|
//+------------------------------------------------------------------+
double ComputeA(const double &opn[], const double &cls[], const long &vol[],
                int shift, int window)
{
    double eUp = 0.0, eDn = 0.0;
    for(int k = 0; k < window; k++)
    {
        int idx = shift + k;
        double v = cls[idx] - opn[idx];                 // signed "velocity"
        double m = (vol[idx] > 0 ? (double)vol[idx] : 1.0); // "mass" fallback
        double e = m * v * v;                           // energy, always >= 0
        if(v > 0.0)      eUp += e;
        else if(v < 0.0) eDn += e;
    }
    double denom = eUp + eDn;
    return (denom > 0.0 ? (eUp - eDn) / denom : 0.0);
}

//+------------------------------------------------------------------+
//| Locate this-EA position on the current symbol.                   |
//+------------------------------------------------------------------+
bool GetPosition(long magic, ulong &ticket, long &ptype)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong t = PositionGetTicket(i);
        if(PositionSelectByTicket(t) &&
           PositionGetString(POSITION_SYMBOL) == _Symbol &&
           PositionGetInteger(POSITION_MAGIC) == magic)
        {
            ticket = t;
            ptype  = PositionGetInteger(POSITION_TYPE);
            return true;
        }
    }
    return false;
}

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

//+------------------------------------------------------------------+
void OnTick()
{
    // Act once per CLOSED bar (shift 0 is the forming bar).
    if(!IsNewBar()) return;

    // Pull enough history to build the A-series (one A per closed bar)
    // plus the energy window each A needs.
    int want = AdaptLookback + EnergyWindow + 2;
    double opn[], cls[];
    long   vol[];
    ArraySetAsSeries(opn, true);
    ArraySetAsSeries(cls, true);
    ArraySetAsSeries(vol, true);

    int gotO = CopyOpen(_Symbol, _Period, 0, want, opn);
    int gotC = CopyClose(_Symbol, _Period, 0, want, cls);
    int gotV = CopyTickVolume(_Symbol, _Period, 0, want, vol);
    if(gotO <= 0 || gotC <= 0 || gotV <= 0) return;

    int n = MathMin(gotO, MathMin(gotC, gotV));   // usable series length
    if(n < EnergyWindow + 1) return;              // need one closed A at least

    // Number of computable A-values (shift 1 .. n-EnergyWindow).
    int aCount = n - EnergyWindow;
    if(aCount < 20) return;                        // adaptive warmup (C#: _aSeries<20)

    // Self-adapting standardisation: z-score of current A against the last
    // `win` A-values (each A is the energy asymmetry of the window ending
    // at its own closed bar).
    int win = MathMin(AdaptLookback, aCount);

    double a = ComputeA(opn, cls, vol, 1, EnergyWindow);   // current A (shift 1)

    double mean = 0.0;
    for(int shift = 1; shift <= win; shift++)
        mean += ComputeA(opn, cls, vol, shift, EnergyWindow);
    mean /= win;

    double var = 0.0;
    for(int shift = 1; shift <= win; shift++)
    {
        double d = ComputeA(opn, cls, vol, shift, EnergyWindow) - mean;
        var += d * d;
    }
    var /= win;
    double std = MathSqrt(var);
    if(std < 1e-9) return;                          // flat energy series - no signal

    double s = (a - mean) / std;                    // adaptive signal (sigma units)

    // --- Position management: one position per magic ---
    ulong  ticket = 0;
    long   ptype  = -1;
    if(GetPosition(Magic, ticket, ptype))
    {
        bool balanced   = (MathAbs(s) <= ExitBand);
        bool oppToLong  = (ptype == POSITION_TYPE_BUY)  && (s <= -EntryThreshold);
        bool oppToShort = (ptype == POSITION_TYPE_SELL) && (s >=  EntryThreshold);
        if(balanced || oppToLong || oppToShort)
        {
            trade.PositionClose(ticket);
            PrintFormat("DEA EXIT %s s=%.2f (balanced=%s)",
                        (ptype == POSITION_TYPE_BUY ? "Buy" : "Sell"),
                        s, (balanced ? "true" : "false"));
        }
        return;   // re-entry evaluated on a later bar
    }

    // --- Dynamic, volatility-scaled risk (ATR of the last closed bar) ---
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atr, 0, 0, 3, atrBuf) < 2) return;
    double atr = atrBuf[1];
    if(atr <= 0.0 || atr == EMPTY_VALUE) return;
    double stopDist = atr * AtrMult;
    if(stopDist <= 0.0) return;

    // --- Entries: ride a significant, persistent energy surplus ---
    if(s >= EntryThreshold)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl    = NormalizeDouble(entry - stopDist, _Digits);
        double tp    = NormalizeDouble(entry + stopDist * RewardRatio, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "DEA long energy surplus");
        PrintFormat("DEA LONG s=%.2f A=%.2f atr=%.5f", s, a, atr);
    }
    else if(s <= -EntryThreshold)
    {
        double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl    = NormalizeDouble(entry + stopDist, _Digits);
        double tp    = NormalizeDouble(entry - stopDist * RewardRatio, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "DEA short energy deficit");
        PrintFormat("DEA SHORT s=%.2f A=%.2f atr=%.5f", s, a, atr);
    }
}
//+------------------------------------------------------------------+
