file. The primary difference from the MT4 version is how MQL5 retrieves historical timeframe data using CopyHigh and CopyLow arrays rather than direct function calls, ensuring strict data synchronization.
Code: Select all
//+------------------------------------------------------------------+
//| LondonNYOverlap.mq5 |
//| London-NY Overlap + ADR Exhaustion (Filtered) |
//+------------------------------------------------------------------+
#property copyright "Community Script"
#property version "1.20"
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
#property indicator_label1 "London High"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrSeaGreen
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label2 "London Low"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrCrimson
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
#property indicator_label3 "London Mid (Balance)"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrSlateGray
#property indicator_style3 STYLE_DOT
#property indicator_width3 1
//--- Session Inputs
input group "Session Settings"
input string InpLondonStart = "08:00"; // London Start (Broker Time)
input string InpLondonEnd = "13:00"; // London End / NY Overlap Start
input string InpOverlapEnd = "17:00"; // NY Overlap End
//--- ADR Inputs
input group "ADR Settings"
input int InpAdrLookback = 14; // ADR Lookback (Days)
input double InpAdrThreshold = 80.0; // Exhaustion Threshold (%)
//--- Alert Inputs
input group "Alert Settings"
input bool InpAlertSweep = true; // Enable Pop-up Alerts
input bool InpPushSweep = true; // Enable Push Notifications
input bool InpSoundSweep = false; // Enable Custom Sound
input string InpSoundFile = "alert.wav"; // Sound File Name (.wav)
//--- Buffers
double BufferHigh[];
double BufferLow[];
double BufferMid[];
int g_lon_start_sec = 0;
int g_lon_end_sec = 0;
int g_overlap_end_sec = 0;
//--- Alert Locks
int g_last_high_sweep_day = -1;
int g_last_low_sweep_day = -1;
int ParseTimeToSeconds(const string time_str)
{
string parts[];
if(StringSplit(time_str, ':', parts) >= 2)
return (int)StringToInteger(parts[0]) * 3600 + (int)StringToInteger(parts[1]) * 60;
return 0;
}
double GetHistoricalADR(int days)
{
if(days <= 0) return 0.0001;
double high_arr[], low_arr[];
ArraySetAsSeries(high_arr, true);
ArraySetAsSeries(low_arr, true);
// Copy historical daily highs and lows starting from index 1 (yesterday)
int copied_high = CopyHigh(_Symbol, PERIOD_D1, 1, days, high_arr);
int copied_low = CopyLow(_Symbol, PERIOD_D1, 1, days, low_arr);
if(copied_high <= 0 || copied_low <= 0) return 0.0001; // Fallback to prevent divide by zero
double sum = 0;
int count = MathMin(copied_high, copied_low);
for(int i = 0; i < count; i++)
{
sum += (high_arr[i] - low_arr[i]);
}
return sum / count;
}
void UpdateVisualStatus(double pct_consumed, bool is_exhausted)
{
string obj_name = "ADR_Status_Label";
if(ObjectFind(0, obj_name) < 0)
{
ObjectCreate(0, obj_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, obj_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, obj_name, OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, obj_name, OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, obj_name, OBJPROP_FONTSIZE, 10);
ObjectSetString(0, obj_name, OBJPROP_FONT, "Arial");
}
string status = is_exhausted ? "NO-GO (EXHAUSTED)" : "GO (ROOM TO MOVE)";
color text_color = is_exhausted ? clrRed : clrLimeGreen;
string text = StringFormat("London ADR Consumed: %.1f%% | %s", pct_consumed, status);
ObjectSetString(0, obj_name, OBJPROP_TEXT, text);
ObjectSetInteger(0, obj_name, OBJPROP_COLOR, text_color);
}
int OnInit()
{
SetIndexBuffer(0, BufferHigh, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
SetIndexBuffer(1, BufferLow, INDICATOR_DATA);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
SetIndexBuffer(2, BufferMid, INDICATOR_DATA);
PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
g_lon_start_sec = ParseTimeToSeconds(InpLondonStart);
g_lon_end_sec = ParseTimeToSeconds(InpLondonEnd);
g_overlap_end_sec = ParseTimeToSeconds(InpOverlapEnd);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, "ADR_Status_Label");
}
void TriggerNotification(string msg)
{
if(InpAlertSweep) Alert(msg);
if(InpPushSweep) SendNotification(msg);
if(InpSoundSweep) PlaySound(InpSoundFile);
}
int OnCalculate(const int rates_total,
const int 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 int &spread[])
{
if(rates_total < 2) return 0;
ArraySetAsSeries(time, false);
ArraySetAsSeries(high, false);
ArraySetAsSeries(low, false);
ArraySetAsSeries(BufferHigh, false);
ArraySetAsSeries(BufferLow, false);
ArraySetAsSeries(BufferMid, false);
int start = prev_calculated - 1;
if(start < 0) start = 0;
// Fetch ADR once per tick
double adr = GetHistoricalADR(InpAdrLookback);
for(int i = start; i < rates_total; i++)
{
MqlDateTime dt;
TimeToStruct(time[i], dt);
int bar_sec = dt.hour * 3600 + dt.min * 60;
int bar_day = dt.day;
if(bar_sec >= g_lon_start_sec && bar_sec < g_overlap_end_sec)
{
double hi = -1.0;
double lo = 9999999.0;
for(int k = i; k >= 0; k--)
{
MqlDateTime dt_k;
TimeToStruct(time[k], dt_k);
if(dt_k.day != bar_day) break;
int k_sec = dt_k.hour * 3600 + dt_k.min * 60;
if(k_sec >= g_lon_start_sec && k_sec < g_lon_end_sec)
{
if(high[k] > hi) hi = high[k];
if(low[k] < lo) lo = low[k];
}
if(k_sec < g_lon_start_sec) break;
}
if(hi > 0 && lo < 9999999.0)
{
BufferHigh[i] = hi;
BufferLow[i] = lo;
BufferMid[i] = (hi + lo) / 2.0;
// ADR Exhaustion Math
double current_range = hi - lo;
double pct_consumed = (current_range / adr) * 100.0;
bool is_exhausted = (pct_consumed >= InpAdrThreshold);
if(prev_calculated > 0 && i == rates_total - 1)
{
// Update UI on live edge
UpdateVisualStatus(pct_consumed, is_exhausted);
if(bar_sec >= g_lon_end_sec && bar_sec < g_overlap_end_sec)
{
// Gate alerts behind the exhaustion check
if(!is_exhausted)
{
if(high[i] > hi && g_last_high_sweep_day != bar_day)
{
string msg = StringFormat("NY Overlap Sweep (GO): %s swept London High. (ADR: %.1f%%)", _Symbol, pct_consumed);
TriggerNotification(msg);
g_last_high_sweep_day = bar_day;
}
if(low[i] < lo && g_last_low_sweep_day != bar_day)
{
string msg = StringFormat("NY Overlap Sweep (GO): %s swept London Low. (ADR: %.1f%%)", _Symbol, pct_consumed);
TriggerNotification(msg);
g_last_low_sweep_day = bar_day;
}
}
}
}
}
else
{
BufferHigh[i] = EMPTY_VALUE;
BufferLow[i] = EMPTY_VALUE;
BufferMid[i] = EMPTY_VALUE;
}
}
else
{
BufferHigh[i] = EMPTY_VALUE;
BufferLow[i] = EMPTY_VALUE;
BufferMid[i] = EMPTY_VALUE;
// Clear UI when out of session
if(prev_calculated > 0 && i == rates_total - 1)
{
ObjectDelete(0, "ADR_Status_Label");
}
}
}
return(rates_total);
}