| //+------------------------------------------------------------------+
|
| //| ICTMacros.mq5 |
|
| //| Copyright 2026, MT5 Coder Project |
|
| //+------------------------------------------------------------------+
|
| // WHAT IT DOES
|
| // Marks ICT macro windows as chart objects (rectangle, plus optional
|
| // 50% midline, start/end vertical lines and a label) for the Asia,
|
| // London and New York sessions. Every session has its own ON/OFF input.
|
| //
|
| // Window times are entered in NEW YORK local time as
|
| // "HH:MM-HH:MM;HH:MM-HH:MM". Defaults:
|
| // London : 02:33-03:00;04:03-04:30
|
| // New York: 08:50-09:10;09:50-10:10;10:50-11:10;11:50-12:10;13:10-13:40;15:15-15:45
|
| // Asia : empty by default - paste Asia windows into InpAsiaMacros and
|
| // the Asia toggle starts working immediately.
|
| // A window whose end time is not later than its start time is treated as
|
| // crossing midnight (e.g. 23:30-00:15).
|
| //
|
| // TIME BASE
|
| // Chart bars are in broker server time. Window times are converted
|
| // NY local -> UTC (US DST rules implemented here) -> server time.
|
| // The server/UTC offset is auto-detected as TimeTradeServer()-TimeGMT()
|
| // rounded to 5 minutes, or taken from InpManualServerGmtOffset.
|
| // TimeGMT() depends on the accuracy of the local PC clock; switch to
|
| // ICT_SRV_MANUAL if that clock is not reliable.
|
| //
|
| // OBJECTS
|
| // Prefix "ICTMacros_". Only objects created by this program are removed.
|
| // Windows on closed bars never move. The window that is currently open is
|
| // refreshed while it is open (InpLiveBox). Windows whose bars do not exist
|
| // yet are not drawn, so a later window of the day appears once its first
|
| // bar has formed.
|
| //
|
| // LIMITATIONS
|
| // * Chart objects only - no indicator buffers, nothing for iCustom.
|
| // * MT5 chart objects have no alpha transparency: a filled box is opaque
|
| // (it is drawn behind the bars). Outline only is the default.
|
| // * Best on M1-M15. On coarse timeframes a window can cover 0 or 1 bar.
|
| // * Object count grows with InpHistoryDays - watch InpMaxObjects.
|
| // * Visual tool only: it draws no signals and does not trade.
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2026, MT5 Coder Project"
|
| #property link "https://www.mql5.com"
|
| #property version "1.00"
|
| #property description "ICT macro windows (Asia / London / New York) drawn in New York local time."
|
|
|
| #property indicator_chart_window
|
| #property indicator_buffers 0
|
| #property indicator_plots 0
|
|
|
| //--- object namespace owned by this program
|
| #define ICT_PREFIX "ICTMacros_"
|
| #define ICT_SEC_PER_DAY 86400
|
| #define ICT_MIN_PER_DAY 1440
|
|
|
| //--- enums
|
| enum ENUM_ICT_TIMEBASE { ICT_TB_NEWYORK = 0, ICT_TB_SERVER = 1, ICT_TB_GMT = 2 };
|
| enum ENUM_ICT_SRVOFF { ICT_SRV_AUTO = 0, ICT_SRV_MANUAL = 1 };
|
| enum ENUM_ICT_SESSION { ICT_ASIA = 0, ICT_LONDON = 1, ICT_NEWYORK = 2 };
|
|
|
| //--- inputs
|
| input group "Sessions"
|
| input bool InpShowAsia = true; // Draw Asia macros
|
| input bool InpShowLondon = true; // Draw London macros
|
| input bool InpShowNewYork = true; // Draw New York macros
|
|
|
| input group "Macro windows (New York local time, format HH:MM-HH:MM;...)"
|
| input string InpAsiaMacros = ""; // Asia windows (empty = none)
|
| input string InpLondonMacros = "02:33-03:00;04:03-04:30";
|
| input string InpNYMacros = "08:50-09:10;09:50-10:10;10:50-11:10;11:50-12:10;13:10-13:40;15:15-15:45";
|
|
|
| input group "Time base"
|
| input ENUM_ICT_TIMEBASE InpTimeBase = ICT_TB_NEWYORK; // Reference zone of the windows above
|
| input ENUM_ICT_SRVOFF InpServerOffsetMode = ICT_SRV_AUTO; // Broker server offset detection
|
| input double InpManualServerGmtOffset = 0.0; // Server GMT offset in hours (manual mode)
|
|
|
| input group "Display"
|
| input bool InpDrawBox = true; // Draw the macro box
|
| input bool InpFillBox = false; // Fill the box (opaque, drawn behind the bars)
|
| input bool InpMidline = false; // Draw the 50% midline of the window
|
| input bool InpVerticalLines = true; // Draw vertical lines at window start/end
|
| input bool InpShowLabels = true; // Draw the window label
|
| input int InpLabelFontSize = 8; // Label font size
|
| input color InpAsiaColor = clrKhaki;
|
| input color InpLondonColor = clrSteelBlue;
|
| input color InpNewYorkColor = clrTomato;
|
| input bool InpLiveBox = true; // Refresh the window that is currently open
|
|
|
| input group "History / limits"
|
| input int InpHistoryDays = 20; // Past days to draw (0 = current day only)
|
| input int InpMaxObjects = 2000; // Safety cap of objects per rebuild
|
| input bool InpCleanStaleObjects = true; // Remove leftover ICTMacros_ objects of former runs
|
|
|
| input group "Diagnostics"
|
| input bool InpVerboseLog = true; // Print time base / offset / object diagnostics
|
|
|
| //--- one macro window (times are minutes from midnight in the reference zone)
|
| struct MacroWindow
|
| {
|
| int session; // ENUM_ICT_SESSION
|
| int index; // 1-based index inside the session
|
| int startMin; // start, minutes from midnight
|
| int endMin; // end, minutes from midnight (may exceed 1440 when crossing midnight)
|
| string label; // display label, e.g. "London 02:33-03:00"
|
| };
|
|
|
| //--- state
|
| MacroWindow g_win[]; // parsed windows of all enabled session lists
|
| int g_winCount = 0;
|
| string g_names[]; // objects of the current rebuild
|
| int g_nameCount = 0;
|
| string g_prevNames[]; // objects of the previous rebuild (stale-object diff)
|
| int g_prevCount = 0;
|
| datetime g_lastBarTime = 0;
|
| int g_srvOffsetSec = 0; // broker server UTC offset in seconds
|
| double g_liveHi = 0.0;
|
| double g_liveLo = 0.0;
|
| bool g_loggedSummary = false;
|
| bool g_capWarned = false;
|
| bool g_orphanSwept = false;
|
|
|
| //+------------------------------------------------------------------+
|
| //| Small text helpers |
|
| //+------------------------------------------------------------------+
|
| string Trim(const string src)
|
| {
|
| string s = src;
|
| StringTrimLeft(s);
|
| StringTrimRight(s);
|
| return s;
|
| }
|
|
|
| bool AllDigits(const string s)
|
| {
|
| const int n = StringLen(s);
|
| if(n == 0)
|
| return false;
|
| for(int i = 0; i < n; i++)
|
| {
|
| const ushort c = StringGetCharacter(s, i);
|
| if(c < 48 || c > 57) // '0' .. '9'
|
| return false;
|
| }
|
| return true;
|
| }
|
|
|
| //| Parse "HH:MM" into minutes from midnight. |
|
| bool ParseHHMM(const string src, int &minutes)
|
| {
|
| const string t = Trim(src);
|
| string hm[];
|
| if(StringSplit(t, ':', hm) != 2)
|
| return false;
|
| const string hs = Trim(hm[0]);
|
| const string ms = Trim(hm[1]);
|
| if(!AllDigits(hs) || !AllDigits(ms))
|
| return false;
|
| const int h = (int)StringToInteger(hs);
|
| const int m = (int)StringToInteger(ms);
|
| if(h < 0 || h > 23 || m < 0 || m > 59)
|
| return false;
|
| minutes = h * 60 + m;
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Session helpers |
|
| //+------------------------------------------------------------------+
|
| string SessionTag(const int session)
|
| {
|
| switch(session)
|
| {
|
| case ICT_ASIA: return "Asia";
|
| case ICT_LONDON: return "London";
|
| case ICT_NEWYORK: return "NY";
|
| }
|
| return "Sess";
|
| }
|
|
|
| string SessionLabel(const int session)
|
| {
|
| switch(session)
|
| {
|
| case ICT_ASIA: return "Asia";
|
| case ICT_LONDON: return "London";
|
| case ICT_NEWYORK: return "New York";
|
| }
|
| return "Session";
|
| }
|
|
|
| color SessionColor(const int session)
|
| {
|
| switch(session)
|
| {
|
| case ICT_ASIA: return InpAsiaColor;
|
| case ICT_LONDON: return InpLondonColor;
|
| case ICT_NEWYORK: return InpNewYorkColor;
|
| }
|
| return clrGray;
|
| }
|
|
|
| bool SessionEnabled(const int session)
|
| {
|
| switch(session)
|
| {
|
| case ICT_ASIA: return InpShowAsia;
|
| case ICT_LONDON: return InpShowLondon;
|
| case ICT_NEWYORK: return InpShowNewYork;
|
| }
|
| return false;
|
| }
|
|
|
| string TimeBaseName(const ENUM_ICT_TIMEBASE tb)
|
| {
|
| switch(tb)
|
| {
|
| case ICT_TB_NEWYORK: return "NewYork";
|
| case ICT_TB_SERVER: return "Server";
|
| case ICT_TB_GMT: return "GMT";
|
| }
|
| return "Unknown";
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Parse one session window list, appending to dst. |
|
| //| Returns the number of windows added. Invalid tokens are skipped |
|
| //| with a diagnostic instead of aborting the whole list. |
|
| //+------------------------------------------------------------------+
|
| int ParseWindowList(const string src, const int session, MacroWindow &dst[])
|
| {
|
| if(StringLen(Trim(src)) == 0)
|
| return 0;
|
|
|
| int added = 0;
|
| string parts[];
|
| const int count = StringSplit(src, ';', parts);
|
| for(int i = 0; i < count; i++)
|
| {
|
| const string token = Trim(parts[i]);
|
| if(token == "")
|
| continue;
|
|
|
| string range[];
|
| if(StringSplit(token, '-', range) != 2)
|
| {
|
| PrintFormat("ICTMacros: skipped window '%s' - expected HH:MM-HH:MM", token);
|
| continue;
|
| }
|
|
|
| int startMin = 0, endMin = 0;
|
| if(!ParseHHMM(range[0], startMin) || !ParseHHMM(range[1], endMin))
|
| {
|
| PrintFormat("ICTMacros: skipped window '%s' - invalid time", token);
|
| continue;
|
| }
|
| if(endMin == startMin)
|
| {
|
| PrintFormat("ICTMacros: skipped window '%s' - zero length", token);
|
| continue;
|
| }
|
|
|
| const int n = ArraySize(dst);
|
| if(ArrayResize(dst, n + 1) != n + 1)
|
| {
|
| Print("ICTMacros: out of memory while parsing macro windows");
|
| break;
|
| }
|
| dst[n].session = session;
|
| dst[n].index = added + 1;
|
| dst[n].startMin = startMin;
|
| dst[n].endMin = (endMin < startMin) ? endMin + ICT_MIN_PER_DAY : endMin;
|
| dst[n].label = StringFormat("%s %02d:%02d-%02d:%02d",
|
| SessionLabel(session),
|
| startMin / 60, startMin % 60,
|
| endMin / 60, endMin % 60);
|
| added++;
|
| }
|
| return added;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| America/New_York DST rules |
|
| //| DST starts 02:00 local (07:00 UTC) on the 2nd Sunday of March, |
|
| //| DST ends 02:00 local (06:00 UTC) on the 1st Sunday of November|
|
| //+------------------------------------------------------------------+
|
| datetime NthSundayUtc(const int year, const int month, const int nth, const int hourUtc)
|
| {
|
| MqlDateTime d;
|
| d.year = year; d.mon = month; d.day = 1; d.hour = hourUtc;
|
| d.min = 0; d.sec = 0; d.day_of_week = 0; d.day_of_year = 0;
|
| const datetime first = StructToTime(d);
|
|
|
| MqlDateTime f;
|
| TimeToStruct(first, f);
|
| const int dow = f.day_of_week; // 0 = Sunday
|
| const int offsetDays = ((7 - dow) % 7) + (nth - 1) * 7;
|
| return (datetime)((long)first + (long)offsetDays * ICT_SEC_PER_DAY);
|
| }
|
|
|
| bool IsUsDst(const datetime utc)
|
| {
|
| MqlDateTime d;
|
| TimeToStruct(utc, d);
|
| const datetime dstStart = NthSundayUtc(d.year, 3, 2, 7);
|
| const datetime dstEnd = NthSundayUtc(d.year, 11, 1, 6);
|
| return (utc >= dstStart && utc < dstEnd);
|
| }
|
|
|
| //| NY local = UTC + offset (EDT -4h, EST -5h) |
|
| int NyUtcOffsetSec(const datetime utc)
|
| {
|
| return IsUsDst(utc) ? -4 * 3600 : -5 * 3600;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Time conversion helpers |
|
| //+------------------------------------------------------------------+
|
| datetime DayStart(const datetime t)
|
| {
|
| return (datetime)((long)t - (long)((long)t % ICT_SEC_PER_DAY));
|
| }
|
|
|
| datetime MakeNaiveDay(const int year, const int month, const int day)
|
| {
|
| MqlDateTime d;
|
| d.year = year; d.mon = month; d.day = day;
|
| d.hour = 0; d.min = 0; d.sec = 0; d.day_of_week = 0; d.day_of_year = 0;
|
| return StructToTime(d);
|
| }
|
|
|
| //| Convert a window boundary (minutes from midnight of refDay in the |
|
| //| reference zone) into broker server time. |
|
| datetime WindowTimeToServer(const datetime refDay, int minutesOfDay)
|
| {
|
| long extraDays = 0;
|
| while(minutesOfDay >= ICT_MIN_PER_DAY)
|
| {
|
| minutesOfDay -= ICT_MIN_PER_DAY;
|
| extraDays++;
|
| }
|
| const datetime naive = (datetime)((long)refDay + extraDays * ICT_SEC_PER_DAY + (long)minutesOfDay * 60);
|
|
|
| if(InpTimeBase == ICT_TB_SERVER)
|
| return naive; // the list is already in server time
|
|
|
| datetime utc = naive; // for GMT the naive stamp is UTC
|
| if(InpTimeBase == ICT_TB_NEWYORK)
|
| {
|
| const int off1 = NyUtcOffsetSec(naive); // first guess from the naive stamp
|
| utc = (datetime)((long)naive - (long)off1);
|
| const int off2 = NyUtcOffsetSec(utc); // second pass, correct across the DST switch
|
| if(off2 != off1)
|
| utc = (datetime)((long)naive - (long)off2);
|
| }
|
| return (datetime)((long)utc + (long)g_srvOffsetSec);
|
| }
|
|
|
| //| "Now" expressed in the wall clock of the configured time base. |
|
| datetime ReferenceNow()
|
| {
|
| const datetime srvNow = TimeTradeServer();
|
| if(InpTimeBase == ICT_TB_SERVER)
|
| return srvNow;
|
|
|
| const datetime utcNow = (datetime)((long)srvNow - (long)g_srvOffsetSec);
|
| if(InpTimeBase == ICT_TB_GMT)
|
| return utcNow;
|
|
|
| return (datetime)((long)utcNow + (long)NyUtcOffsetSec(utcNow));
|
| }
|
|
|
| //| Broker server UTC offset in seconds, snapped to 5 minutes. |
|
| int DetectServerOffsetSec()
|
| {
|
| if(InpTimeBase == ICT_TB_SERVER)
|
| return 0;
|
| if(InpServerOffsetMode == ICT_SRV_MANUAL)
|
| return (int)MathRound(InpManualServerGmtOffset * 3600.0 / 300.0) * 300;
|
|
|
| const double diff = (double)((long)TimeTradeServer() - (long)TimeGMT());
|
| return (int)MathRound(diff / 300.0) * 300;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| First index whose time is >= value (arrays arrive oldest-first). |
|
| //+------------------------------------------------------------------+
|
| int LowerBound(const datetime &time[], const int total, const datetime value)
|
| {
|
| int lo = 0, hi = total;
|
| while(lo < hi)
|
| {
|
| const int mid = lo + (hi - lo) / 2;
|
| if(time[mid] < value)
|
| lo = mid + 1;
|
| else
|
| hi = mid;
|
| }
|
| return lo;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Chart object helpers (own namespace only) |
|
| //+------------------------------------------------------------------+
|
| void SetCommonProps(const string name, const color clr, const int width, const bool back)
|
| {
|
| ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
| ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
|
| ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
|
| ObjectSetInteger(0, name, OBJPROP_BACK, back);
|
| ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
| ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
|
| ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
|
| }
|
|
|
| bool EnsureRect(const string name, const datetime t1, const double p1,
|
| const datetime t2, const double p2, const color clr)
|
| {
|
| if(ObjectFind(0, name) < 0)
|
| {
|
| if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2))
|
| {
|
| PrintFormat("ICTMacros: ObjectCreate failed for %s (error %d)", name, GetLastError());
|
| return false;
|
| }
|
| }
|
| else
|
| {
|
| ObjectMove(0, name, 0, t1, p1);
|
| ObjectMove(0, name, 1, t2, p2);
|
| }
|
| SetCommonProps(name, clr, 1, true);
|
| ObjectSetInteger(0, name, OBJPROP_FILL, InpFillBox);
|
| return true;
|
| }
|
|
|
| bool EnsureTrend(const string name, const datetime t1, const double p1,
|
| const datetime t2, const double p2, const color clr)
|
| {
|
| if(ObjectFind(0, name) < 0)
|
| {
|
| if(!ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2))
|
| {
|
| PrintFormat("ICTMacros: ObjectCreate failed for %s (error %d)", name, GetLastError());
|
| return false;
|
| }
|
| }
|
| else
|
| {
|
| ObjectMove(0, name, 0, t1, p1);
|
| ObjectMove(0, name, 1, t2, p2);
|
| }
|
| SetCommonProps(name, clr, 1, true);
|
| ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
|
| ObjectSetInteger(0, name, OBJPROP_RAY_LEFT, false);
|
| ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
|
| return true;
|
| }
|
|
|
| bool EnsureVLine(const string name, const datetime t, const color clr)
|
| {
|
| if(ObjectFind(0, name) < 0)
|
| {
|
| if(!ObjectCreate(0, name, OBJ_VLINE, 0, t, 0.0))
|
| {
|
| PrintFormat("ICTMacros: ObjectCreate failed for %s (error %d)", name, GetLastError());
|
| return false;
|
| }
|
| }
|
| else
|
| ObjectMove(0, name, 0, t, 0.0);
|
|
|
| SetCommonProps(name, clr, 1, true);
|
| ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
|
| return true;
|
| }
|
|
|
| bool EnsureText(const string name, const datetime t, const double price,
|
| const string text, const color clr, const int fontSize)
|
| {
|
| if(ObjectFind(0, name) < 0)
|
| {
|
| if(!ObjectCreate(0, name, OBJ_TEXT, 0, t, price))
|
| {
|
| PrintFormat("ICTMacros: ObjectCreate failed for %s (error %d)", name, GetLastError());
|
| return false;
|
| }
|
| }
|
| else
|
| ObjectMove(0, name, 0, t, price);
|
|
|
| SetCommonProps(name, clr, 1, false);
|
|
|
| int fs = fontSize;
|
| if(fs < 6) fs = 6;
|
| if(fs > 20) fs = 20;
|
|
|
| ObjectSetString(0, name, OBJPROP_TEXT, text);
|
| ObjectSetString(0, name, OBJPROP_TOOLTIP, text);
|
| ObjectSetString(0, name, OBJPROP_FONT, "Arial");
|
| ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fs);
|
| ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Object name registry (never touches objects of other programs) |
|
| //+------------------------------------------------------------------+
|
| void RegisterName(const string name)
|
| {
|
| const int size = ArraySize(g_names);
|
| if(g_nameCount >= size)
|
| {
|
| if(ArrayResize(g_names, size + 64) <= size)
|
| return; // registry full: keep drawing, skip tracking
|
| }
|
| g_names[g_nameCount] = name;
|
| g_nameCount++;
|
| }
|
|
|
| bool NameInList(const string name, string &list[], const int count)
|
| {
|
| for(int i = 0; i < count; i++)
|
| if(list[i] == name)
|
| return true;
|
| return false;
|
| }
|
|
|
| bool CapacityOk()
|
| {
|
| if(g_nameCount + 4 > InpMaxObjects)
|
| {
|
| if(!g_capWarned)
|
| {
|
| PrintFormat("ICTMacros: object cap InpMaxObjects=%d reached - older windows are skipped", InpMaxObjects);
|
| g_capWarned = true;
|
| }
|
| return false;
|
| }
|
| return true;
|
| }
|
|
|
| //| Remove ICTMacros_ objects left behind by a former run. Runs once per |
|
| //| initialization. Disable InpCleanStaleObjects when two copies of this |
|
| //| indicator share one chart. |
|
| void SweepOrphanObjects()
|
| {
|
| if(!InpCleanStaleObjects || g_orphanSwept)
|
| return;
|
| g_orphanSwept = true;
|
|
|
| const int total = ObjectsTotal(0, -1, -1);
|
| string doomed[];
|
| int doomedCount = 0;
|
|
|
| for(int i = 0; i < total; i++)
|
| {
|
| const string name = ObjectName(0, i, -1, -1);
|
| if(StringFind(name, ICT_PREFIX) != 0) // only this program's namespace
|
| continue;
|
| if(NameInList(name, g_names, g_nameCount))
|
| continue;
|
|
|
| const int size = ArraySize(doomed);
|
| if(doomedCount >= size)
|
| if(ArrayResize(doomed, size + 32) <= size)
|
| break;
|
| doomed[doomedCount] = name;
|
| doomedCount++;
|
| }
|
|
|
| for(int i = 0; i < doomedCount; i++)
|
| ObjectDelete(0, doomed[i]);
|
|
|
| if(doomedCount > 0)
|
| PrintFormat("ICTMacros: removed %d leftover object(s) from a previous run", doomedCount);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Drawing |
|
| //+------------------------------------------------------------------+
|
| string DateTag(const datetime refDay)
|
| {
|
| MqlDateTime d;
|
| TimeToStruct(refDay, d);
|
| return StringFormat("%04d%02d%02d", d.year, d.mon, d.day);
|
| }
|
|
|
| void DrawWindow(const int wi, const datetime refDay, const datetime tStart, const datetime tEnd,
|
| const double hi, const double lo)
|
| {
|
| const int session = g_win[wi].session;
|
| const color clr = SessionColor(session);
|
| const string base = StringFormat("%s%s_%02d_%s",
|
| ICT_PREFIX, SessionTag(session), g_win[wi].index, DateTag(refDay));
|
|
|
| if(InpDrawBox || InpFillBox)
|
| if(EnsureRect(base, tStart, lo, tEnd, hi, clr))
|
| RegisterName(base);
|
|
|
| if(InpMidline)
|
| {
|
| const string midName = base + "_mid";
|
| const double mid = (hi + lo) / 2.0;
|
| if(EnsureTrend(midName, tStart, mid, tEnd, mid, clr))
|
| RegisterName(midName);
|
| }
|
|
|
| if(InpVerticalLines)
|
| {
|
| const string startName = base + "_S";
|
| const string endName = base + "_E";
|
| if(EnsureVLine(startName, tStart, clr))
|
| RegisterName(startName);
|
| if(EnsureVLine(endName, tEnd, clr))
|
| RegisterName(endName);
|
| }
|
|
|
| if(InpShowLabels)
|
| {
|
| const string labelName = base + "_L";
|
| if(EnsureText(labelName, tStart, hi, g_win[wi].label, clr, InpLabelFontSize))
|
| RegisterName(labelName);
|
| }
|
| }
|
|
|
| //| Full rebuild. Runs on init and on every new bar. |
|
| void RebuildAll(const datetime &time[], const double &high[], const double &low[], const int rates_total)
|
| {
|
| g_nameCount = 0;
|
| g_capWarned = false;
|
| g_liveHi = 0.0;
|
| g_liveLo = 0.0;
|
|
|
| int days = InpHistoryDays;
|
| if(days < 0) days = 0;
|
| if(days > 200) days = 200;
|
|
|
| const datetime refDay = DayStart(ReferenceNow());
|
| bool stop = false;
|
|
|
| for(int d = 0; d <= days && !stop; d++)
|
| {
|
| const datetime day = (datetime)((long)refDay - (long)d * ICT_SEC_PER_DAY);
|
|
|
| for(int w = 0; w < g_winCount && !stop; w++)
|
| {
|
| if(!SessionEnabled(g_win[w].session))
|
| continue;
|
| if(!CapacityOk())
|
| {
|
| stop = true;
|
| break;
|
| }
|
|
|
| const datetime t1 = WindowTimeToServer(day, g_win[w].startMin);
|
| const datetime t2 = WindowTimeToServer(day, g_win[w].endMin);
|
|
|
| const int i1 = LowerBound(time, rates_total, t1);
|
| if(i1 >= rates_total)
|
| continue; // window has not started yet
|
| const int i2 = LowerBound(time, rates_total, t2);
|
| const int last = i2 - 1; // last bar inside the window
|
| if(last < i1)
|
| continue; // no bars (weekend / holiday / gap)
|
|
|
| double hi = high[i1];
|
| double lo = low[i1];
|
| for(int i = i1 + 1; i <= last; i++)
|
| {
|
| if(high[i] > hi) hi = high[i];
|
| if(low[i] < lo) lo = low[i];
|
| }
|
|
|
| datetime tEnd = (datetime)((long)time[last] + PeriodSeconds());
|
| if(tEnd > t2)
|
| tEnd = t2;
|
|
|
| DrawWindow(w, day, time[i1], tEnd, hi, lo);
|
| }
|
| }
|
|
|
| //--- drop objects of the previous rebuild that are no longer valid
|
| for(int i = 0; i < g_prevCount; i++)
|
| if(!NameInList(g_prevNames[i], g_names, g_nameCount))
|
| ObjectDelete(0, g_prevNames[i]);
|
|
|
| //--- swap registries
|
| if(ArrayResize(g_prevNames, g_nameCount) != g_nameCount)
|
| Print("ICTMacros: could not resize the object registry");
|
| for(int i = 0; i < g_nameCount; i++)
|
| g_prevNames[i] = g_names[i];
|
| g_prevCount = g_nameCount;
|
|
|
| ChartRedraw();
|
| }
|
|
|
| //| Refresh only the window that is currently open (tick path). |
|
| void RefreshLiveWindow(const datetime &time[], const double &high[], const double &low[], const int rates_total)
|
| {
|
| if(!InpLiveBox || rates_total < 2)
|
| return;
|
|
|
| const datetime lastBar = time[rates_total - 1];
|
| const datetime refDay = DayStart(ReferenceNow());
|
|
|
| for(int w = 0; w < g_winCount; w++)
|
| {
|
| if(!SessionEnabled(g_win[w].session))
|
| continue;
|
|
|
| const datetime t1 = WindowTimeToServer(refDay, g_win[w].startMin);
|
| const datetime t2 = WindowTimeToServer(refDay, g_win[w].endMin);
|
| if(lastBar < t1 || lastBar >= t2)
|
| continue; // this window is not open right now
|
|
|
| const int i1 = LowerBound(time, rates_total, t1);
|
| if(i1 >= rates_total)
|
| return;
|
|
|
| double hi = high[i1];
|
| double lo = low[i1];
|
| for(int i = i1 + 1; i < rates_total; i++)
|
| {
|
| if(high[i] > hi) hi = high[i];
|
| if(low[i] < lo) lo = low[i];
|
| }
|
| if(hi == g_liveHi && lo == g_liveLo)
|
| return; // extremes unchanged: nothing to do
|
|
|
| const string base = StringFormat("%s%s_%02d_%s",
|
| ICT_PREFIX, SessionTag(g_win[w].session), g_win[w].index, DateTag(refDay));
|
| if(ObjectFind(0, base) < 0)
|
| return; // box of this window not built yet
|
|
|
| datetime tEnd = (datetime)((long)lastBar + PeriodSeconds());
|
| if(tEnd > t2)
|
| tEnd = t2;
|
|
|
| ObjectMove(0, base, 0, time[i1], lo);
|
| ObjectMove(0, base, 1, tEnd, hi);
|
|
|
| if(InpMidline)
|
| {
|
| const string midName = base + "_mid";
|
| if(ObjectFind(0, midName) >= 0)
|
| {
|
| const double mid = (hi + lo) / 2.0;
|
| ObjectMove(0, midName, 0, time[i1], mid);
|
| ObjectMove(0, midName, 1, tEnd, mid);
|
| }
|
| }
|
| if(InpShowLabels)
|
| {
|
| const string labelName = base + "_L";
|
| if(ObjectFind(0, labelName) >= 0)
|
| ObjectMove(0, labelName, 0, time[i1], hi);
|
| }
|
|
|
| g_liveHi = hi;
|
| g_liveLo = lo;
|
| ChartRedraw();
|
| return;
|
| }
|
| }
|
|
|
| void LogSummary()
|
| {
|
| if(!InpVerboseLog)
|
| return;
|
|
|
| int asia = 0, london = 0, ny = 0;
|
| for(int i = 0; i < g_winCount; i++)
|
| {
|
| if(g_win[i].session == ICT_ASIA) asia++;
|
| else if(g_win[i].session == ICT_LONDON) london++;
|
| else ny++;
|
| }
|
|
|
| PrintFormat("ICTMacros: base=%s server offset=%d min windows=%d (Asia %d, London %d, NY %d) history=%d d objects=%d",
|
| TimeBaseName(InpTimeBase), g_srvOffsetSec / 60, g_winCount,
|
| asia, london, ny, InpHistoryDays, g_nameCount);
|
|
|
| if(InpTimeBase == ICT_TB_NEWYORK)
|
| {
|
| const datetime utcNow = (datetime)((long)TimeTradeServer() - (long)g_srvOffsetSec);
|
| const datetime nyNow = (datetime)((long)utcNow + (long)NyUtcOffsetSec(utcNow));
|
| string zoneTag = "EST";
|
| if(IsUsDst(utcNow))
|
| zoneTag = "EDT";
|
| PrintFormat("ICTMacros: New York local time now %s (%s)",
|
| TimeToString(nyNow, TIME_DATE | TIME_MINUTES), zoneTag);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Indicator events |
|
| //+------------------------------------------------------------------+
|
| int OnInit()
|
| {
|
| IndicatorSetString(INDICATOR_SHORTNAME, "ICT Macros");
|
|
|
| ArrayResize(g_win, 0);
|
| g_winCount = 0;
|
| g_winCount += ParseWindowList(InpAsiaMacros, ICT_ASIA, g_win);
|
| g_winCount += ParseWindowList(InpLondonMacros, ICT_LONDON, g_win);
|
| g_winCount += ParseWindowList(InpNYMacros, ICT_NEWYORK, g_win);
|
|
|
| g_srvOffsetSec = DetectServerOffsetSec();
|
| g_lastBarTime = 0;
|
| g_liveHi = 0.0;
|
| g_liveLo = 0.0;
|
| g_loggedSummary = false;
|
| g_capWarned = false;
|
| g_orphanSwept = false;
|
| g_nameCount = 0;
|
| g_prevCount = 0;
|
| ArrayResize(g_names, 0);
|
| ArrayResize(g_prevNames, 0);
|
|
|
| if(g_winCount == 0)
|
| Print("ICTMacros: no macro windows configured - nothing will be drawn");
|
| if(!InpShowAsia && !InpShowLondon && !InpShowNewYork)
|
| Print("ICTMacros: all sessions are disabled - nothing will be drawn");
|
| if(g_winCount > 0 && PeriodSeconds() > 1800)
|
| PrintFormat("ICTMacros: chart period is %d s, macro windows may cover 0 or 1 bar (M1-M15 recommended)",
|
| PeriodSeconds());
|
| if(InpTimeBase != ICT_TB_SERVER && InpServerOffsetMode == ICT_SRV_AUTO && InpVerboseLog)
|
| PrintFormat("ICTMacros: server/UTC offset detected as %d minutes", g_srvOffsetSec / 60);
|
|
|
| return INIT_SUCCEEDED;
|
| }
|
|
|
| void OnDeinit(const int reason)
|
| {
|
| for(int i = 0; i < g_nameCount; i++)
|
| ObjectDelete(0, g_names[i]);
|
| for(int i = 0; i < g_prevCount; i++)
|
| ObjectDelete(0, g_prevNames[i]);
|
|
|
| g_nameCount = 0;
|
| g_prevCount = 0;
|
| ArrayResize(g_names, 0);
|
| ArrayResize(g_prevNames, 0);
|
| ChartRedraw();
|
| }
|
|
|
| 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;
|
|
|
| const bool newBar = (time[rates_total - 1] != g_lastBarTime);
|
| const bool firstPass = (prev_calculated <= 0) || (prev_calculated > rates_total);
|
|
|
| if(firstPass || newBar)
|
| {
|
| //--- keep the server offset in sync with broker DST switches
|
| if(InpTimeBase != ICT_TB_SERVER && InpServerOffsetMode == ICT_SRV_AUTO)
|
| {
|
| const int detected = DetectServerOffsetSec();
|
| if(detected != g_srvOffsetSec)
|
| {
|
| if(InpVerboseLog)
|
| PrintFormat("ICTMacros: server/UTC offset changed from %d to %d minutes",
|
| g_srvOffsetSec / 60, detected / 60);
|
| g_srvOffsetSec = detected;
|
| }
|
| }
|
|
|
| g_lastBarTime = time[rates_total - 1];
|
| RebuildAll(time, high, low, rates_total);
|
|
|
| if(!g_loggedSummary)
|
| {
|
| g_loggedSummary = true;
|
| LogSummary();
|
| }
|
| SweepOrphanObjects();
|
| }
|
| else
|
| RefreshLiveWindow(time, high, low, rates_total);
|
|
|
| return rates_total;
|
| }
|
| //+------------------------------------------------------------------+
|