406 lines
15 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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;
| |||
}
| |||
//+------------------------------------------------------------------+
|