//+------------------------------------------------------------------+
//|                                            GeometryDetection.mqh |
//|                                       Copyright 2026, Clemence   |
//|                                         https://www.mql5.com     |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property link      "https://www.mql5.com"
#property strict

//+------------------------------------------------------------------+
//| Configurable geometry detection class for market patterns        |
//+------------------------------------------------------------------+
class CGeometryDetector
  {
private:
   double m_triangle_tolerance;   //--- tolerance for flat lines
   double m_rectangle_tolerance;  //--- tolerance for rectangle range
   double m_mw_depth_factor;      //--- minimum depth for M/W patterns
   int    m_swing_distance;       //--- swing detection distance
   int    m_min_bars;             //--- minimum bars required

//+------------------------------------------------------------------+
//| Adds value to dynamic array                                      |
//+------------------------------------------------------------------+
   template<typename T>
   void ArrayAdd(T &arr[], T value)
     {
      int size = ArraySize(arr);
      ArrayResize(arr, size + 1);
      arr[size] = value;
     }

//+------------------------------------------------------------------+
//| Finds swing highs and lows                                       |
//+------------------------------------------------------------------+
   void FindSwings(const double &highs[], const double &lows[], const int total,
                   int &swing_high_idx[], int &swing_low_idx[])
     {
      ArrayResize(swing_high_idx, 0);
      ArrayResize(swing_low_idx, 0);

      for(int i = m_swing_distance; i < total - m_swing_distance; i++)
        {
         bool is_high = true;
         bool is_low  = true;

         for(int j = 1; j <= m_swing_distance; j++)
           {
            if(highs[i] <= highs[i - j] || highs[i] <= highs[i + j])
               is_high = false;

            if(lows[i] >= lows[i - j] || lows[i] >= lows[i + j])
               is_low = false;
           }

         if(is_high)
            ArrayAdd(swing_high_idx, i);

         if(is_low)
            ArrayAdd(swing_low_idx, i);
        }
     }

//+------------------------------------------------------------------+
//| Calculates linear regression slope                               |
//+------------------------------------------------------------------+
   double Slope(const double &values[], const int start, const int count)
     {
      if(count < 2)
         return(0);

      double sum_x = 0;
      double sum_y = 0;
      double sum_xy = 0;
      double sum_x2 = 0;

      for(int i = 0; i < count; i++)
        {
         double x = i;
         double y = values[start + i];

         sum_x  += x;
         sum_y  += y;
         sum_xy += x * y;
         sum_x2 += x * x;
        }

      double denominator = count * sum_x2 - sum_x * sum_x;

      if(denominator == 0)
         return(0);

      return((count * sum_xy - sum_x * sum_y) / denominator);
     }

//+------------------------------------------------------------------+
//| Checks if values are flat within tolerance                       |
//+------------------------------------------------------------------+
   bool IsFlat(const double &values[], const double tolerance)
     {
      int size = ArraySize(values);

      if(size < 2)
         return(true);

      double avg = 0;

      for(int i = 0; i < size; i++)
         avg += values[i];

      avg /= size;

      for(int i = 0; i < size; i++)
        {
         if(MathAbs(values[i] - avg) > avg * tolerance)
            return(false);
        }

      return(true);
     }

public:

//+------------------------------------------------------------------+
//| Constructor with default configuration                           |
//+------------------------------------------------------------------+
   CGeometryDetector()
     {
      m_triangle_tolerance  = 0.005;
      m_rectangle_tolerance = 0.01;
      m_mw_depth_factor     = 0.005;
      m_swing_distance      = 2;
      m_min_bars            = 6;
     }

//+------------------------------------------------------------------+
//| Configures detection parameters                                  |
//+------------------------------------------------------------------+
   void Configure(const double triangle_tolerance,
                  const double rectangle_tolerance,
                  const double mw_depth_factor,
                  const int swing_distance,
                  const int min_bars)
     {
      m_triangle_tolerance  = triangle_tolerance;
      m_rectangle_tolerance = rectangle_tolerance;
      m_mw_depth_factor     = mw_depth_factor;
      m_swing_distance      = swing_distance;
      m_min_bars            = min_bars;
     }

//+------------------------------------------------------------------+
//| Detects ascending triangle pattern                               |
//+------------------------------------------------------------------+
   bool IsAscendingTriangle(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      int swing_highs[], swing_lows[];
      FindSwings(highs, lows, total, swing_highs, swing_lows);

      if(ArraySize(swing_highs) < 2 || ArraySize(swing_lows) < 2)
         return(false);

      double high_vals[], low_vals[];

      for(int i = 0; i < ArraySize(swing_highs); i++)
         ArrayAdd(high_vals, highs[swing_highs[i]]);

      for(int i = 0; i < ArraySize(swing_lows); i++)
         ArrayAdd(low_vals, lows[swing_lows[i]]);

      if(!IsFlat(high_vals, m_triangle_tolerance))
         return(false);

      for(int i = 1; i < ArraySize(low_vals); i++)
        {
         if(low_vals[i] <= low_vals[i - 1])
            return(false);
        }

      return(true);
     }

//+------------------------------------------------------------------+
//| Detects descending triangle pattern                              |
//+------------------------------------------------------------------+
   bool IsDescendingTriangle(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      int swing_highs[], swing_lows[];
      FindSwings(highs, lows, total, swing_highs, swing_lows);

      if(ArraySize(swing_highs) < 2 || ArraySize(swing_lows) < 2)
         return(false);

      double high_vals[], low_vals[];

      for(int i = 0; i < ArraySize(swing_highs); i++)
         ArrayAdd(high_vals, highs[swing_highs[i]]);

      for(int i = 0; i < ArraySize(swing_lows); i++)
         ArrayAdd(low_vals, lows[swing_lows[i]]);

      if(!IsFlat(low_vals, m_triangle_tolerance))
         return(false);

      for(int i = 1; i < ArraySize(high_vals); i++)
        {
         if(high_vals[i] >= high_vals[i - 1])
            return(false);
        }

      return(true);
     }

//+------------------------------------------------------------------+
//| Detects symmetrical triangle pattern                             |
//+------------------------------------------------------------------+
   bool IsSymmetricalTriangle(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      int swing_highs[], swing_lows[];
      FindSwings(highs, lows, total, swing_highs, swing_lows);

      if(ArraySize(swing_highs) < 2 || ArraySize(swing_lows) < 2)
         return(false);

      double high_vals[], low_vals[];

      for(int i = 0; i < ArraySize(swing_highs); i++)
         ArrayAdd(high_vals, highs[swing_highs[i]]);

      for(int i = 0; i < ArraySize(swing_lows); i++)
         ArrayAdd(low_vals, lows[swing_lows[i]]);

      double high_slope = Slope(high_vals, 0, ArraySize(high_vals));
      double low_slope  = Slope(low_vals, 0, ArraySize(low_vals));

      return(high_slope < 0 && low_slope > 0);
     }

//+------------------------------------------------------------------+
//| Detects rectangle consolidation pattern                          |
//+------------------------------------------------------------------+
   bool IsRectangle(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      double max_high = highs[ArrayMaximum(highs)];
      double min_low  = lows[ArrayMinimum(lows)];
      double avg_price = (max_high + min_low) / 2;
      double tolerance = avg_price * m_rectangle_tolerance;

      for(int i = 0; i < total; i++)
        {
         if(highs[i] > max_high + tolerance || lows[i] < min_low - tolerance)
            return(false);
        }

      double range = max_high - min_low;

      if(range > avg_price * 0.02)
         return(false);

      int high_touches = 0;
      int low_touches  = 0;
      double touch_tol = avg_price * 0.001;

      for(int i = 0; i < total; i++)
        {
         if(highs[i] >= max_high - touch_tol)
            high_touches++;

         if(lows[i] <= min_low + touch_tol)
            low_touches++;
        }

      if(high_touches < 2 || low_touches < 2)
         return(false);

      return(true);
     }

//+------------------------------------------------------------------+
//| Detects M (double top) pattern                                   |
//+------------------------------------------------------------------+
   bool IsM(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      int swing_highs[], swing_lows[];
      FindSwings(highs, lows, total, swing_highs, swing_lows);

      if(ArraySize(swing_highs) < 2)
         return(false);

      int idx1 = swing_highs[0];
      int idx2 = swing_highs[ArraySize(swing_highs) - 1];

      if(idx2 - idx1 < m_swing_distance * 2)
         return(false);

      double min_low = DBL_MAX;

      for(int i = idx1; i <= idx2; i++)
        {
         if(lows[i] < min_low)
            min_low = lows[i];
        }

      double avg_peak = (highs[idx1] + highs[idx2]) / 2;
      double tolerance = avg_peak * m_triangle_tolerance;

      if(MathAbs(highs[idx1] - highs[idx2]) > tolerance)
         return(false);

      double height = avg_peak - min_low;

      if(height < avg_peak * m_mw_depth_factor)
         return(false);

      return(true);
     }

//+------------------------------------------------------------------+
//| Detects W (double bottom) pattern                                |
//+------------------------------------------------------------------+
   bool IsW(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return(false);

      int swing_highs[], swing_lows[];
      FindSwings(highs, lows, total, swing_highs, swing_lows);

      if(ArraySize(swing_lows) < 2)
         return(false);

      int idx1 = swing_lows[0];
      int idx2 = swing_lows[ArraySize(swing_lows) - 1];

      if(idx2 - idx1 < m_swing_distance * 2)
         return(false);

      double max_high = 0;

      for(int i = idx1; i <= idx2; i++)
        {
         if(highs[i] > max_high)
            max_high = highs[i];
        }

      double avg_valley = (lows[idx1] + lows[idx2]) / 2;
      double tolerance = avg_valley * m_triangle_tolerance;

      if(MathAbs(lows[idx1] - lows[idx2]) > tolerance)
         return(false);

      double height = max_high - avg_valley;

      if(height < avg_valley * m_mw_depth_factor)
         return(false);

      return(true);
     }

//+------------------------------------------------------------------+
//| Detects internal base shape                                      |
//+------------------------------------------------------------------+
   string DetectInternalBaseShape(const double &highs[], const double &lows[], const int total)
     {
      if(total < m_min_bars)
         return("undefined (too few bars)");

      if(IsAscendingTriangle(highs, lows, total))
         return("ascending triangle");

      if(IsDescendingTriangle(highs, lows, total))
         return("descending triangle");

      if(IsSymmetricalTriangle(highs, lows, total))
         return("symmetrical triangle");

      if(IsRectangle(highs, lows, total))
         return("rectangle");

      if(IsM(highs, lows, total))
         return("M");

      if(IsW(highs, lows, total))
         return("W");

      return("undefined");
     }
  };
//+------------------------------------------------------------------+