//+------------------------------------------------------------------+
//|                    TMA_MultiKernel_Hybrid.mq4                    |
//|     TMA / Potencial / Fibonacci / Gaussiano / Custom             |
//|     + Bandas ATR + STD + Selector de dirección + PriceSource     |
//+------------------------------------------------------------------+
#property copyright "TMA MultiKernel Hybrid"
#property indicator_chart_window
#property indicator_buffers 9
#property indicator_color1 clrNONE
#property indicator_color2 clrDodgerBlue
#property indicator_color3 clrDodgerBlue
#property indicator_color4 Lime
#property indicator_color5 Red
#property indicator_color6 clrYellow
#property indicator_color7 clrOrange
#property indicator_color8 clrOrange
#property indicator_color9 clrNONE
#property indicator_style2 STYLE_DOT
#property indicator_style3 STYLE_DOT
#property indicator_style7 STYLE_DASHDOT
#property indicator_style8 STYLE_DASHDOT
#property indicator_width2 1
#property indicator_width3 1
#property indicator_width4 3
#property indicator_width5 3
#property indicator_width6 3
#property indicator_width7 1
#property indicator_width8 1

//==================================================================
// Enums
//==================================================================
enum ENUM_BAND_MODE
{
   BAND_ATR  = 0,   // Solo bandas ATR
   BAND_STD  = 1,   // Solo bandas STD
   BAND_BOTH = 2    // Ambas
};

enum ENUM_KERNEL
{
   K_TMA    = 0,    // Triangular (clásico)
   K_POW    = 1,    // Potencial
   K_FIB    = 2,    // Fibonacci simétrico
   K_GAUSS  = 3,    // Gaussiano
   K_CUSTOM = 4     // Lista CSV
};

enum ENUM_DIRMODE
{
   DIR_SLOPE       = 0,   // Pendiente TMA (suave, clásico)
   DIR_POSITION    = 1,   // Close vs TMA (reactivo)
   DIR_COMBO       = 2,   // Pendiente + posición (estable + aviso)
   DIR_CLOSE_SLOPE = 3    // Pendiente del Close filtrada por TMA
};

//==================================================================
// Entradas
//==================================================================
input ENUM_BAND_MODE     BandMode         = BAND_BOTH;    // Bandas
input ENUM_KERNEL        KernelMode       = K_TMA;        // Kernel de suavizado
input ENUM_DIRMODE       DirectionMode    = DIR_COMBO;    // Modo de dirección
input ENUM_APPLIED_PRICE PriceSource      = PRICE_CLOSE;  // Precio fuente

input int                TMAPeriod        = 14;           // Periodo TMA
input double             PowExp           = 1.5;          // Exponente (K_POW)
input double             GaussSigma       = 3.0;          // Sigma (K_GAUSS)
input string             CustomWeights    = "1,1,1.5,1.618,1.78,2,2.5,2.5,2,1.78,1.618,1.5,1,1";

input double             ATRMultiplier    = 2.0;          // Multiplicador ATR
input int                ATRPeriod        = 100;          // Periodo ATR
input double             STDMultiplier    = 2.0;          // Multiplicador STD

input double             TrendThreshold   = 0.5;          // Umbral de tendencia
input int                ConfirmBars      = 3;            // Barras para confirmar
input double             MinATRPoints     = 0;            // Piso mínimo de ATR (0 = off)

input bool               alertsOn         = true;
input bool               alertsMessage    = true;
input bool               alertsSound      = true;
input bool               alertsEmail      = false;

//==================================================================
// Buffers
//==================================================================
double tma[];
double upperBand[];
double lowerBand[];
double bull[];
double bear[];
double neutral[];
double centerTMA[];
double upperSTD[];
double lowerSTD[];

//==================================================================
// Estado
//==================================================================
double   customW[];
int      customSize = 0;

bool     AlertHappened  = false;
datetime AlertTime      = 0;
int      LastAlertTrend = 0;

//+------------------------------------------------------------------+
int init()
{
   IndicatorBuffers(9);

   SetIndexBuffer(0, tma);
   SetIndexStyle(0, DRAW_NONE);
   SetIndexEmptyValue(0, EMPTY_VALUE);

   SetIndexBuffer(1, upperBand);
   SetIndexStyle(1, DRAW_LINE, STYLE_DOT, 1);
   SetIndexLabel(1, "Upper ATR");
   SetIndexEmptyValue(1, EMPTY_VALUE);

   SetIndexBuffer(2, lowerBand);
   SetIndexStyle(2, DRAW_LINE, STYLE_DOT, 1);
   SetIndexLabel(2, "Lower ATR");
   SetIndexEmptyValue(2, EMPTY_VALUE);

   SetIndexBuffer(3, bull);
   SetIndexStyle(3, DRAW_LINE, STYLE_SOLID, 3);
   SetIndexLabel(3, "Bull");
   SetIndexEmptyValue(3, EMPTY_VALUE);

   SetIndexBuffer(4, bear);
   SetIndexStyle(4, DRAW_LINE, STYLE_SOLID, 3);
   SetIndexLabel(4, "Bear");
   SetIndexEmptyValue(4, EMPTY_VALUE);

   SetIndexBuffer(5, neutral);
   SetIndexStyle(5, DRAW_LINE, STYLE_SOLID, 3);
   SetIndexLabel(5, "Neutral");
   SetIndexEmptyValue(5, EMPTY_VALUE);

   SetIndexBuffer(6, centerTMA);
   SetIndexStyle(6, DRAW_NONE);
   SetIndexEmptyValue(6, EMPTY_VALUE);

   SetIndexBuffer(7, upperSTD);
   SetIndexStyle(7, DRAW_LINE, STYLE_DASHDOT, 1);
   SetIndexLabel(7, "Upper STD");
   SetIndexEmptyValue(7, EMPTY_VALUE);

   SetIndexBuffer(8, lowerSTD);
   SetIndexStyle(8, DRAW_LINE, STYLE_DASHDOT, 1);
   SetIndexLabel(8, "Lower STD");
   SetIndexEmptyValue(8, EMPTY_VALUE);

   ParseCustomWeights();

   string kn = "";
   if(KernelMode == K_TMA)    kn = "TMA";
   if(KernelMode == K_POW)    kn = "POW";
   if(KernelMode == K_FIB)    kn = "FIB";
   if(KernelMode == K_GAUSS)  kn = "GAUSS";
   if(KernelMode == K_CUSTOM) kn = "CUSTOM";

   string dm = "";
   if(DirectionMode == DIR_SLOPE)       dm = "SLOPE";
   if(DirectionMode == DIR_POSITION)    dm = "POS";
   if(DirectionMode == DIR_COMBO)       dm = "COMBO";
   if(DirectionMode == DIR_CLOSE_SLOPE) dm = "C-SLOPE";

   string pn = "C";
   if(PriceSource == PRICE_OPEN)     pn = "O";
   if(PriceSource == PRICE_HIGH)     pn = "H";
   if(PriceSource == PRICE_LOW)      pn = "L";
   if(PriceSource == PRICE_MEDIAN)   pn = "HL2";
   if(PriceSource == PRICE_TYPICAL)  pn = "HLC3";
   if(PriceSource == PRICE_WEIGHTED) pn = "HLCC4";

   IndicatorShortName("TMA Hybrid [" + kn + "/" + dm + "/" + pn + "] (" + TMAPeriod + ")");
   return(0);
}

//+------------------------------------------------------------------+
int deinit() { return(0); }

//==================================================================
void ParseCustomWeights()
{
   int    i, len, ch;
   string s = CustomWeights;
   string cur = "";

   customSize = 0;
   ArrayResize(customW, 0);

   len = StringLen(s);
   for(i = 0; i <= len; i++)
   {
      if(i == len)   ch = -1;
      else           ch = StringGetChar(s, i);

      if(ch == -1 || ch == ',' || ch == ';' || ch == ' ' || ch == 9)
      {
         if(StringLen(cur) > 0)
         {
            customSize++;
            ArrayResize(customW, customSize);
            customW[customSize - 1] = StringToDouble(cur);
            cur = "";
         }
      }
      else
      {
         cur = cur + StringSubstr(s, i, 1);
      }
   }

   if(customSize < 2)
   {
      customSize = 2;
      ArrayResize(customW, 2);
      customW[0] = 1.0;
      customW[1] = 1.0;
   }
}

//==================================================================
int FibonacciCalc(int n)
{
   int a, b, c, k;
   if(n <= 0) return(1);
   if(n == 1) return(1);
   a = 1; b = 1;
   for(k = 2; k <= n; k++)
   {
      c = a + b;
      a = b;
      b = c;
   }
   return(b);
}

//==================================================================
double GetWeight(int j, int period)
{
   double d, w, pos, idxF, frac;
   int    center, dist, idxL, idxR, idx;

   if(period <= 1) return(1.0);

   center = (period - 1) / 2;
   dist   = MathAbs(j - center);

   if(KernelMode == K_TMA)
      return((double)MathMin(j + 1, period - j));

   if(KernelMode == K_POW)
   {
      d = (double)(center + 1 - dist);
      if(d < 1.0) d = 1.0;
      return(MathPow(d, PowExp));
   }

   if(KernelMode == K_FIB)
   {
      idx = center - dist;
      if(idx < 0) idx = 0;
      return((double)FibonacciCalc(idx));
   }

   if(KernelMode == K_GAUSS)
   {
      d = (double)dist;
      if(GaussSigma <= 0.0) return(1.0);
      return(MathExp(-(d * d) / (2.0 * GaussSigma * GaussSigma)));
   }

   if(KernelMode == K_CUSTOM)
   {
      pos  = (double)j / (double)(period - 1);
      idxF = pos * (customSize - 1);
      idxL = (int)MathFloor(idxF);
      idxR = idxL + 1;
      if(idxL < 0) idxL = 0;
      if(idxR >= customSize) { idxL = customSize - 1; idxR = idxL; frac = 0.0; }
      else frac = idxF - idxL;

      w = customW[idxL] * (1.0 - frac) + customW[idxR] * frac;
      return(w);
   }

   return(1.0);
}

//==================================================================
// Precio aplicable en el shift dado
//==================================================================
double GetPrice(int shift)
{
   switch(PriceSource)
   {
      case PRICE_CLOSE:    return(Close[shift]);
      case PRICE_OPEN:     return(Open[shift]);
      case PRICE_HIGH:     return(High[shift]);
      case PRICE_LOW:      return(Low[shift]);
      case PRICE_MEDIAN:   return((High[shift] + Low[shift]) / 2.0);
      case PRICE_TYPICAL:  return((High[shift] + Low[shift] + Close[shift]) / 3.0);
      case PRICE_WEIGHTED: return((High[shift] + Low[shift] + Close[shift] + Close[shift]) / 4.0);
      default:             return(Close[shift]);
   }
}

//==================================================================
double CalcKernel(int shift, int period)
{
   double sum = 0.0, wsum = 0.0, w;
   int    j;

   for(j = 0; j < period; j++)
   {
      if(shift + j >= Bars) continue;
      w = GetWeight(j, period);
      sum  += GetPrice(shift + j) * w;
      wsum += w;
   }
   if(wsum > 0.0) return(sum / wsum);
   return(0.0);
}

//==================================================================
double CalcSTD(int shift, int period, double mean)
{
   double s = 0.0, p;
   int    j, n = 0;

   for(j = 0; j < period; j++)
   {
      if(shift + j >= Bars) continue;
      p = GetPrice(shift + j);
      s += (p - mean) * (p - mean);
      n++;
   }
   if(n > 1) return(MathSqrt(s / (double)n));
   return(0.0);
}

//==================================================================
double CalcSlopeAvg(int k, int confirmBars, double atrEff)
{
   double sum = 0.0;
   int    cnt = 0;
   int    c, kc;

   if(atrEff <= 0.0) return(0.0);

   for(c = 0; c < confirmBars; c++)
   {
      kc = k + c;
      if(kc + 1 >= Bars) continue;
      if(centerTMA[kc] == 0 || centerTMA[kc + 1] == 0) continue;

      sum += (centerTMA[kc] - centerTMA[kc + 1]) / (atrEff * 0.1);
      cnt++;
   }
   if(cnt > 0) return(sum / (double)cnt);
   return(0.0);
}

//==================================================================
// Decide el color de la vela k según el modo de dirección
// Devuelve: 1 bull, -1 bear, 0 neutral
//==================================================================
int DecideTrend(int k, double atrEff)
{
   double tmaK    = centerTMA[k];
   double tmaPrev = (k + 1 < Bars) ? centerTMA[k + 1] : 0.0;
   double priceK  = GetPrice(k);
   double priceP  = (k + 1 < Bars) ? GetPrice(k + 1) : 0.0;
   double slope;
   double closeSlope;
   int    sDir;
   int    pDir;

   if(tmaK == 0.0 || tmaPrev == 0.0)
      return(0);

   //------------------------------------------------
   // 1) DIR_SLOPE
   //------------------------------------------------
   if(DirectionMode == DIR_SLOPE)
   {
      slope = CalcSlopeAvg(k, ConfirmBars, atrEff);
      if(slope > TrendThreshold)        return( 1);
      if(slope < -TrendThreshold)       return(-1);
      return(0);
   }

   //------------------------------------------------
   // 2) DIR_POSITION
   //------------------------------------------------
   if(DirectionMode == DIR_POSITION)
   {
      if(priceK > tmaK) return( 1);
      if(priceK < tmaK) return(-1);
      return(0);
   }

   //------------------------------------------------
   // 3) DIR_COMBO
   //------------------------------------------------
   if(DirectionMode == DIR_COMBO)
   {
      slope = CalcSlopeAvg(k, ConfirmBars, atrEff);
      sDir = 0;
      if(slope > TrendThreshold)        sDir =  1;
      else if(slope < -TrendThreshold)  sDir = -1;

      pDir = 0;
      if(priceK > tmaK) pDir =  1;
      else if(priceK < tmaK) pDir = -1;

      if(sDir == 1 && pDir == 1) return( 1);
      if(sDir == -1 && pDir == -1) return(-1);

      if(sDir != 0 && pDir != 0 && sDir != pDir) return(0);
      if(sDir == 0 && pDir != 0) return(pDir);
      if(pDir == 0 && sDir != 0) return(sDir);

      return(0);
   }

   //------------------------------------------------
   // 4) DIR_CLOSE_SLOPE
   //------------------------------------------------
   if(DirectionMode == DIR_CLOSE_SLOPE)
   {
      if(priceP == 0.0)
      {
         if(priceK > tmaK) return( 1);
         if(priceK < tmaK) return(-1);
         return(0);
      }

      closeSlope = (priceK - priceP) / (atrEff * 0.1);

      // Señal clara: pendiente fuerte Y precio del lado correcto
      if(closeSlope > TrendThreshold && priceK > tmaK)  return( 1);
      if(closeSlope < -TrendThreshold && priceK < tmaK) return(-1);

      // Señal cruzada: pendiente fuerte pero contra posición → neutral
      if(closeSlope > TrendThreshold && priceK < tmaK)  return(0);
      if(closeSlope < -TrendThreshold && priceK > tmaK) return(0);

      // Pendiente plana → decidir por posición
      if(priceK > tmaK) return( 1);
      if(priceK < tmaK) return(-1);

      return(0);
   }

   return(0);
}

//==================================================================
int start()
{
   int    counted_bars, limit, i, k, lastTrend, trend;
   double atr, rangeATR, rangeSTD, stdDev, atrEff;

   counted_bars = IndicatorCounted();
   if(counted_bars < 0) return(-1);
   if(counted_bars > 0) counted_bars--;

   limit = Bars - counted_bars - 1;
   if(limit > Bars - TMAPeriod * 2 - 1)
      limit = Bars - TMAPeriod * 2 - 1;
   if(limit < 1) limit = 1;

   // 1) Kernel + bandas
   for(i = limit; i >= 0; i--)
   {
      tma[i]       = CalcKernel(i, TMAPeriod);
      centerTMA[i] = tma[i];

      if(BandMode == BAND_ATR || BandMode == BAND_BOTH)
      {
         atr          = iATR(NULL, 0, ATRPeriod, i);
         rangeATR     = atr * ATRMultiplier;
         upperBand[i] = tma[i] + rangeATR;
         lowerBand[i] = tma[i] - rangeATR;
      }
      else
      {
         upperBand[i] = EMPTY_VALUE;
         lowerBand[i] = EMPTY_VALUE;
      }

      if(BandMode == BAND_STD || BandMode == BAND_BOTH)
      {
         stdDev      = CalcSTD(i, TMAPeriod, tma[i]);
         rangeSTD    = stdDev * STDMultiplier;
         upperSTD[i] = tma[i] + rangeSTD;
         lowerSTD[i] = tma[i] - rangeSTD;
      }
      else
      {
         upperSTD[i] = EMPTY_VALUE;
         lowerSTD[i] = EMPTY_VALUE;
      }
   }

   // 2) Clasificación con carry-forward + conexión visual
   lastTrend = 0;

   for(k = limit; k >= 1; k--)
   {
      atr = iATR(NULL, 0, ATRPeriod, k);
      atrEff = atr;
      if(MinATRPoints > 0 && atrEff < MinATRPoints * Point)
         atrEff = MinATRPoints * Point;

      trend = DecideTrend(k, atrEff);

      // Carry-forward: en modos no-combo mantenemos el último color
      if(DirectionMode != DIR_COMBO)
      {
         if(trend == 0 && lastTrend != 0)
            trend = lastTrend;
      }

      // Primera vela sin carry → clasificar por posición
      if(trend == 0 && lastTrend == 0)
      {
         double pk = GetPrice(k);
         if(centerTMA[k] != 0 && pk > centerTMA[k])      trend =  1;
         else if(centerTMA[k] != 0 && pk < centerTMA[k]) trend = -1;
      }

      bull[k]    = EMPTY_VALUE;
      bear[k]    = EMPTY_VALUE;
      neutral[k] = EMPTY_VALUE;

      if(centerTMA[k] != 0)
      {
         if(trend == 1)       bull[k]    = centerTMA[k];
         else if(trend == -1) bear[k]    = centerTMA[k];
         else                 neutral[k] = centerTMA[k];
      }

      // Conexión visual en cambio de color
      if(lastTrend != 0 && trend != lastTrend && centerTMA[k + 1] != 0)
      {
         if(trend == 1)       bull[k + 1]    = centerTMA[k + 1];
         else if(trend == -1) bear[k + 1]    = centerTMA[k + 1];
         else                 neutral[k + 1] = centerTMA[k + 1];
      }

      lastTrend = trend;
   }

   // 3) Barra 0 (en formación)
   i = 0;
   atr = iATR(NULL, 0, ATRPeriod, i);
   atrEff = atr;
   if(MinATRPoints > 0 && atrEff < MinATRPoints * Point)
      atrEff = MinATRPoints * Point;

   trend = DecideTrend(i, atrEff);
   if(DirectionMode != DIR_COMBO)
   {
      if(trend == 0 && lastTrend != 0)
         trend = lastTrend;
   }
   if(trend == 0 && lastTrend == 0)
   {
      double p0 = GetPrice(i);
      if(centerTMA[i] != 0 && p0 > centerTMA[i])      trend =  1;
      else if(centerTMA[i] != 0 && p0 < centerTMA[i]) trend = -1;
   }

   bull[i]    = EMPTY_VALUE;
   bear[i]    = EMPTY_VALUE;
   neutral[i] = EMPTY_VALUE;

   if(centerTMA[i] != 0)
   {
      if(trend == 1)       bull[i]    = centerTMA[i];
      else if(trend == -1) bear[i]    = centerTMA[i];
      else                 neutral[i] = centerTMA[i];
   }

   if(lastTrend != 0 && trend != lastTrend && centerTMA[1] != 0)
   {
      if(trend == 1)       bull[1]    = centerTMA[1];
      else if(trend == -1) bear[1]    = centerTMA[1];
      else                 neutral[1] = centerTMA[1];
   }

   // 4) Alerts
   if(counted_bars > 0)
      manageAlerts();

   return(0);
}

//+------------------------------------------------------------------+
void manageAlerts()
{
   int    trend = 0;
   double atr, atrEff;

   if(!alertsOn) return;

   atr = iATR(NULL, 0, ATRPeriod, 1);
   atrEff = atr;
   if(MinATRPoints > 0 && atrEff < MinATRPoints * Point)
      atrEff = MinATRPoints * Point;

   if(centerTMA[1] != 0 && centerTMA[2] != 0 && atrEff > 0)
      trend = DecideTrend(1, atrEff);

   if(trend == 0) return;

   if(trend != LastAlertTrend && AlertTime != Time[1])
   {
      if(LastAlertTrend != 0)
      {
         AlertHappened = false;
         if(trend ==  1) doAlert("UP  - Bull confirmed");
         if(trend == -1) doAlert("DOWN - Bear confirmed");
      }
      LastAlertTrend = trend;
      AlertTime      = Time[1];
   }
}

//+------------------------------------------------------------------+
void doAlert(string doWhat)
{
   string message;

   if(AlertHappened) return;
   AlertHappened = true;

   message = StringConcatenate(
      Symbol(), " ", Period(), "M at ",
      TimeToStr(TimeLocal(), TIME_SECONDS),
      " - ", doWhat);

   if(alertsMessage) Alert(message);
   if(alertsEmail)   SendMail(StringConcatenate(Symbol(), " TMA Hybrid"), message);
   if(alertsSound)   PlaySound("alert2.wav");
}
//+------------------------------------------------------------------+