//+------------------------------------------------------------------+
//|                                            Megaphone Pattern.mq5 |
//|                                             Abioye Israel Pelumi |
//|                             https://linktr.ee/abioyeisraelpelumi |
//+------------------------------------------------------------------+
#property copyright "Abioye Israel Pelumi"
#property link      "https://linktr.ee/abioyeisraelpelumi"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0
#define OBJ_PREFIX      "MG_"  // Prefix for all chart objects

int look_back     = 300;              // number of candles to scan for pattern detection
int SwingLookback = 3;                // swing validation range (left and right candles)
int window_start;                     // starting index of scan window
datetime lastTradeBarTime = 0;        // ensures logic runs once per new bar

//--- Bullish Megaphone Pattern Swing Structure Variables (Pattern Anchors)
double s1_h;                         // first swing high price (S1)
datetime s1_h_t;                     // first swing high time
double s2_l;                         // first swing low price (S2)
datetime s2_l_t;                     // first swing low time
double s3_h;                         // second swing high price (S3)
datetime s3_h_t;                     // second swing high time
double s4_l;                         // second swing low price (S4)
datetime s4_l_t;                     // second swing low time

//--- Trendline Object Variables
string line_s1_3;
string line_s2_4;

//--- S3 to S4 refinement (upper expansion leg)
int s3_s4_hbars;        // bars between S3 and S4 for extreme search
double s3_highest;      // highest price between S3 and S4
datetime s3_highest_t;  // time of highest price between S3 and S4
int s3_highest_index;   // index of highest price between S3 and S4

//--- S2 to S3 refinement (middle structure leg)
int s2_s3_lbars;        // bars between S2 and S3 for extreme search
double s2_lowest;       // lowest price between S2 and S3
datetime s2_lowest_t;   // time of lowest price between S2 and S3
int s2_lowest_index;    // index of lowest price between S2 and S3

//--- S1 to S2 refinement (initial structure leg)
int s1_s2_hbars;        // bars between S1 and S2 for extreme search
double s1_highest;      // highest price between S1 and S2
datetime s1_highest_t;  // time of highest price between S1 and S2
int s1_highest_index;   // index of highest price between S1 and S2

//--- Trend line integrity validation variables
bool is_line_s13_break;   // tracks if any candle closed below upper trend line (S1 to S3)
bool is_line_s24_break;   // tracks if any candle closed above lower trend line (S2 to S4)
double line_s13_value;    // stores upper trend line price at a specific candle
double line_s24_value;    // stores lower trend line price at a specific candle

//--- validation thresholds for structural integrity of bullish megaphone pattern
double s1_2_limit;      // maximum allowed expansion limit between S1 and S2 leg
double s2_4_interval;   // measured distance between S2 and S4 swing legs

//--- breakout validation variables for confirmed megaphone structure
double line_s13_cross_value;     // stores upper trend line value during breakout check
double line_s24_cross_value;     // stores lower trend line value during breakout check
bool   is_line_s24_break_check;  // confirms if lower structure is violated after initial break
double line_s24_break_check;     // stores recalculated lower trend line value during revalidation

//-- object variables
string buy_obj;
double sl;
string sl_line_obj;
string sl_txt_obj;
double tp;
string tp_line_obj;
string tp_txt_obj;

//+------------------------------------------------------------------+
//| Create Texts objects                                      |
//+------------------------------------------------------------------+
bool DrawTxt(const string name,
             datetime xt, double x,
             string message, color fillCol)
  {
   bool created = false;

//--- Create object only if it does not exist
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0,name,OBJ_TEXT,0,xt,x);
      ObjectSetString(0,name,OBJPROP_TEXT,message);
      ObjectSetInteger(0,name,OBJPROP_COLOR,fillCol);

      created = true;
     }
   return created;
  }

//+------------------------------------------------------------------+
//| Create buy and sell objects                                      |
//+------------------------------------------------------------------+
bool DrawObject(const string name,
                datetime xt, double x,
                ENUM_OBJECT obj_type)
  {
   bool created = false;

//--- Create object only if it does not exist
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0,name,obj_type,0,xt,x);
      created = true;
     }

   return created;
  }
//+------------------------------------------------------------------+
//| Create or update a trendline                                     |
//| Returns true if a new object was created                         |
//+------------------------------------------------------------------+
bool DrawTrend(const string name,
               datetime x1t, double x1,
               datetime x2t, double x2,
               color fillCol)
  {
   bool created = false;

//--- Create object only if it does not exist
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_TREND, 0, x1t, x1, x2t, x2);
      //--- Set object properties
      ObjectSetInteger(0,name,OBJPROP_COLOR,fillCol);

      created = true;
     }

   return created;
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping


//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| OnDeinit - runs when indicator is removed                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0,OBJ_PREFIX); // delete all indicator's objects
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//--- ensure enough history is available before processing
   if(rates_total < look_back)
      return 0;

   window_start = rates_total - look_back + SwingLookback; // define scanning window
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); // current candle time

   bool need_redraw = false;
//--- execute logic only once per new candle
   if(currentBarTime != lastTradeBarTime)
     {
      //--- loop through candles within lookback window
      for(int i = window_start; i < rates_total - SwingLookback - 2; i++)
        {
         //--- detect first swing high (S1)
         if(IsSwingHigh(high, i, SwingLookback))
           {
            s1_h = high[i];              // store S1 price
            s1_h_t = time[i];           // store S1 time

            //--- search for first swing low (S2)
            for(int j = i; j < rates_total - SwingLookback - 2; j++)
              {
               if(IsSwingLow(low, j, SwingLookback))
                 {
                  s2_l = low[j];        // store S2 price
                  s2_l_t = time[j];     // store S2 time

                  //--- search for second swing high (S3)
                  for(int k = j; k < rates_total - SwingLookback - 2; k++)
                    {
                     if(IsSwingHigh(high, k, SwingLookback) && high[k] > s1_h)
                       {
                        s3_h = high[k];       // store S3 price
                        s3_h_t = time[k];     // store S3 time

                        //--- search for second swing low (S4)
                        for(int l = k; l < rates_total - SwingLookback - 2; l++)
                          {
                           if(IsSwingLow(low, l, SwingLookback) && low[l] < s2_l)
                             {
                              s4_l = low[l];   // store S4 price
                              s4_l_t = time[l]; // store S4 time

                              //--- refine S3 by locating the true highest high between S3 and S4
                              s3_s4_hbars = Bars(_Symbol, PERIOD_CURRENT, s3_h_t, s4_l_t); // total bars between S3 and S4
                              s3_highest = high[ArrayMaximum(high, k, s3_s4_hbars)]; // highest price within S3 to S4 range
                              s3_highest_t = time[ArrayMaximum(high, k, s3_s4_hbars)]; // time of refined S3 high
                              s3_highest_index = ArrayMaximum(high, k, s3_s4_hbars); // index of refined S3 high

                              //--- refine S2 by locating the true lowest low between S2 and refined S3
                              s2_s3_lbars = Bars(_Symbol, PERIOD_CURRENT, s2_l_t, s3_highest_t); // total bars between S2 and refined S3
                              s2_lowest_index = ArrayMinimum(low, j, s2_s3_lbars); // index of refined S2 low
                              s2_lowest = low[s2_lowest_index]; // lowest price within S2 to S3 range
                              s2_lowest_t = time[s2_lowest_index]; // time of refined S2 low

                              //--- refine S1 by locating the true highest high between S1 and refined S2
                              s1_s2_hbars = Bars(_Symbol, PERIOD_CURRENT, s1_h_t, s2_lowest_t); // total bars between S1 and refined S2
                              s1_highest_index = ArrayMaximum(high, i, s1_s2_hbars); // index of refined S1 high
                              s1_highest = high[s1_highest_index]; // highest price within S1 to S2 range
                              s1_highest_t = time[s1_highest_index]; // time of refined S1 high


                              //--- create trend line connecting first swing high (S1) and second swing high (S3)
                              line_s1_3 = OBJ_PREFIX + "Line S13" + TimeToString(time[i]) + TimeToString(time[k]);

                              //--- draw upper trend line of bullish megaphone structure (S1 to S3)
                              if(DrawTrend(line_s1_3, s1_highest_t, s1_h, s3_highest_t, s3_highest, clrBlue))
                                {
                                 ObjectSetInteger(0, line_s1_3, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
                                 need_redraw = true; // flag chart for update after successful drawing
                                }

                              //--- create trend line connecting first swing low (S2) and second swing low (S4)
                              line_s2_4 = OBJ_PREFIX + "Line S24" + TimeToString(time[j]) + TimeToString(time[l]);

                              //--- draw lower trend line of bullish megaphone structure (S2 to S4)
                              if(DrawTrend(line_s2_4, s2_lowest_t, s2_lowest, s4_l_t, s4_l, clrBlue))
                                {
                                 ObjectSetInteger(0, line_s2_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
                                 need_redraw = true; // flag chart for update after successful drawing
                                }

                              //--- validate upper trend line integrity (S1 to S3)
                              is_line_s13_break = false;

                              //--- scan candles between refined S1 and refined S3
                              for(int y = s1_highest_index; y <= s3_highest_index; y++)
                                {
                                 // retrieve upper trend line price at current candle time
                                 line_s13_value = ObjectGetValueByTime(0, line_s1_3, time[y], 0);

                                 // check if candle closed above the upper trend line
                                 if(close[y] > line_s13_value)
                                   {
                                    is_line_s13_break = true; // invalidate structure if breakout occurs inside pattern
                                    break;
                                   }
                                }

                              //--- validate lower trend line integrity (S2 to S4)
                              is_line_s24_break = false;

                              //--- scan candles between refined S2 and S4
                              for(int z = s2_lowest_index; z <= l; z++)
                                {
                                 // retrieve lower trend line price at current candle time
                                 line_s24_value = ObjectGetValueByTime(0, line_s2_4, time[z], 0);

                                 // check if candle closed below the lower trend line
                                 if(close[z] < line_s24_value)
                                   {
                                    is_line_s24_break = true; // invalidate structure if breakout occurs inside pattern
                                    break;
                                   }
                                }

                              //--- calculate maximum allowed structure expansion limit (based on S1 to S2 range)
                              s1_2_limit = (s1_highest - s2_lowest) * 1.5;

                              //--- measure actual expansion between upper and lower refined swings (S3 to S4)
                              s2_4_interval = s3_highest - s4_l;

                              //--- confirm bullish megaphone structure validity conditions
                              if(is_line_s13_break == false && is_line_s24_break == false && s2_lowest < s1_highest && s3_highest > s1_highest
                                 && s4_l < s2_lowest && s2_4_interval <= s1_2_limit)
                                {

                                 //--- hide trend lines from chart
                                 ObjectSetInteger(0, line_s1_3, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
                                 ObjectSetInteger(0, line_s2_4, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);

                                 //--- extend trend lines to the right for real-time breakout monitoring
                                 ObjectSetInteger(0, line_s1_3, OBJPROP_RAY_RIGHT, true);
                                 ObjectSetInteger(0, line_s2_4, OBJPROP_RAY_RIGHT, true);

                                 //--- scan forward from pattern completion point to detect breakout event
                                 for(int m = l; m <= rates_total - 2; m++)
                                   {
                                    //--- get current upper and lower trend line values at candle time
                                    line_s13_cross_value = ObjectGetValueByTime(0, line_s1_3, time[m], 0);
                                    line_s24_cross_value = ObjectGetValueByTime(0, line_s2_4, time[m], 0);

                                    //--- check if price breaks above upper trend line (bullish breakout trigger)
                                    if(close[m] > line_s13_cross_value)
                                      {
                                       //--- move trend lines forward to breakout point for accurate alignment
                                       ObjectMove(0, line_s1_3, 1, time[m], line_s13_cross_value);
                                       ObjectMove(0, line_s2_4, 1, time[m], line_s24_cross_value);

                                       //--- reset revalidation flag for lower structure check
                                       is_line_s24_break_check = false;

                                       //--- confirm lower trend line has not been violated before breakout
                                       for(int x = l; x <= m; x++)
                                         {
                                          line_s24_break_check = ObjectGetValueByTime(0, line_s2_4, time[x], 0);

                                          //--- invalidate breakout if price closed below lower trend line earlier
                                          if(close[x] < line_s24_break_check)
                                            {
                                             is_line_s24_break_check = true;
                                             break;
                                            }
                                         }

                                       //--- confirm valid bullish breakout (no internal structure violation)
                                       if(is_line_s24_break_check == false)
                                         {
                                          //--- finalize trend line state and make it visible across all timeframes
                                          ObjectSetInteger(0, line_s1_3, OBJPROP_RAY_RIGHT, false);
                                          ObjectSetInteger(0, line_s2_4, OBJPROP_RAY_RIGHT, false);

                                          ObjectSetInteger(0, line_s1_3, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
                                          ObjectSetInteger(0, line_s2_4, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);

                                          //--- generate bullish entry signal at breakout candle
                                          buy_obj = OBJ_PREFIX + "Buy OBJ" + TimeToString(time[m]);

                                          if(DrawObject(buy_obj, time[m], close[m], OBJ_ARROW_BUY))
                                            {
                                             need_redraw = true; // refresh chart after signal creation
                                            }

                                          //--- calculate stop loss at midpoint of breakout structure range
                                          sl = ((line_s13_cross_value - line_s24_cross_value) / 2) + line_s24_cross_value;

                                          //--- create stop loss trend line object identifier
                                          sl_line_obj = OBJ_PREFIX + "SL line" + TimeToString(time[l]) + TimeToString(time[m]);

                                          //--- draw stop loss level on chart
                                          if(DrawTrend(sl_line_obj, time[l], sl, time[m], sl, clrRed))
                                            {
                                             need_redraw = true; // refresh chart after SL line is drawn
                                            }

                                          //--- create stop loss label object at breakout candle
                                          sl_txt_obj = OBJ_PREFIX + "SL Text" + TimeToString(time[m]);

                                          //--- display "SL" text on chart at calculated stop loss level
                                          if(DrawTxt(sl_txt_obj, time[m], sl, "SL", clrRed))
                                            {
                                             need_redraw = true; // refresh chart after SL label is drawn
                                            }

                                          //--- calculate take profit based on full height of breakout structure
                                          tp = (line_s13_cross_value - line_s24_cross_value) + line_s13_cross_value;

                                          //--- create take profit trend line object identifier
                                          tp_line_obj = OBJ_PREFIX + "TP line" + TimeToString(time[l]) + TimeToString(time[m]);

                                          //--- draw take profit level on chart
                                          if(DrawTrend(tp_line_obj, time[l], tp, time[m], tp, clrGreen))
                                            {
                                             need_redraw = true; // refresh chart after TP line is drawn
                                            }

                                          //--- create take profit label object at breakout candle
                                          tp_txt_obj = OBJ_PREFIX + "TP Text" + TimeToString(time[m]);

                                          //--- display "TP" text on chart at calculated take profit level
                                          if(DrawTxt(tp_txt_obj, time[m], tp, "TP", clrGreen))
                                            {
                                             need_redraw = true; // refresh chart after TP label is drawn
                                            }
                                         }


                                       break; // stop scanning after first valid breakout
                                      }
                                   }

                                }
                              break;
                             }
                          }
                        break;
                       }
                    }
                  break;
                 }
              }
           }
        }

      lastTradeBarTime = currentBarTime; // update last processed bar
     }

//--- Redraw only if something changed
   if(need_redraw)
      ChartRedraw(0);

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| FUNCTION FOR SWING LOW                                           |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i])
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| FUNCTION FOR SWING HIGH                                          |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i])
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
