Article-22572-Megaphone-Pat.../Article-22572-Megaphone-Pattern-Detection-In-MQL5.mq5

450 lines
22 KiB
MQL5

//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+