450 lines
22 KiB
MQL5
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|