//+------------------------------------------------------------------+
//|                                          RegularizedMAKit.mqh    |
//|                                                                  |
//|   Tikhonov-regularized moving averages kit (Mladen/Satchwell     |
//|   style), tuned for crypto markets (ETH, BNB, BTC).              |
//|                                                                  |
//|   Designed as a drop-in include for custom indicators such as    |
//|   Z-score, STARC bands, WaveTrend oscillator, etc.               |
//|                                                                  |
//|   None of the filters here are repainting: all are causal IIR    |
//|   recursive smoothers (no look-ahead, no global recomputation).  |
//|                                                                  |
//|   Uniform dispatcher:                                            |
//|     void RegMA(ENUM_REG_MA type,                                 |
//|                double &src[], double &dst[],                     |
//|                int total, int period, double lambda);            |
//|                                                                  |
//|   Or call individual functions for full parameter control:       |
//|     RegEma, RegCoral, RegJma, RegTma, RegMcGinley,               |
//|     RegVidya, RegKama                                            |
//|                                                                  |
//|   All arrays expected in series direction (newest at index 0).   |
//|                                                                  |
//|   USAGE EXAMPLE (inside an indicator):                           |
//|                                                                  |
//|     #include <RegularizedMAKit.mqh>                              |
//|                                                                  |
//|     input ENUM_REG_MA InpSmooth = REG_CORAL;                     |
//|     input int    InpPeriod  = 14;                                |
//|     input double InpLambda  = 0.5;                               |
//|                                                                  |
//|     double SmoothBuf[];                                          |
//|     ...                                                          |
//|     RegMA(InpSmooth, PriceBuf, SmoothBuf, rates_total,           |
//|           InpPeriod, InpLambda);                                 |
//|                                                                  |
//|   PARAMETER GUIDANCE for crypto:                                 |
//|     lambda 0.0 .. 0.3  => mild regularization, fast response     |
//|     lambda 0.3 .. 1.0  => balanced (recommended for ETH/BTC/BNB) |
//|     lambda 1.0 .. 3.0  => heavy smoothing, slow but very clean   |
//|     lambda > 3.0       => may over-dampen on intraday timeframes |
//|                                                                  |
//+------------------------------------------------------------------+
#ifndef REGULARIZED_MA_KIT_MQH
#define REGULARIZED_MA_KIT_MQH

//==================================================================
//  ENUMERATIONS
//==================================================================
enum ENUM_REG_MA {
   REG_CORAL,        // T3 with 6 regularized EMAs (Mladen-style Coral)
   REG_JMA,          // JMA approximation with lambda in final phase
   REG_TMA,          // Triangular MA with regularized second pass
   REG_MCGINLEY,     // McGinley with lambda-augmented denominator
   REG_VIDYA,        // VIDYA with regularized CMO
   REG_KAMA          // KAMA with smoothed (regularized) Efficiency Ratio
};

//==================================================================
//  CORE HELPER 1: Satchwell-style regularized EMA
//  ----------------------------------------------------------------
//  Recurrence:
//    y[t] = ( y[t-1]·(1+2λ) − λ·y[t-2] + α·(x[t] − y[t-1]) ) / (1+λ)
//  where α = 2/(period+1)
//
//  Properties:
//    * λ = 0   -> standard EMA exactly
//    * Unity steady-state gain for any λ ≥ 0
//    * Stable for λ in [0, ~10]; over-damped beyond
//==================================================================
void RegEma(double &src[], double &dst[], int total, int period, double lambda) {
   if(period < 2) period = 2;
   if(lambda < 0) lambda = 0;
   double a     = 2.0 / (period + 1.0);
   double denom = 1.0 + lambda;
   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   // Iterate oldest -> newest (i decreasing in series direction)
   for(int i = total - 1; i >= 0; i--) {
      double p = src[i];
      if(p == EMPTY_VALUE) { dst[i] = EMPTY_VALUE; continue; }
      double y1 = (i+1 < total && dst[i+1] != EMPTY_VALUE) ? dst[i+1] : p;
      double y2 = (i+2 < total && dst[i+2] != EMPTY_VALUE) ? dst[i+2] : y1;
      dst[i] = (y1 * (1.0 + 2.0*lambda) - lambda*y2 + a*(p - y1)) / denom;
   }
}

//==================================================================
//  CORE HELPER 2: Standard SMA (used by TMA)
//==================================================================
void RegKit_Sma(double &src[], double &dst[], int total, int period) {
   if(period < 1) period = 1;
   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   for(int i = 0; i < total; i++) {
      if(i + period > total) { dst[i] = EMPTY_VALUE; continue; }
      double s = 0; bool ok = true;
      for(int k = 0; k < period; k++) {
         if(src[i+k] == EMPTY_VALUE) { ok = false; break; }
         s += src[i+k];
      }
      dst[i] = ok ? s/period : EMPTY_VALUE;
   }
}

//==================================================================
//  1) CORAL REGULARIZED
//  ----------------------------------------------------------------
//  Tillson T3 with all 6 EMAs replaced by Satchwell-regularized EMAs.
//  Hot factor controls "lookahead" weighting (Tillson's original is
//  0.7; for crypto we keep 0.7 by default — gives a tight ribbon).
//==================================================================
void RegCoral(double &src[], double &dst[], int total, int period,
              double lambda, double hotFactor = 0.7) {
   if(period < 2) period = 2;
   double v = hotFactor;
   double e1[], e2[], e3[], e4[], e5[], e6[];
   ArrayResize(e1, total); ArraySetAsSeries(e1, true);
   ArrayResize(e2, total); ArraySetAsSeries(e2, true);
   ArrayResize(e3, total); ArraySetAsSeries(e3, true);
   ArrayResize(e4, total); ArraySetAsSeries(e4, true);
   ArrayResize(e5, total); ArraySetAsSeries(e5, true);
   ArrayResize(e6, total); ArraySetAsSeries(e6, true);

   // Six cascaded regularized EMAs
   RegEma(src, e1, total, period, lambda);
   RegEma(e1,  e2, total, period, lambda);
   RegEma(e2,  e3, total, period, lambda);
   RegEma(e3,  e4, total, period, lambda);
   RegEma(e4,  e5, total, period, lambda);
   RegEma(e5,  e6, total, period, lambda);

   double c1 = -v*v*v;
   double c2 =  3.0*v*v + 3.0*v*v*v;
   double c3 = -6.0*v*v - 3.0*v - 3.0*v*v*v;
   double c4 =  1.0 + 3.0*v + v*v*v + 3.0*v*v;

   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   for(int i = 0; i < total; i++) {
      if(e3[i]==EMPTY_VALUE || e4[i]==EMPTY_VALUE ||
         e5[i]==EMPTY_VALUE || e6[i]==EMPTY_VALUE) continue;
      dst[i] = c1*e6[i] + c2*e5[i] + c3*e4[i] + c4*e3[i];
   }
}

//==================================================================
//  2) JMA REGULARIZED
//  ----------------------------------------------------------------
//  Three-stage JMA approximation (public-domain; Jurik's real JMA is
//  proprietary). Regularization is injected at the final stage e2
//  via a λ·(e2[t-1] − e2[t-2]) inertia term, normalized by (1+λ).
//  phase ∈ [-100, +100]; 0 is the default neutral phase.
//==================================================================
void RegJma(double &src[], double &dst[], int total, int period,
            double lambda, double phase = 0) {
   if(period < 2) period = 2;
   if(lambda < 0) lambda = 0;
   double phaseRatio = (phase < -100) ? 0.5
                     : (phase >  100) ? 2.5
                                      : (phase/100.0 + 1.5);
   double beta  = 0.45*(period-1) / (0.45*(period-1) + 2.0);
   double alpha = MathPow(beta, phaseRatio);
   double denom = 1.0 + lambda;

   double e0[], e1[], e2[];
   ArrayResize(e0, total); ArraySetAsSeries(e0, true);
   ArrayResize(e1, total); ArraySetAsSeries(e1, true);
   ArrayResize(e2, total); ArraySetAsSeries(e2, true);
   ArrayInitialize(e0, EMPTY_VALUE);
   ArrayInitialize(e1, EMPTY_VALUE);
   ArrayInitialize(e2, EMPTY_VALUE);

   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);

   for(int i = total - 1; i >= 0; i--) {
      double p = src[i];
      if(p == EMPTY_VALUE) continue;
      double e0p = (i+1<total && e0[i+1]!=EMPTY_VALUE) ? e0[i+1] : p;
      double e1p = (i+1<total && e1[i+1]!=EMPTY_VALUE) ? e1[i+1] : 0;
      double e2p = (i+1<total && e2[i+1]!=EMPTY_VALUE) ? e2[i+1] : p;
      double e2pp= (i+2<total && e2[i+2]!=EMPTY_VALUE) ? e2[i+2] : e2p;

      e0[i] = (1.0 - alpha)*p + alpha*e0p;
      e1[i] = (p - e0[i])*(1.0 - beta) + beta*e1p;

      // Standard JMA final stage:
      double e2_unreg = (e0[i] + phaseRatio*e1[i] - e2p) * MathPow(1.0-alpha, 2)
                        + MathPow(alpha, 2)*e2p + e2p;

      // Regularize: add inertia term, normalize
      e2[i] = (e2_unreg + lambda*(e2p - e2pp)) / denom;
      // Restore unity steady-state by absorbing the (1+λ) properly:
      // since e2_unreg already approaches p in steady state, dividing
      // by (1+λ) would shrink the output. We instead add only the
      // inertia delta (which is zero in steady state) to e2_unreg:
      e2[i] = e2_unreg + (lambda/(1.0+lambda)) * (e2p - e2pp);
      dst[i] = e2[i];
   }
}

//==================================================================
//  3) TMA REGULARIZED
//  ----------------------------------------------------------------
//  Triangular MA: SMA(SMA(price, n), n) where n = (period+1)/2.
//  Regularization is applied on the SECOND pass: the second SMA is
//  replaced with a Satchwell-regularized EMA of half-period.
//  This blends the smoothness of TMA with the inertia of Tikhonov.
//==================================================================
void RegTma(double &src[], double &dst[], int total, int period, double lambda) {
   int n = MathMax(2, (period+1)/2);
   double tmp[]; ArrayResize(tmp, total); ArraySetAsSeries(tmp, true);
   RegKit_Sma(src, tmp, total, n);
   RegEma(tmp, dst, total, n, lambda);
}

//==================================================================
//  4) McGINLEY REGULARIZED
//  ----------------------------------------------------------------
//  Standard McGinley:
//    y[t] = y[t-1] + (x[t] − y[t-1]) / ( N · (x[t]/y[t-1])^4 )
//
//  Regularized version: add λ to the denominator so larger λ damps
//  the response symmetrically:
//    y[t] = y[t-1] + (x[t] − y[t-1]) / ( N·(x[t]/y[t-1])^4 + λ )
//
//  Notes:
//    * Guards against ratio explosion when y[t-1] near zero
//    * λ=0 recovers original McGinley exactly
//==================================================================
void RegMcGinley(double &src[], double &dst[], int total, int period, double lambda) {
   if(period < 2) period = 2;
   if(lambda < 0) lambda = 0;
   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   for(int i = total - 1; i >= 0; i--) {
      double p = src[i];
      if(p == EMPTY_VALUE) continue;
      double prev = (i+1 < total && dst[i+1] != EMPTY_VALUE) ? dst[i+1] : p;
      // Guard against degenerate prev
      double safePrev = (MathAbs(prev) < 1e-10) ? p : prev;
      double ratio   = p / safePrev;
      double r4      = MathPow(ratio, 4);
      double denom   = period * r4 + lambda;
      // Final safety: denom should never be < 1e-8
      if(denom < 1e-8) denom = 1e-8;
      dst[i] = prev + (p - prev) / denom;
   }
}

//==================================================================
//  5) VIDYA REGULARIZED (Type A — regularization on the CMO itself)
//  ----------------------------------------------------------------
//  Standard VIDYA:
//    α = 2/(period+1)
//    k = |CMO(cmoPeriod)|     ∈ [0,1]
//    y[t] = α·k·x[t] + (1 − α·k)·y[t-1]
//
//  Regularized: smooth the CMO via RegEma before taking |·|.
//  This removes the spiky behaviour of raw CMO on crypto data,
//  which has many isolated ticks that flip CMO sign abruptly.
//
//  cmoPeriod defaults to 9 (Chande's recommendation).
//==================================================================
void RegVidya(double &src[], double &dst[], int total, int period,
              double lambda, int cmoPeriod = 9) {
   if(period < 2) period = 2;
   if(cmoPeriod < 2) cmoPeriod = 2;
   if(lambda < 0) lambda = 0;

   // 1) Raw CMO  (signed, range [-1, +1])
   double cmoRaw[]; ArrayResize(cmoRaw, total); ArraySetAsSeries(cmoRaw, true);
   ArrayInitialize(cmoRaw, EMPTY_VALUE);
   for(int i = 0; i < total - cmoPeriod; i++) {
      double up = 0, dn = 0;
      bool ok = true;
      for(int k = 0; k < cmoPeriod; k++) {
         if(src[i+k]   == EMPTY_VALUE ||
            src[i+k+1] == EMPTY_VALUE) { ok = false; break; }
         double diff = src[i+k] - src[i+k+1];
         if(diff > 0) up += diff;
         else         dn -= diff;
      }
      if(!ok)              { cmoRaw[i] = EMPTY_VALUE; continue; }
      if((up + dn) == 0)   { cmoRaw[i] = 0;          continue; }
      cmoRaw[i] = (up - dn) / (up + dn);
   }

   // 2) Smoothed CMO via RegEma
   double cmoSm[]; ArrayResize(cmoSm, total); ArraySetAsSeries(cmoSm, true);
   RegEma(cmoRaw, cmoSm, total, cmoPeriod, lambda);

   // 3) VIDYA recurrence with |smoothed CMO| as adaptive factor
   double a = 2.0 / (period + 1.0);
   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   for(int i = total - 1; i >= 0; i--) {
      if(src[i] == EMPTY_VALUE || cmoSm[i] == EMPTY_VALUE) continue;
      double k = MathAbs(cmoSm[i]);
      if(k > 1.0) k = 1.0;
      double prev = (i+1 < total && dst[i+1] != EMPTY_VALUE) ? dst[i+1] : src[i];
      dst[i] = a*k*src[i] + (1.0 - a*k)*prev;
   }
}

//==================================================================
//  6) KAMA REGULARIZED (ER smoothed)
//  ----------------------------------------------------------------
//  Standard KAMA:
//    direction  = |x[t] − x[t-period]|
//    volatility = Σ |x[t-k] − x[t-k-1]|     for k = 0..period-1
//    ER         = direction / volatility
//    SC         = ( ER·(fastSC − slowSC) + slowSC )²
//    y[t]       = y[t-1] + SC·(x[t] − y[t-1])
//
//  Regularized: smooth the raw ER through RegEma first. The smoothed
//  ER produces a stabler SC, which prevents the typical KAMA "flat
//  spots" during fast crypto reversals.
//
//  fast/slow default to Perry Kaufman's original (2, 30).
//==================================================================
void RegKama(double &src[], double &dst[], int total, int period,
             double lambda, int fast = 2, int slow = 30) {
   if(period < 2) period = 2;
   if(fast < 2)   fast = 2;
   if(slow < fast) slow = fast + 1;
   if(lambda < 0) lambda = 0;
   double fastSC = 2.0 / (fast + 1.0);
   double slowSC = 2.0 / (slow + 1.0);

   // 1) Raw efficiency ratio
   double erRaw[]; ArrayResize(erRaw, total); ArraySetAsSeries(erRaw, true);
   ArrayInitialize(erRaw, EMPTY_VALUE);
   for(int i = 0; i < total - period; i++) {
      if(src[i] == EMPTY_VALUE || src[i+period] == EMPTY_VALUE) continue;
      double dir = MathAbs(src[i] - src[i+period]);
      double vol = 0;
      bool ok = true;
      for(int k = 0; k < period; k++) {
         if(src[i+k] == EMPTY_VALUE || src[i+k+1] == EMPTY_VALUE) { ok = false; break; }
         vol += MathAbs(src[i+k] - src[i+k+1]);
      }
      if(!ok || vol <= 0) { erRaw[i] = 0; continue; }
      erRaw[i] = dir / vol;     // in [0, 1]
   }

   // 2) Smoothed ER via RegEma
   double erSm[]; ArrayResize(erSm, total); ArraySetAsSeries(erSm, true);
   RegEma(erRaw, erSm, total, period, lambda);

   // 3) KAMA recurrence using smoothed ER
   ArraySetAsSeries(dst, true);
   ArrayInitialize(dst, EMPTY_VALUE);
   for(int i = total - 1; i >= 0; i--) {
      if(src[i] == EMPTY_VALUE || erSm[i] == EMPTY_VALUE) continue;
      double er = erSm[i];
      if(er < 0) er = 0;
      if(er > 1) er = 1;
      double sc = MathPow(er*(fastSC - slowSC) + slowSC, 2);
      double prev = (i+1 < total && dst[i+1] != EMPTY_VALUE) ? dst[i+1] : src[i];
      dst[i] = prev + sc*(src[i] - prev);
   }
}

//==================================================================
//  UNIFIED DISPATCHER
//  ----------------------------------------------------------------
//  Uses default extra-parameter values:
//    Coral:    hotFactor  = 0.7
//    JMA:      phase      = 0
//    VIDYA:    cmoPeriod  = 9
//    KAMA:     fast=2, slow=30
//
//  For full control over those extras, call the individual functions.
//==================================================================
void RegMA(ENUM_REG_MA type, double &src[], double &dst[],
           int total, int period, double lambda) {
   switch(type) {
      case REG_CORAL:    RegCoral    (src, dst, total, period, lambda);       break;
      case REG_JMA:      RegJma      (src, dst, total, period, lambda);       break;
      case REG_TMA:      RegTma      (src, dst, total, period, lambda);       break;
      case REG_MCGINLEY: RegMcGinley (src, dst, total, period, lambda);       break;
      case REG_VIDYA:    RegVidya    (src, dst, total, period, lambda);       break;
      case REG_KAMA:     RegKama     (src, dst, total, period, lambda);       break;
      default:           RegEma      (src, dst, total, period, lambda);       break;
   }
}

//==================================================================
//  ENUM NAME HELPER (for indicator short-names / debugging)
//==================================================================
string RegMA_Name(ENUM_REG_MA t) {
   switch(t) {
      case REG_CORAL:    return "CoralReg";
      case REG_JMA:      return "JmaReg";
      case REG_TMA:      return "TmaReg";
      case REG_MCGINLEY: return "McGinleyReg";
      case REG_VIDYA:    return "VidyaReg";
      case REG_KAMA:     return "KamaReg";
   }
   return "?";
}

#endif // REGULARIZED_MA_KIT_MQH
//+------------------------------------------------------------------+
