//+------------------------------------------------------------------+ //| SessionLevels.mq5 | //| Copyright 2026, Allan Niñal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| Trading sessions (Asia, London, New York) as translucent boxes | //| with their high and low, plus the previous day and previous week | //| high/low and today's open as lines. Session times are entered in | //| New York time and converted per bar from the broker's clock, so | //| daylight saving is right on any broker. A trading day ends at | //| the 17:00 New York close. | //| Alerts (closed bars only, each at most once per trading day): | //| close above the previous day high / below the previous day low, | //| and after the Asia session, close above its high / below its low.| //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #property copyright "Copyright 2026, Allan Niñal " #property link "https://www.allanninal.dev/mql5" #property version "1.2" #property description "Asia / London / New York session boxes, previous day and week high/low, today's open. Times in New York time, DST-safe." #property description "Alerts on closed bars: break of the previous day high/low and of the Asia range. Non-repainting." #property description "Optional fakeout marker: a break of the overnight range that closes back inside it (a marker and an alert, not a trade signal)." #property indicator_chart_window #property indicator_buffers 28 #property indicator_plots 8 #property indicator_label1 "Prev day high" #property indicator_type1 DRAW_NONE #property indicator_label2 "Prev day low" #property indicator_type2 DRAW_NONE #property indicator_label3 "Prev week high" #property indicator_type3 DRAW_NONE #property indicator_label4 "Prev week low" #property indicator_type4 DRAW_NONE #property indicator_label5 "Asia high (so far)" #property indicator_type5 DRAW_NONE #property indicator_label6 "Asia low (so far)" #property indicator_type6 DRAW_NONE #property indicator_label7 "Signal" #property indicator_type7 DRAW_NONE #property indicator_label8 "Fakeout" #property indicator_type8 DRAW_NONE enum ENUM_SL_CLOCK { SL_CLOCK_AUTO=0, // Auto: detect from the live server time SL_CLOCK_NY=1, // New York close: UTC+2/+3 with US daylight saving SL_CLOCK_EET=2, // UTC+2/+3 with European daylight saving SL_CLOCK_FIXED=3 // Fixed offset from UTC (set below), no daylight saving }; input int InpDays = 5; // Days to draw input group "Asia session (New York time)" input bool InpShowAsia = true; // Draw Asia input string InpAsiaStart = "19:00"; // Asia start HH:MM input string InpAsiaEnd = "04:00"; // Asia end HH:MM input color InpAsiaColor = 16748574; // Asia colour input group "London session (New York time)" input bool InpShowLondon = true; // Draw London input string InpLondonStart = "03:00"; // London start HH:MM input string InpLondonEnd = "12:00"; // London end HH:MM input color InpLondonColor = 42495; // London colour input group "New York session (New York time)" input bool InpShowNY = true; // Draw New York input string InpNYStart = "08:00"; // New York start HH:MM input string InpNYEnd = "17:00"; // New York end HH:MM input color InpNYColor = 7451452; // New York colour input group "Session boxes" input bool InpSessionLabels = true; // Label each box input bool InpFillBoxes = true; // Fill the boxes (translucent) input group "Levels" input bool InpShowPD = true; // Previous day high/low input bool InpShowPW = true; // Previous week high/low input bool InpShowOpen = true; // Today's open input color InpPDColor = 55295; // Previous day colour input color InpPWColor = 14053594; // Previous week colour input color InpOpenColor = 12632256; // Today's open colour input bool InpLevelLabels = true; // Price labels on the levels input int InpLevelWidth = 1; // Line width input group "Broker clock" input ENUM_SL_CLOCK InpClock = SL_CLOCK_AUTO; // Broker server clock input int InpFixedUTC = 0; // Broker offset from UTC, hours (Fixed offset only) input group "Alert kinds" input bool InpAlertPDH = true; // Close above previous day high input bool InpAlertPDL = true; // Close below previous day low input bool InpAlertAsiaH = true; // Close above Asia high (after Asia) input bool InpAlertAsiaL = true; // Close below Asia low (after Asia) #include "SignalAlerts.mqh" #include "ObjSync.mqh" #include //--- new in 1.2, declared after the alert inputs so the order of every earlier input is unchanged for iCustom callers input group "Fakeout marker (overnight range, New York time)" input bool InpFoOn = true; // Mark fakeouts of the overnight range input string InpFoRangeStart= "17:00"; // Range start HH:MM input string InpFoRangeEnd = "02:00"; // Range end HH:MM input string InpFoWinStart = "02:00"; // Fakeout window start HH:MM input string InpFoWinEnd = "11:00"; // Fakeout window end HH:MM input int InpFoBars = 2; // Bars to close back inside (1 = break bar only, 2 = break bar or the next) input bool InpFoTrend = false; // Daily-structure filter (lower highs and lows: bearish only; higher: bullish only) input bool InpFoMarkers = true; // Draw the marker (arrow and label) input color InpFoBearColor = clrTomato; // Bearish fakeout colour (above the high) input color InpFoBullColor = clrDodgerBlue; // Bullish fakeout colour (below the low) input bool InpAlertFakeout= true; // Alert on a fakeout input group "Support" input bool InpDebug = false; // Debug log for bug reports (Experts tab) //--- buffers shown to the outside double g_pdh[],g_pdl[],g_pwh[],g_pwl[],g_ash[],g_asl[],g_sig[],g_fo[]; //--- internal state, one value per bar so a recalculation can restart from any bar double g_nyw[]; // New York wall time of the bar (seconds) double g_dk[]; // trading-day number (New York close clock) double g_wk[]; // trading-week number double g_dhi[],g_dlo[],g_dop[]; // the day so far double g_whi[],g_wlo[]; // the week so far double g_aown[]; // owner date of the running Asia instance double g_adk[]; // trading day the running Asia instance belongs to double g_ahi[],g_alo[]; double g_flag[]; // signals already given today: 1 PDH, 2 PDL, 4 Asia high, 8 Asia low double g_frh[],g_frl[],g_frc[]; // fakeout range so far today: high, low, bar count double g_fsh[],g_fsl[]; // fakeout state per side: 0 no break yet, n>0 broke n bars ago and waiting, -1 finished double g_ftr[]; // daily structure of today: +1 higher highs and lows, -1 lower, 0 mixed double g_dord[]; // trading-day ordinal (0 = first day on the chart) double g_dayhi[],g_daylo[]; // per trading-day ordinal: final high and low (indexed by ordinal, not by bar) const string PRODUCT="Session Levels"; const int NY_CLOSE_SHIFT=7*3600; // New York-close clock minus New York wall clock ENUM_SL_CLOCK g_clock=SL_CLOCK_NY; int g_fixed_off=0; bool g_native=true; long g_sh_hour=-1; int g_sh_val=0; int g_s_start[3],g_s_end[3]; int g_fo_rs=-1,g_fo_re=-1,g_fo_ws=-1,g_fo_we=-1,g_fo_need=1; bool g_fo_ok=false; bool g_s_on[3]; color g_s_clr[3]; string g_s_name[3]={"Asia","London","New York"}; string g_pfx=""; int g_obj_fail=0,g_dbg_fail=0; // objects the chart refused in the last pass / last reported datetime g_draw_bar=0; uint g_draw_tick=0; double g_draw_hi=0,g_draw_lo=0; int g_secs=60; bool g_no_draw=false; // set in OnInit: tester without visual mode (no chart to draw on) //+------------------------------------------------------------------+ //| Broker clock -> New York time (ported from Gold Trend Sentinel) | //+------------------------------------------------------------------+ //--- Sunday on or after `day` of `month` in year `y`, at `hour` UTC datetime SundayFrom(const int y,const int month,const int day,const int hour) { MqlDateTime t; ZeroMemory(t); t.year=y; t.mon=month; t.day=day; datetime d=StructToTime(t); TimeToStruct(d,t); return(d+(datetime)(((7-t.day_of_week)%7)*86400+hour*3600)); } //--- US daylight saving (second Sunday of March 07:00 UTC to first Sunday of November 06:00 UTC) bool UsDst(const datetime utc) { MqlDateTime t; TimeToStruct(utc,t); return(utc>=SundayFrom(t.year,3,8,7) && utc=SundayFrom(t.year,3,25,1) && utc minutes since midnight, -1 when invalid int ParseHM(const string s) { string p[]; if(StringSplit(s,':',p)!=2) return(-1); int h=(int)StringToInteger(p[0]),m=(int)StringToInteger(p[1]); if(h<0 || h>23 || m<0 || m>59) return(-1); return(h*60+m); } //--- is minute-of-day `m` inside [s,e) ? A session with s>e crosses midnight bool InSession(const int m,const int s,const int e) { if(s==e) return(false); if(s=s && m=s || me && m>=s) ? d+1 : d); } //--- daily structure for trading-day ordinal `ord` from the days before it: the last two confirmed swing highs and //--- lows (3 days each side; a swing is confirmed 3 days later). +1 both rose, -1 both fell, 0 otherwise. int DailyStructure(const int ord) { double hs[2],ls[2]; int nh=0,nl=0; for(int j=ord-4; j>=3 && (nh<2 || nl<2); j--) { double mh=g_dayhi[j],ml=g_daylo[j]; for(int q=j-3; q<=j+3; q++) { mh=MathMax(mh,g_dayhi[q]); ml=MathMin(ml,g_daylo[q]); } if(nh<2 && g_dayhi[j]==mh) hs[nh++]=g_dayhi[j]; if(nl<2 && g_daylo[j]==ml) ls[nl++]=g_daylo[j]; } if(nh<2 || nl<2) return(0); //--- hs[0] is the latest swing high, hs[1] the one before if(hs[0]>hs[1] && ls[0]>ls[1]) return(1); if(hs[0]5 || InpFixedUTC<-12 || InpFixedUTC>14) { Print("Session Levels: days to draw at least 1, line width 1-5, broker offset from UTC -12 to 14 hours"); CkDbg("init refused: days, line width or UTC offset"); return(INIT_PARAMETERS_INCORRECT); } SetIndexBuffer(0,g_pdh,INDICATOR_DATA); SetIndexBuffer(1,g_pdl,INDICATOR_DATA); SetIndexBuffer(2,g_pwh,INDICATOR_DATA); SetIndexBuffer(3,g_pwl,INDICATOR_DATA); SetIndexBuffer(4,g_ash,INDICATOR_DATA); SetIndexBuffer(5,g_asl,INDICATOR_DATA); SetIndexBuffer(6,g_sig,INDICATOR_DATA); SetIndexBuffer(7,g_fo,INDICATOR_DATA); SetIndexBuffer(8,g_nyw,INDICATOR_CALCULATIONS); SetIndexBuffer(9,g_dk,INDICATOR_CALCULATIONS); SetIndexBuffer(10,g_wk,INDICATOR_CALCULATIONS); SetIndexBuffer(11,g_dhi,INDICATOR_CALCULATIONS); SetIndexBuffer(12,g_dlo,INDICATOR_CALCULATIONS); SetIndexBuffer(13,g_dop,INDICATOR_CALCULATIONS); SetIndexBuffer(14,g_whi,INDICATOR_CALCULATIONS); SetIndexBuffer(15,g_wlo,INDICATOR_CALCULATIONS); SetIndexBuffer(16,g_aown,INDICATOR_CALCULATIONS); SetIndexBuffer(17,g_adk,INDICATOR_CALCULATIONS); SetIndexBuffer(18,g_ahi,INDICATOR_CALCULATIONS); SetIndexBuffer(19,g_alo,INDICATOR_CALCULATIONS); SetIndexBuffer(20,g_flag,INDICATOR_CALCULATIONS); SetIndexBuffer(21,g_frh,INDICATOR_CALCULATIONS); SetIndexBuffer(22,g_frl,INDICATOR_CALCULATIONS); SetIndexBuffer(23,g_frc,INDICATOR_CALCULATIONS); SetIndexBuffer(24,g_fsh,INDICATOR_CALCULATIONS); SetIndexBuffer(25,g_fsl,INDICATOR_CALCULATIONS); SetIndexBuffer(26,g_ftr,INDICATOR_CALCULATIONS); SetIndexBuffer(27,g_dord,INDICATOR_CALCULATIONS); for(int p=0; p<8; p++) PlotIndexSetDouble(p,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetString(INDICATOR_SHORTNAME,"Session Levels"); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); g_fo_rs=ParseHM(InpFoRangeStart); g_fo_re=ParseHM(InpFoRangeEnd); g_fo_ws=ParseHM(InpFoWinStart); g_fo_we=ParseHM(InpFoWinEnd); if(g_fo_rs<0 || g_fo_re<0 || g_fo_ws<0 || g_fo_we<0 || InpFoBars<1 || InpFoBars>5) { Print("Session Levels: fakeout times must be HH:MM and the bars to close back inside 1 to 5"); CkDbg("init refused: fakeout times or bars"); return(INIT_PARAMETERS_INCORRECT); } g_secs=PeriodSeconds(); //--- fakeout: M1 to H1 only; the range must hold at least 5/6 of its bars (30 of 36 on M15 for a 9-hour range) int rmin=((g_fo_re-g_fo_rs)%1440+1440)%1440; g_fo_ok=(InpFoOn && g_secs<=3600 && rmin>0 && g_fo_ws!=g_fo_we); g_fo_need=MathMax(1,(int)MathCeil((double)rmin*60.0/(double)g_secs*30.0/36.0)); ResolveClock(); CkDbg(StringFormat("clock resolved: mode %d, offset from UTC %d s, %s",(int)g_clock,g_fixed_off,g_native ? "server time is New York close" : "converted per bar")); if(MQLInfoInteger(MQL_TESTER) && InpClock==SL_CLOCK_AUTO) Print("Session Levels: the Strategy Tester can't detect the broker's clock; New York close is assumed. ", "Set 'Broker server clock' for a broker on another clock."); g_secs=PeriodSeconds(); g_pfx="SLV"+IntegerToString((int)(GetMicrosecondCount()%1000000))+"_"; g_draw_bar=0; g_obj_fail=0; g_dbg_fail=0; g_no_draw=(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_VISUAL_MODE)); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(g_pfx!="") { ObjectsDeleteAll(0,g_pfx); ChartRedraw(); } } //+------------------------------------------------------------------+ //| Drawing | //+------------------------------------------------------------------+ void PutObj(const string name,const ENUM_OBJECT type,const datetime t1,const double p1,const datetime t2,const double p2) { if(!OsPlace(name,type,2,t1,p1,t2,p2)) { g_obj_fail++; return; } OsInt(name,OBJPROP_BACK,true); } void PutText(const string name,const datetime t,const double price,const string text,const color clr,const ENUM_ANCHOR_POINT anchor) { if(!OsPlace(name,OBJ_TEXT,1,t,price,0,0)) { g_obj_fail++; return; } OsStr(name,OBJPROP_TEXT,text); OsInt(name,OBJPROP_COLOR,clr); OsInt(name,OBJPROP_ANCHOR,anchor); OsInt(name,OBJPROP_FONTSIZE,8); } void DrawBox(const int s,const long owner,const datetime t1,const datetime t2,const double hi,const double lo) { string nm=g_pfx+"B"+IntegerToString(s)+"_"+IntegerToString(owner); PutObj(nm,OBJ_RECTANGLE,t1,hi,t2,lo); OsInt(nm,OBJPROP_COLOR,InpFillBoxes ? ColorToARGB(g_s_clr[s],60) : g_s_clr[s]); OsInt(nm,OBJPROP_FILL,InpFillBoxes); OsInt(nm,OBJPROP_WIDTH,1); if(InpSessionLabels) PutText(g_pfx+"T"+IntegerToString(s)+"_"+IntegerToString(owner),t1,hi,g_s_name[s],g_s_clr[s],ANCHOR_LEFT_LOWER); } void DrawLevel(const string tag,const string label,const double price,const color clr,const int a,const int b, const bool current,const datetime &time[],const int last,const ENUM_LINE_STYLE style) { if(price==EMPTY_VALUE || price<=0) return; datetime t1=time[a],t2=(current ? time[last]+g_secs : time[b]+g_secs); string nm=g_pfx+"L"+tag+"_"+IntegerToString((long)time[a]); PutObj(nm,OBJ_TREND,t1,price,t2,price); OsInt(nm,OBJPROP_COLOR,clr); OsInt(nm,OBJPROP_WIDTH,InpLevelWidth); OsInt(nm,OBJPROP_STYLE,style); OsInt(nm,OBJPROP_RAY_RIGHT,current); if(current && InpLevelLabels) PutText(g_pfx+"P"+tag,time[last]+3*g_secs,price,label+" "+DoubleToString(price,_Digits),clr,ANCHOR_LEFT); } //--- redraw everything for the last InpDays days (objects are deleted first, so old days disappear) void Draw(const int rates_total,const datetime &time[],const double &high[],const double &low[]) { int last=rates_total-1; bool intraday=(g_secs<=PeriodSeconds(PERIOD_H4)); int days=(intraday ? MathMin(InpDays,60) : 1); OsBegin(); g_obj_fail=0; //--- first bar of the oldest day to draw int first=last,cnt=1; for(int k=last; k>0; k--) { if(g_dk[k-1]!=g_dk[k]) { if(cnt>=days) break; cnt++; } first=k-1; } //--- session boxes if(intraday) for(int s=0; s<3; s++) { if(!g_s_on[s]) continue; bool open=false; long owner=0; int st=0,en=0; double hi=0,lo=0; for(int j=first; j<=last; j++) { int m=(int)(((long)g_nyw[j]%86400)/60); if(InSession(m,g_s_start[s],g_s_end[s])) { long ow=OwnerDate((long)g_nyw[j],m,g_s_start[s],g_s_end[s]); if(open && ow==owner) { en=j; hi=MathMax(hi,high[j]); lo=MathMin(lo,low[j]); } else { if(open) DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo); open=true; owner=ow; st=j; en=j; hi=high[j]; lo=low[j]; } } else if(open) { DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo); open=false; } } if(open) DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo); } //--- fakeout markers: an arrow and the label in one text object at the confirming bar if(InpFoMarkers && g_fo_ok) for(int j=first; j0 ? prev_calculated-1 : 0); for(int i=start; i0 ? g_dhi[i-1] : EMPTY_VALUE); g_pdl[i]=(i>0 ? g_dlo[i-1] : EMPTY_VALUE); g_dhi[i]=high[i]; g_dlo[i]=low[i]; g_dop[i]=open[i]; } else { g_pdh[i]=g_pdh[i-1]; g_pdl[i]=g_pdl[i-1]; g_dhi[i]=MathMax(g_dhi[i-1],high[i]); g_dlo[i]=MathMin(g_dlo[i-1],low[i]); g_dop[i]=g_dop[i-1]; fl=(int)g_flag[i-1]; } //--- trading-day ordinal and the daily structure (built from the chart's own bars) if(new_day) { int ord=(i>0 ? (int)g_dord[i-1]+1 : 0); if(ArraySize(g_dayhi)0) { g_dayhi[ord-1]=g_dhi[i-1]; g_daylo[ord-1]=g_dlo[i-1]; } g_dord[i]=ord; g_ftr[i]=(g_fo_ok && InpFoTrend ? DailyStructure(ord) : 0); } else { g_dord[i]=g_dord[i-1]; g_ftr[i]=g_ftr[i-1]; } //--- week if(new_week) { g_pwh[i]=(i>0 ? g_whi[i-1] : EMPTY_VALUE); g_pwl[i]=(i>0 ? g_wlo[i-1] : EMPTY_VALUE); g_whi[i]=high[i]; g_wlo[i]=low[i]; } else { g_pwh[i]=g_pwh[i-1]; g_pwl[i]=g_pwl[i-1]; g_whi[i]=MathMax(g_whi[i-1],high[i]); g_wlo[i]=MathMin(g_wlo[i-1],low[i]); } //--- Asia range of the current trading day if(i>0) { g_aown[i]=g_aown[i-1]; g_adk[i]=g_adk[i-1]; g_ahi[i]=g_ahi[i-1]; g_alo[i]=g_alo[i-1]; } else { g_aown[i]=-1; g_adk[i]=-1; g_ahi[i]=0; g_alo[i]=0; } bool in_asia=InSession(m,g_s_start[0],g_s_end[0]); if(in_asia) { long ow=OwnerDate(nyw,m,g_s_start[0],g_s_end[0]); if((long)g_aown[i]!=ow) { g_aown[i]=(double)ow; g_adk[i]=(double)dk; g_ahi[i]=high[i]; g_alo[i]=low[i]; } else { g_ahi[i]=MathMax(g_ahi[i],high[i]); g_alo[i]=MathMin(g_alo[i],low[i]); } } bool asia_ok=((long)g_adk[i]==dk && g_aown[i]!=-1); g_ash[i]=(asia_ok ? g_ahi[i] : EMPTY_VALUE); g_asl[i]=(asia_ok ? g_alo[i] : EMPTY_VALUE); //--- fakeout of the overnight range: a bar trades beyond one side inside the window and that bar (or one of the //--- next InpFoBars-1) closes back inside the range. Decided on the bar's close; once per side per day. int fo=0; double fo_hi=0,fo_lo=0; if(g_fo_ok) { if(new_day) { g_frh[i]=0; g_frl[i]=0; g_frc[i]=0; g_fsh[i]=0; g_fsl[i]=0; } else { g_frh[i]=g_frh[i-1]; g_frl[i]=g_frl[i-1]; g_frc[i]=g_frc[i-1]; g_fsh[i]=g_fsh[i-1]; g_fsl[i]=g_fsl[i-1]; } if(InSession(m,g_fo_rs,g_fo_re)) { g_frh[i]=(g_frc[i]==0 ? high[i] : MathMax(g_frh[i],high[i])); g_frl[i]=(g_frc[i]==0 ? low[i] : MathMin(g_frl[i],low[i])); g_frc[i]+=1; } else if(InSession(m,g_fo_ws,g_fo_we) && (int)g_frc[i]>=g_fo_need) { double rh=g_frh[i],rl=g_frl[i]; bool inside=(close[i]rl); //--- high side: bearish fakeout int st=(int)g_fsh[i]; if(st>=0 && (!InpFoTrend || (int)g_ftr[i]==-1)) { if(st>0 || high[i]>rh) { st++; if(inside) { fo|=1; fo_hi=rh; st=-1; } else if(st>=InpFoBars) st=-1; } g_fsh[i]=st; } //--- low side: bullish fakeout st=(int)g_fsl[i]; if(st>=0 && (!InpFoTrend || (int)g_ftr[i]==1)) { if(st>0 || low[i]=InpFoBars) st=-1; } g_fsl[i]=st; } } else { //--- outside the window a pending break can no longer be confirmed if(g_fsh[i]>0) g_fsh[i]=-1; if(g_fsl[i]>0) g_fsl[i]=-1; } } g_fo[i]=EMPTY_VALUE; //--- signals: closed bars only, each kind once per trading day g_sig[i]=EMPTY_VALUE; if(i<=rates_total-2) { int kinds=0; if(g_pdh[i]!=EMPTY_VALUE) { if((fl&1)==0 && close[i]>g_pdh[i]) kinds|=1; if((fl&2)==0 && close[i]g_ahi[i]) kinds|=4; if((fl&8)==0 && close[i]0); } } } g_flag[i]=fl; } if(g_no_draw) return(rates_total); //--- objects: on a new bar, or (throttled) when the live bar widens the live session int last=rates_total-1; bool newbar=(time[last]!=g_draw_bar); bool widen=(g_secs<=PeriodSeconds(PERIOD_H4) && (high[last]>g_draw_hi || low[last]=1000); if(newbar || widen) Draw(rates_total,time,high,low); return(rates_total); } //+------------------------------------------------------------------+