//+------------------------------------------------------------------+
//|  Asymmetric Volatility Bands | JV  –  MT4  (fix v1.3)            |
//+------------------------------------------------------------------+
#property copyright "jv_indicators (original), MT4 port"
#property version   "1.03"
#property strict
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1  Yellow
#property indicator_color2  Aqua
#property indicator_color3  Gainsboro
#property indicator_style1  STYLE_SOLID
#property indicator_style2  STYLE_SOLID
#property indicator_style3  STYLE_DOT
#property indicator_width1  1
#property indicator_width2  1
#property indicator_width3  1

//─── Inputs ────────────────────────────────────────────────────────
extern string  Basis_Type      = "ALMA";
extern int     Basis_Length    = 20;
extern double  ALMA_Offset     = 0.85;
extern double  ALMA_Sigma      = 6.0;
extern int     ATR_Length      = 14;
extern double  Band_Multiplier = 1.0;
extern int     Skew_Length     = 20;
extern double  Ratio_Power     = 0.5;
extern bool    Clamp_Ratio     = true;
extern double  Ratio_Min       = 0.5;
extern double  Ratio_Max       = 2.0;
extern bool    Show_Basis      = true;

//─── Buffers ───────────────────────────────────────────────────────
double UpperBand[];
double LowerBand[];
double BasisBuf[];

#define EPS 1e-10

//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorBuffers(3);

   SetIndexBuffer(0, UpperBand);
   SetIndexBuffer(1, LowerBand);
   SetIndexBuffer(2, BasisBuf);

   SetIndexLabel(0, "Upper Band");
   SetIndexLabel(1, "Lower Band");
   SetIndexLabel(2, "Basis");

   if(!Show_Basis) SetIndexStyle(2, DRAW_NONE);

   SetIndexEmptyValue(0, EMPTY_VALUE);
   SetIndexEmptyValue(1, EMPTY_VALUE);
   SetIndexEmptyValue(2, EMPTY_VALUE);

   int need = MathMax(Basis_Length, MathMax(ATR_Length, Skew_Length)) + 5;
   IndicatorDigits(Digits);
   SetIndexDrawBegin(0, need);
   SetIndexDrawBegin(1, need);
   SetIndexDrawBegin(2, need);

   IndicatorShortName("AVB("+Basis_Type+","+IntegerToString(Basis_Length)+")");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
double GetBasis(int bar, int total, double &almaW[], double almaWS)
{
   int L = Basis_Length;

   if(Basis_Type == "EMA" || Basis_Type == "RMA")
      return EMPTY_VALUE;

   if(Basis_Type == "SMA")
   {
      if(bar + L - 1 >= total) return EMPTY_VALUE;
      double s = 0;
      for(int j = 0; j < L; j++) s += Close[bar+j];
      return s / L;
   }

   if(Basis_Type == "WMA")
   {
      if(bar + L - 1 >= total) return EMPTY_VALUE;
      double s = 0, ws = (double)L*(L+1)/2.0;
      for(int j = 0; j < L; j++) s += Close[bar+j]*(L-j);
      return s / ws;
   }

   if(Basis_Type == "VWMA")
   {
      if(bar + L - 1 >= total) return EMPTY_VALUE;
      double sv=0, vv=0;
      for(int j = 0; j < L; j++)
      {
         double v = (double)Volume[bar+j];
         sv += Close[bar+j]*v;
         vv += v;
      }
      return vv > EPS ? sv/vv : EMPTY_VALUE;
   }

   // ALMA
   if(bar + L - 1 >= total) return EMPTY_VALUE;
   if(almaWS < EPS) return EMPTY_VALUE;
   double s = 0;
   for(int j = 0; j < L; j++) s += Close[bar+j] * almaW[j];
   return s / almaWS;
}

//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int      &spread[])
{
   int total = rates_total;
   int need  = MathMax(Basis_Length, MathMax(ATR_Length, Skew_Length)) + 5;
   if(total < need) return 0;

   int limit;
   if(prev_calculated == 0)
      limit = total - need;
   else
      limit = total - prev_calculated + 1;
   if(limit <= 0) limit = 1;

   // ALMA súlyok
   double almaW[];
   double almaWS = 0;
   int L = Basis_Length;
   ArrayResize(almaW, L);
   {
      double m  = ALMA_Offset*(L-1);
      double sg = L / ALMA_Sigma;
      for(int j = 0; j < L; j++)
      {
         int pi = L-1-j;
         almaW[j] = MathExp(-(((double)pi-m)*((double)pi-m))/(2.0*sg*sg));
         almaWS  += almaW[j];
      }
   }

   // EMA / RMA rekurzív basis
   bool basisIsRecursive = (Basis_Type=="EMA" || Basis_Type=="RMA");
   if(basisIsRecursive && prev_calculated == 0)
   {
      double k = (Basis_Type=="EMA") ? 2.0/(L+1) : 1.0/L;
      BasisBuf[total-1] = Close[total-1];
      for(int i = total-2; i >= 0; i--)
         BasisBuf[i] = Close[i]*k + BasisBuf[i+1]*(1.0-k);
   }
   else if(basisIsRecursive)
   {
      double k = (Basis_Type=="EMA") ? 2.0/(L+1) : 1.0/L;
      for(int i = limit-1; i >= 0; i--)
         BasisBuf[i] = Close[i]*k + BasisBuf[i+1]*(1.0-k);
   }

   // Directional Volatility
   double upVol[], dnVol[];
   ArrayResize(upVol, total);
   ArrayResize(dnVol, total);
   ArrayInitialize(upVol, EPS);
   ArrayInitialize(dnVol, EPS);

   {
      int SL = Skew_Length;
      if(total >= SL + 2)
      {
         double sqU[], sqD[];
         ArrayResize(sqU, total);
         ArrayResize(sqD, total);
         ArrayInitialize(sqU, 0);
         ArrayInitialize(sqD, 0);

         for(int i = 0; i < total-1; i++)
         {
            if(Close[i+1] <= 0) continue;
            double lr = MathLog(Close[i]/Close[i+1]);
            if(lr > 0) sqU[i] = lr*lr;
            else       sqD[i] = lr*lr;
         }

         double su=0, sd=0;
         for(int j = 0; j < SL; j++)
         {
            su += sqU[total-1-j];
            sd += sqD[total-1-j];
         }
         int fv = total - SL;
         upVol[fv] = MathSqrt(su / SL);
         dnVol[fv] = MathSqrt(sd / SL);

         for(int b = fv-1; b >= 0; b--)
         {
            su += sqU[b] - sqU[b+SL];
            sd += sqD[b] - sqD[b+SL];
            if(su < 0) su = 0;
            if(sd < 0) sd = 0;
            upVol[b] = MathSqrt(su / SL);
            dnVol[b] = MathSqrt(sd / SL);
         }
         for(int b = fv+1; b < total; b++)
         { upVol[b]=EPS; dnVol[b]=EPS; }
      }
   }

   // Fő számítási ciklus
   for(int bar = limit-1; bar >= 0; bar--)
   {
      double b;
      if(basisIsRecursive)
         b = BasisBuf[bar];
      else
      {
         b = GetBasis(bar, total, almaW, almaWS);
         BasisBuf[bar] = (Show_Basis && b != EMPTY_VALUE) ? b : EMPTY_VALUE;
      }

      if(b == EMPTY_VALUE || b <= 0)
      {
         UpperBand[bar] = LowerBand[bar] = EMPTY_VALUE;
         if(basisIsRecursive) BasisBuf[bar] = Show_Basis ? b : EMPTY_VALUE;
         continue;
      }

      double av = iATR(NULL, 0, ATR_Length, bar);
      if(av <= 0)
      {
         UpperBand[bar] = LowerBand[bar] = EMPTY_VALUE;
         continue;
      }

      double up_v = (upVol[bar] > EPS) ? upVol[bar] : EPS;
      double dn_v = (dnVol[bar] > EPS) ? dnVol[bar] : EPS;

      double up_r = up_v / dn_v;
      double dn_r = dn_v / up_v;

      if(Clamp_Ratio)
      {
         up_r = MathMax(MathMin(up_r, Ratio_Max), Ratio_Min);
         dn_r = MathMax(MathMin(dn_r, Ratio_Max), Ratio_Min);
      }

      UpperBand[bar] = b + av * Band_Multiplier * MathPow(up_r, Ratio_Power);
      LowerBand[bar] = b - av * Band_Multiplier * MathPow(dn_r, Ratio_Power);
      BasisBuf[bar]  = Show_Basis ? b : EMPTY_VALUE;
   }

   return rates_total;
}
//+------------------------------------------------------------------+