//+------------------------------------------------------------------+
//|          Pythagorean_Structure_Strength_ATR.mq4                  |
//|  Indicador MT4: geometria precio-tiempo + ATR + score operativo  |
//|                                                                  |
//|  Esta version NO proyecta un movimiento copiado por defecto.      |
//|  Mide estructura, impulso, velocidad geometrica y agotamiento.    |
//+------------------------------------------------------------------+
#property strict
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 clrDodgerBlue
#property indicator_color2 clrOrangeRed
#property indicator_width1 2
#property indicator_width2 2

#property indicator_level1 1.5
#property indicator_level2 3.0
#property indicator_level3 5.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT

//-------------------- Inputs principales --------------------------//
input int      LookbackBars              = 20;      // Barras para calculo continuo
input double   TimePipsPerBar            = 20.0;    // k: convierte 1 barra en X pips
input int      ATRPeriod                 = 14;      // ATR para normalizar
input int      SwingDepth                = 8;       // Profundidad para detectar swings
input int      ScanBars                  = 300;     // Barras analizadas para swings
input int      MaxSegments               = 8;       // Ultimos tramos swing a dibujar

//-------------------- Filtros y clasificacion ---------------------//
input double   MinPriceMoveATR           = 0.60;    // Ignora tramos menores a X ATR de precio
input double   WeakScore                 = 1.20;    // Por debajo: tramo debil/lateral
input double   ContinuationScore         = 2.20;    // Desde aqui: impulso relevante
input double   ExhaustionScore           = 4.50;    // Desde aqui: posible agotamiento/extension
input double   FastVelocityATR           = 0.35;    // Velocidad normalizada alta
input double   HighAngleDegrees          = 55.0;    // Angulo alto para extension/agresividad

//-------------------- Zonas ---------------------------------------//
input bool     DrawZones                 = true;    // Dibujar zonas de continuidad/agotamiento
input int      ZoneForwardBars           = 20;      // Barras hacia futuro para extender zonas
input double   PullbackZoneFrom          = 0.382;   // Inicio zona de continuidad
input double   PullbackZoneTo            = 0.618;   // Final zona de continuidad
input double   ExhaustionZoneATR         = 0.25;    // Banda de agotamiento en multiplos de ATR

//-------------------- Visual --------------------------------------//
input bool     DrawSwingGeometry         = true;    // Dibujar lineas entre swings
input bool     ShowLabels                = true;    // Mostrar etiquetas en tramos
input bool     ShowMarkers               = true;    // Marcar impulso / agotamiento
input bool     ShowPanel                 = true;    // Mostrar resumen en comentario
input color    BullColor                 = clrLimeGreen;
input color    BearColor                 = clrTomato;
input color    WeakColor                 = clrSilver;
input color    ExhaustionColor           = clrGold;
input color    ContinuationZoneColor     = clrDeepSkyBlue;
input color    ExhaustionZoneColor       = clrOrange;
input color    TextColor                 = clrWhite;
input int      LineWidth                 = 2;
input int      FontSize                  = 8;

//-------------------- Buffers -------------------------------------//
double NormDistanceBuffer[];
double ImpulseScoreBuffer[];

string PREFIX = "PYTH_STR_";
double PI_VAL = 3.14159265358979323846;

//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorShortName("Pythagorean Structure Strength ATR");

   SetIndexBuffer(0, NormDistanceBuffer);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexLabel(0, "Distancia normalizada ATR");

   SetIndexBuffer(1, ImpulseScoreBuffer);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexLabel(1, "Impulse Score");

   ArraySetAsSeries(NormDistanceBuffer, true);
   ArraySetAsSeries(ImpulseScoreBuffer, true);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   DeleteObjectsByPrefix(PREFIX);
   Comment("");
}

//+------------------------------------------------------------------+
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 lb = LookbackBars;
   if(lb < 1) lb = 1;

   int atrP = ATRPeriod;
   if(atrP < 1) atrP = 1;

   int depth = SwingDepth;
   if(depth < 1) depth = 1;

   double k = TimePipsPerBar;
   if(k <= 0.0) k = 1.0;

   if(rates_total <= lb + atrP + depth + 5)
      return(0);

   double pip = PipSize();

   for(int i = 0; i < rates_total; i++)
   {
      NormDistanceBuffer[i] = EMPTY_VALUE;
      ImpulseScoreBuffer[i] = EMPTY_VALUE;
   }

   int maxCalc = rates_total - lb - 1;

   // Calculo continuo:
   // c_norm = sqrt((dp/ATR)^2 + ((k*dt)/ATR)^2)
   // score  = c_norm * (1 + velocidad_norm) * peso_angulo
   for(int bar = 0; bar <= maxCalc; bar++)
   {
      double atrPips = iATR(NULL, 0, atrP, bar) / pip;
      if(atrPips <= 0.0)
         continue;

      double dpPips = (close[bar] - close[bar + lb]) / pip;
      double timeLegPips = k * lb;

      double priceNorm = MathAbs(dpPips) / atrPips;
      double timeNorm  = timeLegPips / atrPips;

      double cNorm = MathSqrt(priceNorm * priceNorm + timeNorm * timeNorm);

      double angle = 0.0;
      if(timeLegPips != 0.0)
         angle = MathArctan(dpPips / timeLegPips) * 180.0 / PI_VAL;

      double velocityNorm = cNorm / lb;
      double angleWeight = MathAbs(angle) / 90.0;
      if(angleWeight < 0.0) angleWeight = 0.0;
      if(angleWeight > 1.0) angleWeight = 1.0;

      double score = cNorm * (1.0 + velocityNorm) * (0.50 + 0.50 * angleWeight);

      NormDistanceBuffer[bar] = cNorm;
      ImpulseScoreBuffer[bar] = score;
   }

   if(DrawSwingGeometry)
      DrawStructure(time, high, low, close, rates_total, pip, k, atrP, depth);

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Tamano de pip compatible con brokers de 3/5 digitos              |
//+------------------------------------------------------------------+
double PipSize()
{
   if(Digits == 3 || Digits == 5)
      return(Point * 10.0);

   return(Point);
}

//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high[], int shift, int depth, int total)
{
   if(shift - depth < 0 || shift + depth >= total)
      return(false);

   double v = high[shift];

   for(int j = 1; j <= depth; j++)
   {
      if(v <= high[shift - j]) return(false);
      if(v <= high[shift + j]) return(false);
   }

   return(true);
}

//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int shift, int depth, int total)
{
   if(shift - depth < 0 || shift + depth >= total)
      return(false);

   double v = low[shift];

   for(int j = 1; j <= depth; j++)
   {
      if(v >= low[shift - j]) return(false);
      if(v >= low[shift + j]) return(false);
   }

   return(true);
}

//+------------------------------------------------------------------+
void AddPivot(int shift,
              double price,
              int type,
              int &shifts[],
              double &prices[],
              int &types[],
              int &count)
{
   if(count > 0)
   {
      int last = count - 1;

      if(types[last] == type)
      {
         if((type == 1 && price > prices[last]) ||
            (type == -1 && price < prices[last]))
         {
            shifts[last] = shift;
            prices[last] = price;
            types[last]  = type;
         }
         return;
      }
   }

   ArrayResize(shifts, count + 1);
   ArrayResize(prices, count + 1);
   ArrayResize(types,  count + 1);

   shifts[count] = shift;
   prices[count] = price;
   types[count]  = type;
   count++;
}

//+------------------------------------------------------------------+
void DrawStructure(const datetime &time[],
                   const double &high[],
                   const double &low[],
                   const double &close[],
                   int total,
                   double pip,
                   double k,
                   int atrP,
                   int depth)
{
   DeleteObjectsByPrefix(PREFIX);

   int maxScan = ScanBars;
   if(maxScan > total - depth - 1)
      maxScan = total - depth - 1;

   if(maxScan <= depth)
      return;

   int shifts[];
   double prices[];
   int types[];
   int count = 0;

   // De antiguo a reciente.
   for(int s = maxScan; s >= depth; s--)
   {
      bool h = IsSwingHigh(high, s, depth, total);
      bool l = IsSwingLow(low,  s, depth, total);

      if(h && l)
         continue;

      if(h)
         AddPivot(s, high[s], 1, shifts, prices, types, count);
      else if(l)
         AddPivot(s, low[s], -1, shifts, prices, types, count);
   }

   if(count < 2)
      return;

   int firstSegment = count - MaxSegments;
   if(firstSegment < 1)
      firstSegment = 1;

   double lastScore = 0.0;
   double lastCNorm = 0.0;
   double lastVelocity = 0.0;
   double lastAngle = 0.0;
   double lastPriceNorm = 0.0;
   double lastDeltaPips = 0.0;
   int    lastBars = 0;
   string lastClass = "";

   for(int p = firstSegment; p < count; p++)
   {
      int s1 = shifts[p - 1];
      int s2 = shifts[p];

      double price1 = prices[p - 1];
      double price2 = prices[p];

      int barsDelta = MathAbs(s1 - s2);
      if(barsDelta <= 0)
         continue;

      double atrPips = iATR(NULL, 0, atrP, s2) / pip;
      if(atrPips <= 0.0)
         continue;

      double dpPips = (price2 - price1) / pip;
      double absDpPips = MathAbs(dpPips);
      double timeLegPips = k * barsDelta;

      double priceNorm = absDpPips / atrPips;
      double timeNorm = timeLegPips / atrPips;

      if(priceNorm < MinPriceMoveATR)
         continue;

      double cNorm = MathSqrt(priceNorm * priceNorm + timeNorm * timeNorm);

      double angle = 0.0;
      if(timeLegPips != 0.0)
         angle = MathArctan(dpPips / timeLegPips) * 180.0 / PI_VAL;

      double velocityNorm = cNorm / barsDelta;

      double angleWeight = MathAbs(angle) / 90.0;
      if(angleWeight < 0.0) angleWeight = 0.0;
      if(angleWeight > 1.0) angleWeight = 1.0;

      double score = cNorm * (1.0 + velocityNorm) * (0.50 + 0.50 * angleWeight);

      string segmentClass = ClassifySegment(score, velocityNorm, angle, dpPips, priceNorm);
      color segColor = ColorForClass(segmentClass, dpPips);

      // Linea del tramo.
      string lineName = PREFIX + "SEG_" + IntegerToString(p);
      ObjectCreate(0, lineName, OBJ_TREND, 0, time[s1], price1, time[s2], price2);
      ObjectSetInteger(0, lineName, OBJPROP_COLOR, segColor);
      ObjectSetInteger(0, lineName, OBJPROP_WIDTH, LineWidth);
      ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, lineName, OBJPROP_SELECTABLE, false);

      // Etiqueta.
      if(ShowLabels)
      {
         datetime midTime = time[s1] + (time[s2] - time[s1]) / 2;
         double midPrice = (price1 + price2) / 2.0;

         string labelName = PREFIX + "LBL_" + IntegerToString(p);
         string txt = StringFormat("%s | score %.2f | cATR %.2f | dp %.1f | v %.2f | %.1f deg",
                                   segmentClass, score, cNorm, dpPips, velocityNorm, angle);

         ObjectCreate(0, labelName, OBJ_TEXT, 0, midTime, midPrice);
         ObjectSetText(labelName, txt, FontSize, "Arial", TextColor);
         ObjectSetInteger(0, labelName, OBJPROP_SELECTABLE, false);
      }

      // Marcadores.
      if(ShowMarkers)
         DrawMarker(p, time[s2], price2, segmentClass, dpPips);

      // Zonas solo sobre el ultimo segmento valido.
      if(DrawZones && p == count - 1)
         DrawDecisionZones(time, s2, price1, price2, dpPips, score, segmentClass, atrPips, pip);

      lastScore = score;
      lastCNorm = cNorm;
      lastVelocity = velocityNorm;
      lastAngle = angle;
      lastPriceNorm = priceNorm;
      lastDeltaPips = dpPips;
      lastBars = barsDelta;
      lastClass = segmentClass;
   }

   if(ShowPanel)
   {
      Comment("Pythagorean Structure Strength ATR",
              "\nUltimo tramo: ", lastClass,
              "\nScore: ", DoubleToString(lastScore, 2),
              " | cATR: ", DoubleToString(lastCNorm, 2),
              " | precio: ", DoubleToString(lastPriceNorm, 2), " ATR",
              "\nDP: ", DoubleToString(lastDeltaPips, 1), " pips",
              " | DT: ", IntegerToString(lastBars), " barras",
              " | velocidad: ", DoubleToString(lastVelocity, 2),
              " | angulo: ", DoubleToString(lastAngle, 1), " grados");
   }
}

//+------------------------------------------------------------------+
string ClassifySegment(double score,
                       double velocityNorm,
                       double angle,
                       double dpPips,
                       double priceNorm)
{
   string dir = "ALCISTA";
   if(dpPips < 0.0)
      dir = "BAJISTA";

   if(score < WeakScore)
      return("TRAMO DEBIL");

   if(score >= ExhaustionScore &&
      velocityNorm >= FastVelocityATR &&
      MathAbs(angle) >= HighAngleDegrees)
      return("EXTENSION " + dir + " / AGOTAMIENTO");

   if(score >= ContinuationScore)
      return("IMPULSO " + dir);

   return("MOVIMIENTO " + dir + " MODERADO");
}

//+------------------------------------------------------------------+
color ColorForClass(string segmentClass, double dpPips)
{
   if(StringFind(segmentClass, "AGOTAMIENTO", 0) >= 0)
      return(ExhaustionColor);

   if(StringFind(segmentClass, "DEBIL", 0) >= 0)
      return(WeakColor);

   if(dpPips >= 0.0)
      return(BullColor);

   return(BearColor);
}

//+------------------------------------------------------------------+
void DrawMarker(int p,
                datetime t,
                double price,
                string segmentClass,
                double dpPips)
{
   string name = PREFIX + "MRK_" + IntegerToString(p);

   ObjectCreate(0, name, OBJ_ARROW, 0, t, price);

   if(StringFind(segmentClass, "AGOTAMIENTO", 0) >= 0)
   {
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, 251); // punto/circulo
      ObjectSetInteger(0, name, OBJPROP_COLOR, ExhaustionColor);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 3);
   }
   else if(StringFind(segmentClass, "IMPULSO", 0) >= 0)
   {
      if(dpPips >= 0.0)
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, 233); // flecha arriba
      else
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, 234); // flecha abajo

      ObjectSetInteger(0, name, OBJPROP_COLOR, dpPips >= 0.0 ? BullColor : BearColor);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
   }
   else
   {
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, 159);
      ObjectSetInteger(0, name, OBJPROP_COLOR, WeakColor);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   }

   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}

//+------------------------------------------------------------------+
void DrawDecisionZones(const datetime &time[],
                       int lastShift,
                       double price1,
                       double price2,
                       double dpPips,
                       double score,
                       string segmentClass,
                       double atrPips,
                       double pip)
{
   datetime t1 = time[lastShift];
   datetime t2 = t1 + ZoneForwardBars * TFSeconds();

   double move = price2 - price1;

   // Zona de continuidad: retroceso geometrico del ultimo impulso.
   if(StringFind(segmentClass, "IMPULSO", 0) >= 0)
   {
      double zA = price2 - move * PullbackZoneFrom;
      double zB = price2 - move * PullbackZoneTo;

      double top = MathMax(zA, zB);
      double bottom = MathMin(zA, zB);

      string zn = PREFIX + "CONT_ZONE";
      ObjectCreate(0, zn, OBJ_RECTANGLE, 0, t1, top, t2, bottom);
      ObjectSetInteger(0, zn, OBJPROP_COLOR, ContinuationZoneColor);
      ObjectSetInteger(0, zn, OBJPROP_STYLE, STYLE_DOT);
      ObjectSetInteger(0, zn, OBJPROP_BACK, true);
      ObjectSetInteger(0, zn, OBJPROP_SELECTABLE, false);
   }

   // Zona de agotamiento: banda alrededor del extremo final.
   if(StringFind(segmentClass, "AGOTAMIENTO", 0) >= 0)
   {
      double band = atrPips * pip * ExhaustionZoneATR;
      double top2 = price2 + band;
      double bottom2 = price2 - band;

      string zn2 = PREFIX + "EXH_ZONE";
      ObjectCreate(0, zn2, OBJ_RECTANGLE, 0, t1, top2, t2, bottom2);
      ObjectSetInteger(0, zn2, OBJPROP_COLOR, ExhaustionZoneColor);
      ObjectSetInteger(0, zn2, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, zn2, OBJPROP_BACK, true);
      ObjectSetInteger(0, zn2, OBJPROP_SELECTABLE, false);
   }
}

//+------------------------------------------------------------------+
int TFSeconds()
{
   int tf = Period();

   switch(tf)
   {
      case PERIOD_M1:  return(60);
      case PERIOD_M5:  return(300);
      case PERIOD_M15: return(900);
      case PERIOD_M30: return(1800);
      case PERIOD_H1:  return(3600);
      case PERIOD_H4:  return(14400);
      case PERIOD_D1:  return(86400);
      case PERIOD_W1:  return(604800);
      case PERIOD_MN1: return(2592000);
   }

   return(tf * 60);
}

//+------------------------------------------------------------------+
void DeleteObjectsByPrefix(string prefix)
{
   for(int i = ObjectsTotal() - 1; i >= 0; i--)
   {
      string name = ObjectName(i);
      if(StringFind(name, prefix, 0) == 0)
         ObjectDelete(name);
   }
}
//+------------------------------------------------------------------+
