//+------------------------------------------------------------------+ //| SessionLevels.mq4 | //| 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.| //| MT4 edition: the same calculation, inputs and 7 iCustom buffers | //| as the MT5 one. MT4 has no transparency, so a filled box is the | //| session colour blended into the chart background. | //+------------------------------------------------------------------+ // 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.1" #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 indicator_chart_window #property indicator_buffers 7 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 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 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 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 bool InpSessionLabels = true; // Label each box input bool InpFillBoxes = true; // Fill the boxes (translucent) 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 ENUM_SL_CLOCK InpClock = SL_CLOCK_AUTO; // Broker server clock input int InpFixedUTC = 0; // Broker offset from UTC, hours (Fixed offset only) 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 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[]; //--- 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 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]; 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) && utc14*3600 || off==NyOffset(gmt)) g_clock=SL_CLOCK_NY; else if(off==(EuDst(gmt) ? 3*3600 : 2*3600)) g_clock=SL_CLOCK_EET; else { g_clock=SL_CLOCK_FIXED; g_fixed_off=off; } } } g_native=(g_clock==SL_CLOCK_NY); g_sh_hour=-1; } //+------------------------------------------------------------------+ //| Helpers | //+------------------------------------------------------------------+ //--- "HH:MM" -> 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); } int OnInit() { CkDebugStart(PRODUCT,"1.1",InpDebug,StringFormat("days %d, sessions Asia %d %s-%s, London %d %s-%s, NY %d %s-%s, PD %d, PW %d, open %d, clock %d (UTC %d)", InpDays,InpShowAsia,InpAsiaStart,InpAsiaEnd,InpShowLondon,InpLondonStart,InpLondonEnd,InpShowNY,InpNYStart,InpNYEnd, InpShowPD,InpShowPW,InpShowOpen,(int)InpClock,InpFixedUTC)); g_s_start[0]=ParseHM(InpAsiaStart); g_s_end[0]=ParseHM(InpAsiaEnd); g_s_start[1]=ParseHM(InpLondonStart); g_s_end[1]=ParseHM(InpLondonEnd); g_s_start[2]=ParseHM(InpNYStart); g_s_end[2]=ParseHM(InpNYEnd); for(int s=0; s<3; s++) if(g_s_start[s]<0 || g_s_end[s]<0) { Print("Session Levels: session times must be HH:MM (00:00 to 23:59)"); CkDbg("init refused: session times"); return(INIT_PARAMETERS_INCORRECT); } g_s_on[0]=InpShowAsia; g_s_clr[0]=InpAsiaColor; g_s_on[1]=InpShowLondon; g_s_clr[1]=InpLondonColor; g_s_on[2]=InpShowNY; g_s_clr[2]=InpNYColor; if(InpDays<1 || InpLevelWidth<1 || InpLevelWidth>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); } IndicatorBuffers(20); 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_nyw,INDICATOR_CALCULATIONS); SetIndexBuffer(8,g_dk,INDICATOR_CALCULATIONS); SetIndexBuffer(9,g_wk,INDICATOR_CALCULATIONS); SetIndexBuffer(10,g_dhi,INDICATOR_CALCULATIONS); SetIndexBuffer(11,g_dlo,INDICATOR_CALCULATIONS); SetIndexBuffer(12,g_dop,INDICATOR_CALCULATIONS); SetIndexBuffer(13,g_whi,INDICATOR_CALCULATIONS); SetIndexBuffer(14,g_wlo,INDICATOR_CALCULATIONS); SetIndexBuffer(15,g_aown,INDICATOR_CALCULATIONS); SetIndexBuffer(16,g_adk,INDICATOR_CALCULATIONS); SetIndexBuffer(17,g_ahi,INDICATOR_CALCULATIONS); SetIndexBuffer(18,g_alo,INDICATOR_CALCULATIONS); SetIndexBuffer(19,g_flag,INDICATOR_CALCULATIONS); string names[7]={"Prev day high","Prev day low","Prev week high","Prev week low","Asia high (so far)", "Asia low (so far)","Signal"}; for(int p=0; p<20; p++) { SetIndexStyle(p,DRAW_NONE); SetIndexEmptyValue(p,EMPTY_VALUE); SetIndexLabel(p,p<7 ? names[p] : NULL); } IndicatorShortName("Session Levels"); IndicatorDigits(_Digits); 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(IsTesting() && 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=(IsTesting() && !IsVisualMode()); 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); } //--- MT4 cannot draw a translucent rectangle: blend the colour into the chart background by `alpha` (0-255) color Blend(const color c,const int alpha) { color bg=(color)ChartGetInteger(0,CHART_COLOR_BACKGROUND); int r=((c&0xFF)*alpha+(bg&0xFF)*(255-alpha))/255; int g=(((c>>8)&0xFF)*alpha+((bg>>8)&0xFF)*(255-alpha))/255; int b=(((c>>16)&0xFF)*alpha+((bg>>16)&0xFF)*(255-alpha))/255; return((color)(r|(g<<8)|(b<<16))); } 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 ? Blend(g_s_clr[s],60) : g_s_clr[s]); OsInt(nm,OBJPROP_FILL,InpFillBoxes); OsInt(nm,OBJPROP_BACK,InpFillBoxes); // MT4 fills a rectangle drawn in the background 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); } //--- level lines, one segment per day; the current day is a ray to the right int a=first; while(a<=last) { int b=a; while(b0 ? 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]; } //--- 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); //--- 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); } //+------------------------------------------------------------------+