//+------------------------------------------------------------------+ //| DinexSpace | Session Boxes | //| Draws the Asia (Tokyo), Europe (London) and US (New York) | //| sessions as boxes spanning each session's high and low. | //| Session hours are entered in each market's local time; London | //| and New York daylight saving is applied automatically. | //+------------------------------------------------------------------+ #property copyright "(c) 2026 DinexSpace" #property version "1.00" #property description "Asia, Europe and US trading sessions as boxes with their high-low range." #property description "Hours are in each market's local time; daylight saving is handled automatically." #property strict #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 //--- server time handling enum ENUM_SERVER_TZ { SERVER_AUTO_US_DST = 0, // Auto, broker shifts with US DST (most brokers) SERVER_AUTO_FIXED = 1, // Auto, fixed offset all year SERVER_MANUAL = 2 // Manual offset (below) }; enum ENUM_RANGE_UNIT { UNIT_PRICE = 0, // Price UNIT_POINTS = 1, // Points UNIT_PIPS = 2 // Pips (forex) }; input group "Sessions (hours in each market's local time)" input bool InpShowAsia = true; // Show Asia (Tokyo, UTC+9) input int InpAsiaStart = 900; // Asia start (HHMM) input int InpAsiaEnd = 1800; // Asia end (HHMM) input color InpAsiaColor = C'60,50,120'; // Asia colour input bool InpShowLondon = true; // Show Europe (London, UK daylight saving) input int InpLonStart = 800; // Europe start (HHMM) input int InpLonEnd = 1700; // Europe end (HHMM) input color InpLonColor = C'110,80,0'; // Europe colour input bool InpShowNY = true; // Show US (New York, US daylight saving) input int InpNYStart = 800; // US start (HHMM) input int InpNYEnd = 1700; // US end (HHMM) input color InpNYColor = C'110,40,70'; // US colour input group "Server time" input ENUM_SERVER_TZ InpServerTz = SERVER_AUTO_US_DST; // Server time zone input double InpServerOffset = 2.0; // Manual server offset from UTC (hours) input group "Display" input bool InpFill = true; // Fill boxes (drawn behind candles) input bool InpLabels = true; // Label with name and range input ENUM_RANGE_UNIT InpUnit = UNIT_PRICE; // Range unit input bool InpMidLine = false; // Mid-line (50% of range) input int InpKeep = 20; // Sessions to keep on chart (each) input int InpMaxBars = 5000; // Bars to scan input bool InpPanel = true; // Status panel input ENUM_BASE_CORNER InpCorner = CORNER_RIGHT_UPPER; // Panel corner #define NS 3 const string PFX = "DNXSB_"; string g_name[NS] = {"Asia", "London", "New York"}; bool g_show[NS]; int g_start[NS], g_end[NS]; // minutes of day, local color g_col[NS]; datetime g_key[NS]; // start time (UTC) of the session being tracked datetime g_t0[NS]; // server time of first bar of that session double g_hi[NS], g_lo[NS]; bool g_open[NS]; // session contains the latest bar struct SHist { datetime k[]; }; // keys of drawn sessions (oldest first) SHist g_hist[NS]; //+------------------------------------------------------------------+ //| Calendar helpers | //+------------------------------------------------------------------+ datetime MakeDate(int y, int m, int d) { MqlDateTime s; ZeroMemory(s); s.year = y; s.mon = m; s.day = d; return StructToTime(s); } // Day of month of the n-th Sunday (n >= 1) or the last Sunday (n = 0) of a month. int SundayOf(int y, int m, int n) { if(n > 0) { MqlDateTime s; TimeToStruct(MakeDate(y, m, 1), s); int first = 1 + (7 - s.day_of_week) % 7; return first + 7 * (n - 1); } int nm = (m == 12) ? 1 : m + 1, ny = (m == 12) ? y + 1 : y; MqlDateTime e; TimeToStruct(MakeDate(ny, nm, 1) - 86400, e); return e.day - e.day_of_week; } // US daylight saving: second Sunday of March 07:00 UTC to first Sunday of November 06:00 UTC. bool IsUsDst(datetime utc) { MqlDateTime s; TimeToStruct(utc, s); datetime a = MakeDate(s.year, 3, SundayOf(s.year, 3, 2)) + 7 * 3600; datetime b = MakeDate(s.year, 11, SundayOf(s.year, 11, 1)) + 6 * 3600; return utc >= a && utc < b; } // EU daylight saving: last Sunday of March 01:00 UTC to last Sunday of October 01:00 UTC. bool IsEuDst(datetime utc) { MqlDateTime s; TimeToStruct(utc, s); datetime a = MakeDate(s.year, 3, SundayOf(s.year, 3, 0)) + 3600; datetime b = MakeDate(s.year, 10, SundayOf(s.year, 10, 0)) + 3600; return utc >= a && utc < b; } //+------------------------------------------------------------------+ //| Time zones | //+------------------------------------------------------------------+ int g_baseOffset = 0; // server offset from UTC in seconds (standard time for SERVER_AUTO_US_DST) void DetectServerOffset() { if(InpServerTz == SERVER_MANUAL) { g_baseOffset = (int)MathRound(InpServerOffset * 3600.0); return; } datetime srv = TimeTradeServer(), gmt = TimeGMT(); int off = (int)MathRound((double)(srv - gmt) / 1800.0) * 1800; if(InpServerTz == SERVER_AUTO_US_DST && IsUsDst(gmt)) off -= 3600; g_baseOffset = off; } datetime ServerToUtc(datetime t) { datetime u = t - g_baseOffset; if(InpServerTz == SERVER_AUTO_US_DST && IsUsDst(u)) u -= 3600; return u; } // Offset of session market s from UTC at time utc, in seconds. int MarketOffset(int s, datetime utc) { if(s == 0) return 9 * 3600; // Tokyo, no daylight saving if(s == 1) return IsEuDst(utc) ? 3600 : 0; // London return IsUsDst(utc) ? -4 * 3600 : -5 * 3600; // New York } int HhmmToMin(int v) { return (v / 100) * 60 + v % 100; } // If the bar at UTC time `utc` is inside session s, returns true and the session's start time (UTC). bool InSession(int s, datetime utc, datetime &key) { int off = MarketOffset(s, utc); datetime loc = utc + off; MqlDateTime d; TimeToStruct(loc, d); int mins = d.hour * 60 + d.min; int st = g_start[s], en = g_end[s]; datetime dayStart = loc - (d.hour * 3600 + d.min * 60 + d.sec); bool inS; datetime startLoc; if(st < en) { inS = mins >= st && mins < en; startLoc = dayStart + st * 60; } else { inS = mins >= st || mins < en; startLoc = (mins >= st) ? dayStart + st * 60 : dayStart - 86400 + st * 60; } if(!inS) return false; MqlDateTime sd; TimeToStruct(startLoc, sd); if(sd.day_of_week == 0 || sd.day_of_week == 6) return false; // no sessions starting on weekends key = startLoc - off; return true; } //+------------------------------------------------------------------+ //| Drawing | //+------------------------------------------------------------------+ string FmtRange(double r) { if(InpUnit == UNIT_POINTS) return IntegerToString((long)MathRound(r / _Point)) + " pts"; if(InpUnit == UNIT_PIPS) { double pip = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point; return DoubleToString(r / pip, 1) + " pips"; } return DoubleToString(r, _Digits); } string ObjName(int s, datetime key, string part) { return PFX + IntegerToString(s) + "_" + IntegerToString((long)key) + "_" + part; } void DeleteSession(int s, datetime key) { ObjectDelete(0, ObjName(s, key, "box")); ObjectDelete(0, ObjName(s, key, "lbl")); ObjectDelete(0, ObjName(s, key, "mid")); } void DrawSession(int s, datetime key, datetime t0, datetime t1, double hi, double lo) { string bx = ObjName(s, key, "box"); if(ObjectFind(0, bx) < 0) { ObjectCreate(0, bx, OBJ_RECTANGLE, 0, t0, hi, t1, lo); ObjectSetInteger(0, bx, OBJPROP_COLOR, g_col[s]); ObjectSetInteger(0, bx, OBJPROP_FILL, InpFill); ObjectSetInteger(0, bx, OBJPROP_BACK, true); ObjectSetInteger(0, bx, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, bx, OBJPROP_HIDDEN, true); } ObjectMove(0, bx, 0, t0, hi); ObjectMove(0, bx, 1, t1, lo); if(InpLabels) { string lb = ObjName(s, key, "lbl"); if(ObjectFind(0, lb) < 0) { ObjectCreate(0, lb, OBJ_TEXT, 0, t0, hi); ObjectSetInteger(0, lb, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); ObjectSetInteger(0, lb, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, lb, OBJPROP_COLOR, clrSilver); ObjectSetInteger(0, lb, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, lb, OBJPROP_HIDDEN, true); } ObjectMove(0, lb, 0, t0, hi); ObjectSetString(0, lb, OBJPROP_TEXT, g_name[s] + " " + FmtRange(hi - lo)); } if(InpMidLine) { string md = ObjName(s, key, "mid"); double mid = (hi + lo) / 2.0; if(ObjectFind(0, md) < 0) { ObjectCreate(0, md, OBJ_TREND, 0, t0, mid, t1, mid); ObjectSetInteger(0, md, OBJPROP_COLOR, g_col[s]); ObjectSetInteger(0, md, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, md, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, md, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, md, OBJPROP_HIDDEN, true); } ObjectMove(0, md, 0, t0, mid); ObjectMove(0, md, 1, t1, mid); } } void PanelLine(int row, string text, color col) { string nm = PFX + "panel_" + IntegerToString(row); if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, nm, OBJPROP_CORNER, InpCorner); bool right = (InpCorner == CORNER_RIGHT_UPPER || InpCorner == CORNER_RIGHT_LOWER); bool lower = (InpCorner == CORNER_LEFT_LOWER || InpCorner == CORNER_RIGHT_LOWER); ObjectSetInteger(0, nm, OBJPROP_ANCHOR, right ? (lower ? ANCHOR_RIGHT_LOWER : ANCHOR_RIGHT_UPPER) : (lower ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER)); ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, 20 + (lower ? 4 - row : row) * 16); ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 9); ObjectSetString(0, nm, OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); } ObjectSetString(0, nm, OBJPROP_TEXT, text); ObjectSetInteger(0, nm, OBJPROP_COLOR, col); } void UpdatePanel() { if(!InpPanel) return; if(PeriodSeconds(_Period) >= 86400) { PanelLine(0, "Sessions need an intraday chart", clrOrange); PanelLine(4, "DinexSpace", clrSpringGreen); return; } PanelLine(0, "Session Status Range", clrGray); for(int s = 0; s < NS; s++) { string rng = (g_key[s] > 0) ? FmtRange(g_hi[s] - g_lo[s]) : "-"; string st = !g_show[s] ? "off" : (g_open[s] ? "open" : "closed"); PanelLine(1 + s, StringFormat("%-12s %-8s %s", g_name[s], st, rng), g_open[s] ? clrSpringGreen : clrSilver); } PanelLine(4, "DinexSpace", clrSpringGreen); } //+------------------------------------------------------------------+ //| Indicator events | //+------------------------------------------------------------------+ void ResetState() { for(int s = 0; s < NS; s++) { g_key[s] = 0; g_t0[s] = 0; g_hi[s] = 0; g_lo[s] = 0; g_open[s] = false; ArrayResize(g_hist[s].k, 0); } } int OnInit() { g_show[0] = InpShowAsia; g_start[0] = HhmmToMin(InpAsiaStart); g_end[0] = HhmmToMin(InpAsiaEnd); g_col[0] = InpAsiaColor; g_show[1] = InpShowLondon; g_start[1] = HhmmToMin(InpLonStart); g_end[1] = HhmmToMin(InpLonEnd); g_col[1] = InpLonColor; g_show[2] = InpShowNY; g_start[2] = HhmmToMin(InpNYStart); g_end[2] = HhmmToMin(InpNYEnd); g_col[2] = InpNYColor; for(int s = 0; s < NS; s++) if(g_start[s] < 0 || g_start[s] >= 1440 || g_end[s] < 0 || g_end[s] >= 1440 || g_start[s] == g_end[s]) { Print("DinexSpace Session Boxes: invalid hours for ", g_name[s], " (use HHMM, start different from end)"); return INIT_PARAMETERS_INCORRECT; } if(InpKeep < 1 || InpMaxBars < 10) return INIT_PARAMETERS_INCORRECT; DetectServerOffset(); ResetState(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { ObjectsDeleteAll(0, PFX); } 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); if(PeriodSeconds(_Period) >= 86400) { UpdatePanel(); return rates_total; } int from; if(prev_calculated == 0) { ObjectsDeleteAll(0, PFX); DetectServerOffset(); ResetState(); from = MathMax(0, rates_total - InpMaxBars); } else from = prev_calculated - 1; for(int i = from; i < rates_total; i++) { datetime utc = ServerToUtc(time[i]); for(int s = 0; s < NS; s++) { if(!g_show[s]) continue; datetime key; bool inS = InSession(s, utc, key); if(i == rates_total - 1) g_open[s] = inS; if(!inS) continue; if(key != g_key[s]) { // a new session starts on this bar g_key[s] = key; g_t0[s] = time[i]; g_hi[s] = high[i]; g_lo[s] = low[i]; int n = ArraySize(g_hist[s].k); ArrayResize(g_hist[s].k, n + 1); g_hist[s].k[n] = key; if(n + 1 > InpKeep) { DeleteSession(s, g_hist[s].k[0]); ArrayRemove(g_hist[s].k, 0, 1); } } else { g_hi[s] = MathMax(g_hi[s], high[i]); g_lo[s] = MathMin(g_lo[s], low[i]); } DrawSession(s, g_key[s], g_t0[s], time[i], g_hi[s], g_lo[s]); } } UpdatePanel(); return rates_total; } //+------------------------------------------------------------------+