//+------------------------------------------------------------------+ //| Article-22572-Megaphone-Pattern-Detection-In-MQL5.mq5 | //| Abioye Israel Pelumi | //| https://Algoyin.com | //+------------------------------------------------------------------+ #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; } //+------------------------------------------------------------------+