| //+------------------------------------------------------------------+
|
| //| 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;
|
| }
|
| //+------------------------------------------------------------------+
|