//+------------------------------------------------------------------+
//|                                    CapacitiveChargeReversion.mq5  |
//|                                                          Algobot  |
//|                                    https://www.algobot.live       |
//+------------------------------------------------------------------+
//  ORIGINAL HYPOTHESIS
//  Treat displacement of price from its local equilibrium as a VOLTAGE,
//  and model the market's stored "reversion potential" as CHARGE on a
//  leaky capacitor.  A brief excursion barely charges the capacitor and
//  leaks away; price that stays PERSISTENTLY on one side of its baseline
//  keeps pumping charge in faster than it leaks -- storing energy primed
//  to discharge back through equilibrium.
//
//    baseline  mu_t = mean(close, W)
//    scale     s_t  = mean|close - mu| over W           (MAD)
//    voltage   x_t  = (close_t - mu_t) / s_t
//    charge    Q_t  = lambda * Q_{t-1} + x_t            (leaky integrator)
//    trigger   theta_t = k * std(Q, Wq)                 (self-calibrating)
//
//  ENTRY  Q >= +theta -> over-charged up   -> SELL (fade to baseline)
//         Q <= -theta -> over-charged down -> BUY
//  EXIT   charge discharges inside ExitFraction*theta of zero, or flips
//         to the opposite extreme; ATR stop/take bound the risk.
//+------------------------------------------------------------------+
#property copyright "Algobot"
#property link      "https://www.algobot.live"
#property version   "1.00"
#property strict

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

//--- inputs (mapped from DescribeParameters) ---------------------------------
input int    Window        = 20;      // local equilibrium window (W)
input double Decay         = 0.80;    // leaky-integrator decay (lambda)
input int    ChargeWindow  = 100;     // window for self-calibrating threshold (Wq)
input double ThresholdMult = 2.0;     // k in theta = k * std(Q)
input double ExitFraction  = 0.30;    // discharge exit band fraction
input int    AtrPeriod     = 14;      // ATR period for risk bounds
input double StopAtrMult   = 2.0;     // SL = price +/- StopAtrMult * ATR
input double TakeAtrMult   = 3.0;     // TP = price -/+ TakeAtrMult * ATR
input double Lots          = 0.10;    // fixed trade volume
input long   Magic         = 700123;  // magic number

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

//--- state -------------------------------------------------------------------
double    g_q = 0.0;        // capacitor charge (leaky integrator)
double    g_charges[];      // history of Q (newest-last)
const int MaxHistory = 1200;

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

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

    g_q = 0.0;
    ArrayResize(g_charges, 0);
    return INIT_SUCCEEDED;
}

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

//+------------------------------------------------------------------+
//| Act once per newly-closed bar (no intrabar repaint)              |
//+------------------------------------------------------------------+
bool IsNewBar()
{
    static datetime last = 0;
    datetime cur = iTime(_Symbol, _Period, 0);
    if(cur != last){ last = cur; return true; }
    return false;
}

//+------------------------------------------------------------------+
//| Append a charge value to the newest-last history (capped)        |
//+------------------------------------------------------------------+
void PushCharge(double q)
{
    int n = ArraySize(g_charges);
    if(n >= MaxHistory)
    {
        // drop oldest, shift left, keep size == MaxHistory
        for(int i = 1; i < n; i++) g_charges[i-1] = g_charges[i];
        g_charges[n-1] = q;
    }
    else
    {
        ArrayResize(g_charges, n + 1);
        g_charges[n] = q;
    }
}

//+------------------------------------------------------------------+
//| Find the first open position for this symbol + magic             |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsNewBar()) return;

    // Need at least Window closed bars (shift 1..Window) plus the forming bar.
    if(Bars(_Symbol, _Period) < Window + 1) return;

    // The bar that just closed is shift 1.
    double closedClose = iClose(_Symbol, _Period, 1);

    // --- Local equilibrium (mean) over the last Window closed bars ---
    double mean = 0.0;
    for(int i = 1; i <= Window; i++) mean += iClose(_Symbol, _Period, i);
    mean /= Window;

    // --- Robust scale: mean absolute deviation ---
    double mad = 0.0;
    for(int i = 1; i <= Window; i++) mad += MathAbs(iClose(_Symbol, _Period, i) - mean);
    mad /= Window;
    if(mad <= 0.0) return;   // flat window -> no meaningful displacement, no charge update

    // --- Normalized displacement (voltage) and leaky-integrator charge ---
    double x = (closedClose - mean) / mad;
    g_q = Decay * g_q + x;
    PushCharge(g_q);

    int m = ArraySize(g_charges);
    if(m < ChargeWindow) return;

    // --- Self-calibrating threshold = k * std(charge) over ChargeWindow ---
    double qMean = 0.0;
    for(int i = m - ChargeWindow; i < m; i++) qMean += g_charges[i];
    qMean /= ChargeWindow;

    double qVar = 0.0;
    for(int i = m - ChargeWindow; i < m; i++)
    {
        double d = g_charges[i] - qMean;
        qVar += d * d;
    }
    qVar /= ChargeWindow;
    double qStd = MathSqrt(qVar);
    if(qStd <= 0.0) return;

    double threshold = ThresholdMult * qStd;
    double exitBand  = ExitFraction  * threshold;

    // --- ATR for risk bounds (value of the just-closed bar, shift 1) ---
    double atrBuf[];
    ArraySetAsSeries(atrBuf, true);
    if(CopyBuffer(g_atrHandle, 0, 0, 3, atrBuf) < 3) return;
    double atr = atrBuf[1];
    if(atr <= 0.0) return;

    // --- Manage an open position: discharge / opposite-extreme exit ---
    ulong ticket = 0;
    long  ptype  = 0;
    if(GetPosition(Magic, ticket, ptype))
    {
        bool exit;
        if(ptype == POSITION_TYPE_BUY)
            // opened on downward over-charge (Q <= -theta); exit as charge discharges up
            exit = (g_q >= -exitBand) || (g_q >= threshold);
        else
            exit = (g_q <= exitBand) || (g_q <= -threshold);

        if(exit) trade.PositionClose(ticket);
        return;   // one position at a time; re-enter on a later bar
    }

    // --- Entry: fade the accumulated charge back toward equilibrium ---
    if(g_q >= threshold)
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double sl = NormalizeDouble(price + StopAtrMult * atr, _Digits);
        double tp = NormalizeDouble(price - TakeAtrMult * atr, _Digits);
        trade.Sell(Lots, _Symbol, 0.0, sl, tp, "CCR-short");
    }
    else if(g_q <= -threshold)
    {
        double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double sl = NormalizeDouble(price - StopAtrMult * atr, _Digits);
        double tp = NormalizeDouble(price + TakeAtrMult * atr, _Digits);
        trade.Buy(Lots, _Symbol, 0.0, sl, tp, "CCR-long");
    }
}
//+------------------------------------------------------------------+
