//+------------------------------------------------------------------+
//|                                              ICTKillZones_BT.mq5 |
//|            ICT Kill Zones: Asian, London, New York, London Close |
//|                         Copyright © 2026, Muhammad Muqeem Korejo |
//+------------------------------------------------------------------+
//
// Kill zones are the hours in which ICT (Inner Circle Trader) traders
// expect the moves of the day. They are defined in New York local time:
//   Asian kill zone         20:00 - 00:00
//   London kill zone        02:00 - 05:00
//   New York kill zone      07:00 - 10:00
//   London Close kill zone  10:00 - 12:00
// Every time can be changed (HH:MM-HH:MM, New York time).
// The chart shows the broker's server time, so every candle is converted
// to New York time, summer time (DST) included:
//   Auto  - the server's GMT offset is read from the terminal;
//           GMT+2 / GMT+3 servers are taken as New York + 7 h (the usual
//           forex server clock), other offsets as a fixed offset.
//           The Strategy Tester has no real clock: New York + 7 h there.
//   NY+7  - the server clock is always New York + 7 h
//   EU    - GMT+2 in winter, GMT+3 in European summer time
//   Fixed - a fixed GMT offset, for example GMT+0
// US summer time: second Sunday of March - first Sunday of November.
// Every kill zone gets a box from its high to its low.
// Optional: the high and low of each finished kill zone are extended
// until price trades through them (liquidity that is taken later), and
// the midnight open line (00:00 New York opening price).
// Kill zones are a few hours long, so they are drawn on M1 - H1 charts.
// Buffers for Expert Advisors (iCustom, not drawn). They hold the LAST
// FINISHED kill zone of each kind, so they never repaint:
//   0 / 1 = Asian high / low,     2 / 3 = London high / low,
//   4 / 5 = New York high / low,  6 / 7 = London Close high / low,
//   EMPTY_VALUE when there is none (and on charts above H1).
// Read-only: the indicator never trades.
//
#property copyright   "Copyright © 2026, Muhammad Muqeem Korejo"
#property link        "https://www.mql5.com/en/users/korejo123"
#property version     "1.00"
#property description "ICT Kill Zones with On/Off Button"
#property description "Asian, London, New York and London Close in New York time, converted"
#property description "from the broker's server time with summer time (auto, NY + 7, EU or fixed GMT offset)."
#property description "High / low boxes, highs and lows extended until price takes them, midnight open line."
#property description "Alerts and buffers for EAs. Read-only: never trades."
#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots   8
#property indicator_type1   DRAW_NONE
#property indicator_label1  "Asian high"
#property indicator_type2   DRAW_NONE
#property indicator_label2  "Asian low"
#property indicator_type3   DRAW_NONE
#property indicator_label3  "London high"
#property indicator_type4   DRAW_NONE
#property indicator_label4  "London low"
#property indicator_type5   DRAW_NONE
#property indicator_label5  "New York high"
#property indicator_type6   DRAW_NONE
#property indicator_label6  "New York low"
#property indicator_type7   DRAW_NONE
#property indicator_label7  "London Close high"
#property indicator_type8   DRAW_NONE
#property indicator_label8  "London Close low"

enum ENUM_KZ_CLOCK
  {
   CLOCK_AUTO  = 0, // Auto (read from the terminal)
   CLOCK_NY7   = 1, // New York + 7 h (GMT+2 / GMT+3, US dates)
   CLOCK_EU    = 2, // GMT+2 / GMT+3 with European dates
   CLOCK_FIXED = 3  // Fixed GMT offset (next input)
  };

input ENUM_KZ_CLOCK InpClock         = CLOCK_AUTO;      // Broker server clock
input int           InpGMTOffset     = 0;               // Fixed clock: server GMT offset (hours)
input int           InpDays          = 5;               // Trading days shown
input bool          InpAsia          = true;            // Asian kill zone
input string        InpAsiaTime      = "20:00-00:00";   // Asian time (New York)
input color         InpAsiaColor     = C'206,224,250';  // Asian colour
input bool          InpLondon        = true;            // London kill zone
input string        InpLondonTime    = "02:00-05:00";   // London time (New York)
input color         InpLondonColor   = C'253,226,190';  // London colour
input bool          InpNewYork       = true;            // New York kill zone
input string        InpNewYorkTime   = "07:00-10:00";   // New York time (New York)
input color         InpNewYorkColor  = C'204,236,204';  // New York colour
input bool          InpClose         = true;            // London Close kill zone
input string        InpCloseTime     = "10:00-12:00";   // London Close time (New York)
input color         InpCloseColor    = C'232,214,242';  // London Close colour
input bool          InpLevels        = true;            // Extend highs / lows until price takes them
input bool          InpMidnight      = true;            // Midnight open line (00:00 New York)
input color         InpMidnightColor = clrGray;         // Midnight open colour
input bool          InpLabels        = true;            // Kill zone names on the boxes
input bool          InpInfo          = true;            // Clock info in the chart corner
input bool          InpAlertStart    = false;           // Alert when a kill zone starts
input bool          InpAlertTaken    = false;           // Alert when price takes a kill zone high / low
input bool          InpPush          = false;           // Alerts also as push notification
input bool          InpEmail         = false;           // Alerts also by e-mail

//Forex-Station copy & paste code; Button code start 11
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                  // What window to put the button on
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "KZ: ON";           // Display label
input string             btn_Font              = "Arial";            // button font name
input int                btn_FontSize          = 9;                  // btn__font size
input color              btn_text_ON_color     = clrLime;            // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;             // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;         // background color of the button
input color              btn_border_color      = clrBlack;           // border color the button
input int                button_x              = 180;                // Horizontal location
input int                button_y              = 80;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "ICTKZ_BT";         // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

#define PREFIX "ICTKZ_"
#define KINDS  4
#define HOUR   3600
#define DAY    86400

struct Zone
  {
   int               kind;       // 0 Asia, 1 London, 2 New York, 3 London Close
   long              start;      // New York time in seconds (also the id)
   long              end;
   long              tday;       // trading day (17:00 - 17:00 New York)
   datetime          t1;         // first and last candle (server time)
   datetime          t2;
   double            hi;
   double            lo;
   datetime          hiTaken;    // candle that traded through the high after the zone (0 = not yet)
   datetime          loTaken;
  };

struct DayOpen
  {
   long              day;        // New York date (00:00) in seconds
   datetime          t1;         // first and last candle of that date
   datetime          t2;
   double            price;
  };

double   g_b0[], g_b1[], g_b2[], g_b3[], g_b4[], g_b5[], g_b6[], g_b7[];
Zone     g_z[];
int      g_zn = 0;
DayOpen  g_d[];
int      g_dn = 0;
long     g_days[];                       // trading days in the window, oldest first
int      g_tn = 0;
int      g_from[KINDS], g_to[KINDS];     // minutes after New York midnight (g_to > g_from)
bool     g_on[KINDS];
color    g_clr[KINDS];
string   g_name[KINDS] = {"Asia", "London", "New York", "London Close"};
int      g_clock    = CLOCK_NY7;
long     g_offset   = 0;                 // fixed clock: server time - GMT in seconds
int      g_last     = -1;                // last calculated candle (the forming one)
datetime g_lastTime = 0;                 // its time (another time there = the history has changed)
datetime g_lastBar  = 0;
bool     g_live     = false;             // false while the history is calculated (no alerts)
bool     g_dirty    = true;
long     g_nyNow    = 0;
string   g_drawn[];
//--- summer time dates of one year (GMT, cached)
long     g_yFrom = 0, g_yTo = 0, g_usOn = 0, g_usOff = 0, g_euOn = 0, g_euOff = 0;

//+------------------------------------------------------------------+
//| Button State Tracking Variables                                  |
//+------------------------------------------------------------------+
bool    IndicatorEnabled = true;
bool    IndicatorHidden  = false; // True when indicator is turned OFF via toggle button
string  ToggleButtonName;
string  g_gvHidden;
string  g_prefix;
string  IndicatorName, IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------+
//| Helper to resolve button text based on state                     |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled)
{
   string t = btn_text;
   if(isEnabled)
   {
      if(StringFind(t, "OFF") >= 0) StringReplace(t, "OFF", "ON");
      else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On");
      else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on");
   }
   else
   {
      if(StringFind(t, "ON") >= 0) StringReplace(t, "ON", "OFF");
      else if(StringFind(t, "On") >= 0) StringReplace(t, "On", "Off");
      else if(StringFind(t, "on") >= 0) StringReplace(t, "on", "off");
   }
   return t;
}

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0)
   {
      int y_pos = button_y;
      int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON);
      bool collision = true;
      while(collision)
      {
         collision = false;
         for(int i = 0; i < obj_total; i++)
         {
            string name = ObjectName(0, i, -1, OBJ_BUTTON);
            if(name == buttonId) continue;
            if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner) continue;
            if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x &&
               ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos)
            {
               y_pos += btn_Height + 4;
               collision = true;
               break;
            }
         }
      }

      ObjectCreate(0, buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0);

      ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);
   }

   UpdateToggleButton();
}

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0) return;

   IndicatorEnabled = !IndicatorHidden;

   string labelText  = GetButtonText(IndicatorEnabled);
   color  textColor  = IndicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, buttonId, OBJPROP_STATE,        IndicatorEnabled);
   ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Ensure all buttons on chart have higher ZORDER                   |
//+------------------------------------------------------------------+
void BringButtonsToFront()
{
   int total = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = 0; i < total; i++)
   {
      string name = ObjectName(0, i, -1, OBJ_BUTTON);
      if(name != "")
      {
         if(ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100)
            ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
      }
   }
}

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
{
   IndicatorHidden  = !IndicatorHidden;
   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   if(IndicatorHidden)
   {
      ObjectsDeleteAll(0, PREFIX);
      ArrayFree(g_drawn);
      ArrayInitialize(g_b0, EMPTY_VALUE);
      ArrayInitialize(g_b1, EMPTY_VALUE);
      ArrayInitialize(g_b2, EMPTY_VALUE);
      ArrayInitialize(g_b3, EMPTY_VALUE);
      ArrayInitialize(g_b4, EMPTY_VALUE);
      ArrayInitialize(g_b5, EMPTY_VALUE);
      ArrayInitialize(g_b6, EMPTY_VALUE);
      ArrayInitialize(g_b7, EMPTY_VALUE);
      g_dirty = false;
   }
   else
   {
      g_dirty = true;
      if(g_zn > 0)
      {
         datetime now = TimeCurrent();
         Draw(now);
      }
      else
      {
         g_last     = -1;
         g_lastTime = 0;
         g_lastBar  = 0;
         ChartSetSymbolPeriod(0, _Symbol, (ENUM_TIMEFRAMES)_Period);
      }
   }

   UpdateToggleButton();
   BringButtonsToFront();
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Inputs and buffers                                               |
//+------------------------------------------------------------------+
int OnInit()
  {
   string times[KINDS];
   times[0] = InpAsiaTime;
   times[1] = InpLondonTime;
   times[2] = InpNewYorkTime;
   times[3] = InpCloseTime;
   g_on[0]  = InpAsia;
   g_on[1]  = InpLondon;
   g_on[2]  = InpNewYork;
   g_on[3]  = InpClose;
   g_clr[0] = InpAsiaColor;
   g_clr[1] = InpLondonColor;
   g_clr[2] = InpNewYorkColor;
   g_clr[3] = InpCloseColor;
   for(int k = 0; k < KINDS; k++)
      if(!ParseRange(times[k], g_from[k], g_to[k]))
        {
         PrintFormat("ICT Kill Zones: \"%s\" is not a time range, use HH:MM-HH:MM in New York time, for example 02:00-05:00", times[k]);
         return INIT_PARAMETERS_INCORRECT;
        }
   if(InpDays < 1 || InpDays > 60 || InpGMTOffset < -12 || InpGMTOffset > 14)
     {
      Print("ICT Kill Zones: trading days 1 - 60, GMT offset -12 - 14");
      return INIT_PARAMETERS_INCORRECT;
     }
   g_clock  = (InpClock == CLOCK_AUTO ? CLOCK_NY7 : (int)InpClock);
   g_offset = (long)InpGMTOffset * HOUR;
   SetIndexBuffer(0, g_b0, INDICATOR_DATA);
   SetIndexBuffer(1, g_b1, INDICATOR_DATA);
   SetIndexBuffer(2, g_b2, INDICATOR_DATA);
   SetIndexBuffer(3, g_b3, INDICATOR_DATA);
   SetIndexBuffer(4, g_b4, INDICATOR_DATA);
   SetIndexBuffer(5, g_b5, INDICATOR_DATA);
   SetIndexBuffer(6, g_b6, INDICATOR_DATA);
   SetIndexBuffer(7, g_b7, INDICATOR_DATA);
   for(int b = 0; b < 2 * KINDS; b++)
      PlotIndexSetDouble(b, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetString(INDICATOR_SHORTNAME, "ICT Kill Zones");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   //--- Set up unique IDs and global variables for button state tracking
   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   IndicatorName      = "ICT Kill Zones";
   g_prefix           = "ICTKZ_BT_" + UniqueButtonID + "_";
   g_gvHidden         = g_prefix + IntegerToString(ChartID()) + "_Hidden";

   //--- Clean up any old duplicate or phantom buttons
   int total_objs = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = total_objs - 1; i >= 0; i--)
     {
      string bName = ObjectName(0, i, -1, OBJ_BUTTON);
      if(StringFind(bName, UniqueButtonID) >= 0 || StringFind(bName, "ICTKZ_BT") >= 0)
        {
         if(bName != buttonId)
            ObjectDelete(0, bName);
        }
     }

   //--- When parameters are changed by user in properties dialog, delete old global variable
   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange && g_gvHidden != "")
     {
      GlobalVariableDel(g_gvHidden);
     }

   //--- Restore toggle state across timeframe/symbol changes
   bool hasSavedState = false;
   bool savedHidden   = false;

   if(!isParamChange)
     {
      if(ObjectFind(0, buttonId) >= 0)
        {
         string btnTxt = ObjectGetString(0, buttonId, OBJPROP_TEXT);
         long   btnSt  = ObjectGetInteger(0, buttonId, OBJPROP_STATE);

         if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0)
            savedHidden = true;
         else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0)
            savedHidden = false;
         hasSavedState = true;
        }

      if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
        {
         savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
         hasSavedState = true;
        }
     }

   if(hasSavedState)
      IndicatorHidden = savedHidden;
   else
      IndicatorHidden = false;

   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   CreateToggleButton();
   BringButtonsToFront();

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Remove the drawings and button                                   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PREFIX);
   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
     {
      ObjectDelete(0, buttonId);
      ObjectDelete(0, ToggleButtonName);
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);
     }
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator chart event handler                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
  {
   if(id == CHARTEVENT_OBJECT_CLICK)
     {
      string altId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
      if(sparam == buttonId || sparam == ToggleButtonName || sparam == altId)
        {
         ToggleIndicatorVisibility();
         return;
        }
     }

   if(id >= CHARTEVENT_CUSTOM)
      return;

   if(CHARTEVENT_CHART_CHANGE == id)
     {
      BringButtonsToFront();
     }
  }

//+------------------------------------------------------------------+
//| The last InpDays trading days; the forming candle is calculated   |
//| again on every tick (its high can only grow, its low only fall)   |
//+------------------------------------------------------------------+
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(IndicatorHidden)
     {
      ObjectsDeleteAll(0, PREFIX);
      ArrayFree(g_drawn);
      return rates_total;
     }

   if(rates_total < 2)
      return 0;
   bool full   = (prev_calculated == 0 || g_last < 0 || g_last > rates_total - 1 || time[g_last] != g_lastTime);
   bool newBar = (time[rates_total - 1] != g_lastBar);
   //--- auto clock: measured again on every new candle, a change draws everything again
   if(InpClock == CLOCK_AUTO && (full || newBar))
     {
      int  clock;
      long offset;
      DetectClock(clock, offset);
      if(clock != g_clock || offset != g_offset)
         full = true;
      g_clock  = clock;
      g_offset = offset;
     }
   bool small = (PeriodSeconds() <= HOUR);
   if(full)
     {
      ArrayInitialize(g_b0, EMPTY_VALUE);
      ArrayInitialize(g_b1, EMPTY_VALUE);
      ArrayInitialize(g_b2, EMPTY_VALUE);
      ArrayInitialize(g_b3, EMPTY_VALUE);
      ArrayInitialize(g_b4, EMPTY_VALUE);
      ArrayInitialize(g_b5, EMPTY_VALUE);
      ArrayInitialize(g_b6, EMPTY_VALUE);
      ArrayInitialize(g_b7, EMPTY_VALUE);
      g_zn    = 0;
      g_dn    = 0;
      g_tn    = 0;
      g_last  = (small ? WindowStart(rates_total, time) : rates_total - 1);
      g_live  = false;
      g_dirty = true;
     }
   if(!small)
     {
      //--- kill zones are a few hours long: nothing is drawn above H1, the buffers stay empty
      for(int k = g_last + 1; k < rates_total; k++)
         for(int b = 0; b < 2 * KINDS; b++)
            SetBuf(b, k, EMPTY_VALUE);
      g_last     = rates_total - 1;
      g_lastTime = time[g_last];
      g_lastBar  = time[g_last];
      if(full)
        {
         Info("ICT Kill Zones: kill zones are drawn on M1 - H1 charts");
         ChartRedraw();
        }
      return rates_total;
     }
   bool alerts = (g_live && !full);
   for(int k = g_last; k < rates_total; k++)
      AddCandle(k, time[k], open[k], high[k], low[k], alerts);
   g_last     = rates_total - 1;
   g_lastTime = time[g_last];
   g_nyNow    = ToNY(time[g_last]);
   if(newBar)
     {
      g_lastBar = time[g_last];
      g_dirty   = true;
     }
   if(g_dirty)
     {
      Draw(time[g_last]);
      g_dirty = false;
     }
   g_live = true;
   return rates_total;
  }

//+------------------------------------------------------------------+
//| First candle of the last InpDays trading days                    |
//+------------------------------------------------------------------+
int WindowStart(const int rates_total, const datetime &time[])
  {
   int  start = rates_total - 1;
   long day   = TradingDay(ToNY(time[start]));
   int  days  = 1;
   for(int i = rates_total - 2; i >= 0; i--)
     {
      long d = TradingDay(ToNY(time[i]));
      if(d != day)
        {
         day = d;
         if(++days > InpDays)
            break;
        }
      start = i;
     }
   return start;
  }

//+------------------------------------------------------------------+
//| One candle: midnight open, kill zones, highs / lows taken, buffers|
//+------------------------------------------------------------------+
void AddCandle(const int k, const datetime t, const double o, const double h, const double l, const bool alert)
  {
   long ny  = ToNY(t);
   long day = ny - ny % DAY;
   AddTradingDay(TradingDay(ny));
   //--- midnight open: the first candle of the New York date (none on Sunday, the week opens at 17:00)
   if(g_dn > 0 && g_d[g_dn - 1].day == day)
     {
      if(t > g_d[g_dn - 1].t2)
        {
         g_d[g_dn - 1].t2 = t;
         g_dirty = true;
        }
     }
   else
      if(ny - day < HOUR)
        {
         if(g_dn >= ArraySize(g_d))
            ArrayResize(g_d, g_dn + 16, 16);
         g_d[g_dn].day   = day;
         g_d[g_dn].t1    = t;
         g_d[g_dn].t2    = t;
         g_d[g_dn].price = o;
         g_dn++;
         g_dirty = true;
        }
   //--- the kill zones this candle belongs to
   for(int kind = 0; kind < KINDS; kind++)
     {
      long st, en;
      if(!InZone(kind, ny, st, en))
         continue;
      int i = Newest(kind);
      if(i >= 0 && g_z[i].start == st)
        {
         if(h > g_z[i].hi)
           {
            g_z[i].hi = h;
            g_dirty   = true;
           }
         if(l < g_z[i].lo)
           {
            g_z[i].lo = l;
            g_dirty   = true;
           }
         if(t > g_z[i].t2)
           {
            g_z[i].t2 = t;
            g_dirty   = true;
           }
         continue;
        }
      if(g_zn >= ArraySize(g_z))
         ArrayResize(g_z, g_zn + 32, 32);
      g_z[g_zn].kind    = kind;
      g_z[g_zn].start   = st;
      g_z[g_zn].end     = en;
      g_z[g_zn].tday    = TradingDay(st);
      g_z[g_zn].t1      = t;
      g_z[g_zn].t2      = t;
      g_z[g_zn].hi      = h;
      g_z[g_zn].lo      = l;
      g_z[g_zn].hiTaken = 0;
      g_z[g_zn].loTaken = 0;
      g_zn++;
      g_dirty = true;
      if(alert && InpAlertStart && g_on[kind])
         Notify(g_name[kind] + " kill zone has started");
     }
   //--- highs / lows of finished kill zones taken by this candle
   if(InpLevels)
      for(int i = 0; i < g_zn; i++)
         if(g_z[i].end <= ny && g_on[g_z[i].kind])
           {
            if(g_z[i].hiTaken == 0 && h >= g_z[i].hi)
              {
               g_z[i].hiTaken = t;
               g_dirty        = true;
               if(alert && InpAlertTaken)
                  Notify(g_name[g_z[i].kind] + " high " + DoubleToString(g_z[i].hi, _Digits) + " taken");
              }
            if(g_z[i].loTaken == 0 && l <= g_z[i].lo)
              {
               g_z[i].loTaken = t;
               g_dirty        = true;
               if(alert && InpAlertTaken)
                  Notify(g_name[g_z[i].kind] + " low " + DoubleToString(g_z[i].lo, _Digits) + " taken");
              }
           }
   //--- buffers for Expert Advisors (last finished kill zone of each kind)
   for(int kind = 0; kind < KINDS; kind++)
     {
      double lastHi = EMPTY_VALUE, lastLo = EMPTY_VALUE;
      int    i      = NewestFinished(kind, ny);
      if(i >= 0)
        {
         lastHi = g_z[i].hi;
         lastLo = g_z[i].lo;
        }
      SetBuf(2 * kind,     k, lastHi);
      SetBuf(2 * kind + 1, k, lastLo);
     }
  }

void SetBuf(const int b, const int k, const double v)
  {
   switch(b)
     {
      case 0: g_b0[k] = v; break;
      case 1: g_b1[k] = v; break;
      case 2: g_b2[k] = v; break;
      case 3: g_b3[k] = v; break;
      case 4: g_b4[k] = v; break;
      case 5: g_b5[k] = v; break;
      case 6: g_b6[k] = v; break;
      case 7: g_b7[k] = v; break;
     }
  }

//+------------------------------------------------------------------+
//| Time is in the kill zone: start / end are the New York borders   |
//+------------------------------------------------------------------+
bool InZone(const int kind, const long ny, long &start, long &end)
  {
   long day   = ny - ny % DAY;
   long minNY = (ny - day) / 60;
   if(g_from[kind] < g_to[kind])
     {
      if(minNY < g_from[kind] || minNY >= g_to[kind])
         return false;
      start = day + (long)g_from[kind] * 60;
      end   = day + (long)g_to[kind] * 60;
      return true;
     }
   if(minNY >= g_from[kind])
     {
      start = day + (long)g_from[kind] * 60;
      end   = day + DAY + (long)g_to[kind] * 60;
      return true;
     }
   if(minNY < g_to[kind])
     {
      start = day - DAY + (long)g_from[kind] * 60;
      end   = day + (long)g_to[kind] * 60;
      return true;
     }
   return false;
  }

int Newest(const int kind)
  {
   for(int i = g_zn - 1; i >= 0; i--)
      if(g_z[i].kind == kind)
         return i;
   return -1;
  }

int NewestFinished(const int kind, const long ny)
  {
   for(int i = g_zn - 1; i >= 0; i--)
      if(g_z[i].kind == kind && g_z[i].end <= ny)
         return i;
   return -1;
  }

//+------------------------------------------------------------------+
//| A trading day runs from 17:00 to 17:00 New York                  |
//+------------------------------------------------------------------+
long TradingDay(const long ny)
  {
   long day   = ny - ny % DAY;
   long secNY = ny - day;
   return(secNY < 17 * HOUR ? day : day + DAY);
  }

void AddTradingDay(const long tday)
  {
   if(g_tn > 0 && g_days[g_tn - 1] == tday)
      return;
   if(g_tn >= ArraySize(g_days))
      ArrayResize(g_days, g_tn + 8, 8);
   g_days[g_tn++] = tday;
   PruneDays();
  }

void PruneDays()
  {
   if(g_tn <= InpDays)
      return;
   long oldest = g_days[g_tn - InpDays];
   int  w      = 0;
   for(int r = 0; r < g_zn; r++)
      if(g_z[r].tday >= oldest)
         g_z[w++] = g_z[r];
   g_zn = w;
   w = 0;
   for(int r = 0; r < g_dn; r++)
      if(g_d[r].day >= oldest)
         g_d[w++] = g_d[r];
   g_dn = w;
   w = 0;
   for(int r = g_tn - InpDays; r < g_tn; r++)
      g_days[w++] = g_days[r];
   g_tn = w;
  }

//+------------------------------------------------------------------+
//| Server time to New York time                                     |
//+------------------------------------------------------------------+
long ToNY(const datetime t)
  {
   long s = (long)t;
   if(g_clock == CLOCK_NY7)
      return s - 7 * HOUR;
   long gmt;
   if(g_clock == CLOCK_FIXED)
      gmt = s - g_offset;
   else
     {
      DST(s);
      gmt = s - (2 * HOUR + g_euOn);
     }
   DST(gmt);
   return gmt - (5 * HOUR - g_usOn);
  }

//+------------------------------------------------------------------+
//| Summer time (DST) in one year: caches the borders                |
//+------------------------------------------------------------------+
void DST(const long t)
  {
   if(t >= g_yFrom && t < g_yTo)
      return;
   MqlDateTime dt;
   TimeToStruct((datetime)t, dt);
   dt.mon = 1; dt.day = 1; dt.hour = 0; dt.min = 0; dt.sec = 0;
   g_yFrom = (long)StructToTime(dt);
   dt.year++;
   g_yTo   = (long)StructToTime(dt);
   int y   = dt.year - 1;
   //--- US: second Sunday of March 02:00 New York (07:00 GMT), first Sunday of November 02:00 (06:00 GMT)
   g_usOn  = (long)StructToTime(Sunday(y, 3, 2, 7));
   g_usOff = (long)StructToTime(Sunday(y, 11, 1, 6));
   //--- EU: last Sunday of March 01:00 GMT, last Sunday of October 01:00 GMT
   g_euOn  = (long)StructToTime(LastSunday(y, 3, 1));
   g_euOff = (long)StructToTime(LastSunday(y, 10, 1));
  }

MqlDateTime Sunday(const int y, const int m, const int n, const int h)
  {
   MqlDateTime dt;
   dt.year = y; dt.mon = m; dt.day = 1; dt.hour = h; dt.min = 0; dt.sec = 0;
   datetime t = StructToTime(dt);
   TimeToStruct(t, dt);
   int s = (7 - dt.day_of_week) % 7 + 1 + (n - 1) * 7;
   dt.day = s;
   return dt;
  }

MqlDateTime LastSunday(const int y, const int m, const int h)
  {
   MqlDateTime dt;
   dt.year = y; dt.mon = m + 1; dt.day = 1; dt.hour = h; dt.min = 0; dt.sec = 0;
   if(m == 12)
     {
      dt.year++;
      dt.mon = 1;
     }
   datetime t = StructToTime(dt) - DAY;
   TimeToStruct(t, dt);
   dt.day -= dt.day_of_week;
   return dt;
  }

//+------------------------------------------------------------------+
//| Auto clock: detects the server offset from TimeCurrent vs TimeGMT|
//+------------------------------------------------------------------+
void DetectClock(int &clock, long &offset)
  {
   datetime nowServer = TimeCurrent();
   datetime nowGMT    = TimeGMT();
   if(nowServer == 0 || nowGMT == 0)
     {
      clock  = CLOCK_NY7;
      offset = 0;
      return;
     }
   long sec = (long)nowServer - (long)nowGMT;
   int  h   = (int)MathRound((double)sec / HOUR);
   if(h == 2 || h == 3)
     {
      clock  = CLOCK_NY7;
      offset = 0;
     }
   else
     {
      clock  = CLOCK_FIXED;
      offset = (long)h * HOUR;
     }
  }

//+------------------------------------------------------------------+
//| Boxes, names, highs / lows until taken, midnight open lines      |
//+------------------------------------------------------------------+
void Draw(const datetime now)
  {
   string keep[];
   int    cnt = 0;
   for(int i = 0; i < g_zn; i++)
     {
      int kind = g_z[i].kind;
      if(!g_on[kind])
         continue;
      string id   = IntegerToString(kind) + "_" + IntegerToString(g_z[i].start);
      color  dark = Strong(g_clr[kind]);
      string hi   = DoubleToString(g_z[i].hi, _Digits), lo = DoubleToString(g_z[i].lo, _Digits);
      Box(PREFIX + "B" + id, g_z[i].t1, g_z[i].hi, g_z[i].t2, g_z[i].lo, g_clr[kind],
          g_name[kind] + " kill zone: high " + hi + ", low " + lo);
      Keep(keep, cnt, PREFIX + "B" + id);
      if(InpLabels)
        {
         Text(PREFIX + "T" + id, g_z[i].t1, g_z[i].hi, g_name[kind], dark);
         Keep(keep, cnt, PREFIX + "T" + id);
        }
      if(InpLevels && g_z[i].end <= g_nyNow)
        {
         Line(PREFIX + "H" + id, g_z[i].t2, g_z[i].hiTaken > 0 ? g_z[i].hiTaken : now, g_z[i].hi, dark, STYLE_DOT,
              g_name[kind] + " high " + hi + (g_z[i].hiTaken > 0 ? " (taken)" : " (not taken yet)"));
         Keep(keep, cnt, PREFIX + "H" + id);
         Line(PREFIX + "L" + id, g_z[i].t2, g_z[i].loTaken > 0 ? g_z[i].loTaken : now, g_z[i].lo, dark, STYLE_DOT,
              g_name[kind] + " low " + lo + (g_z[i].loTaken > 0 ? " (taken)" : " (not taken yet)"));
         Keep(keep, cnt, PREFIX + "L" + id);
        }
     }
   if(InpMidnight)
      for(int i = 0; i < g_dn; i++)
        {
         string id = IntegerToString(g_d[i].day);
         Line(PREFIX + "M" + id, g_d[i].t1, g_d[i].t2, g_d[i].price, InpMidnightColor, STYLE_DASH,
              "Midnight open (00:00 New York) " + DoubleToString(g_d[i].price, _Digits));
         Keep(keep, cnt, PREFIX + "M" + id);
        }
   for(int i = 0; i < ArraySize(g_drawn); i++)
     {
      bool still = false;
      for(int j = 0; j < cnt && !still; j++)
         still = (keep[j] == g_drawn[i]);
      if(!still)
         ObjectDelete(0, g_drawn[i]);
     }
   ArrayResize(g_drawn, cnt);
   for(int j = 0; j < cnt; j++)
      g_drawn[j] = keep[j];
   if(InpInfo)
      Info(ClockText());
   ChartRedraw();
  }

void Keep(string &list[], int &cnt, const string name)
  {
   if(cnt >= ArraySize(list))
      ArrayResize(list, cnt + 64);
   list[cnt++] = name;
  }

//+------------------------------------------------------------------+
//| A stronger shade of a light colour (names and lines)             |
//+------------------------------------------------------------------+
color Strong(const color c)
  {
   uint v = (uint)c;
   int  r = 255 - (255 - (int)(v & 0xFF)) * 3;
   int  g = 255 - (255 - (int)((v >> 8) & 0xFF)) * 3;
   int  b = 255 - (255 - (int)((v >> 16) & 0xFF)) * 3;
   r = (int)((r < 0 ? 0 : r) * 0.8);
   g = (int)((g < 0 ? 0 : g) * 0.8);
   b = (int)((b < 0 ? 0 : b) * 0.8);
   return (color)(r | (g << 8) | (b << 16));
  }

void Box(const string name, const datetime t1, const double p1, const datetime t2, const double p2, const color c, const string tip)
  {
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
      ObjectSetString(0, name, OBJPROP_TOOLTIP, tip);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectSetInteger(0, name, OBJPROP_TIME,  0, t1);
      ObjectSetDouble(0,  name, OBJPROP_PRICE, 0, p1);
      ObjectSetInteger(0, name, OBJPROP_TIME,  1, t2);
      ObjectSetDouble(0,  name, OBJPROP_PRICE, 1, p2);
     }
  }

void Line(const string name, const datetime t1, const datetime t2, const double price, const color c, const ENUM_LINE_STYLE style, const string tip)
  {
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_TREND, 0, t1, price, t2, price);
      ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
      ObjectSetString(0, name, OBJPROP_TOOLTIP, tip);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectSetInteger(0, name, OBJPROP_TIME,  0, t1);
      ObjectSetInteger(0, name, OBJPROP_TIME,  1, t2);
      ObjectSetDouble(0,  name, OBJPROP_PRICE, 0, price);
      ObjectSetDouble(0,  name, OBJPROP_PRICE, 1, price);
     }
  }

void Text(const string name, const datetime t, const double price, const string text, const color c)
  {
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_TEXT, 0, t, price);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 7);
      ObjectSetString(0, name, OBJPROP_FONT, "Segoe UI");
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
      ObjectSetString(0, name, OBJPROP_TEXT, " " + text);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectSetInteger(0, name, OBJPROP_TIME,  t);
      ObjectSetDouble(0,  name, OBJPROP_PRICE, price);
     }
  }

void Info(const string text)
  {
   string name = PREFIX + "I";
   if(!InpInfo)
     {
      ObjectDelete(0, name);
      return;
     }
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 10);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);
      ObjectSetString(0, name, OBJPROP_FONT, "Segoe UI");
      ObjectSetInteger(0, name, OBJPROP_COLOR, clrSilver);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
   ObjectSetString(0, name, OBJPROP_TEXT, text);
  }

string ClockText()
  {
   string s;
   switch(g_clock)
     {
      case CLOCK_NY7:   s = "clock: NY+7 (GMT+2/3)"; break;
      case CLOCK_EU:    s = "clock: EU (GMT+2/3)"; break;
      case CLOCK_FIXED: s = StringFormat("clock: GMT%+d", (int)(g_offset / HOUR)); break;
      default:          s = "clock: auto"; break;
     }
   if(InpClock == CLOCK_AUTO)
      s = "auto " + s;
   return s;
  }

void Notify(const string text)
  {
   string msg = "[" + _Symbol + "] " + text;
   Alert(msg);
   if(InpPush)
      SendNotification(msg);
   if(InpEmail)
      SendMail("ICT Kill Zones alert", msg);
  }

//+------------------------------------------------------------------+
//| Parses "02:00-05:00" into minutes after midnight (120, 300)      |
//+------------------------------------------------------------------+
bool ParseRange(const string s, int &from, int &to)
  {
   int dash = StringFind(s, "-");
   if(dash < 0)
      return false;
   int h1, m1, h2, m2;
   if(!ParseTime(StringSubstr(s, 0, dash), h1, m1))
      return false;
   if(!ParseTime(StringSubstr(s, dash + 1), h2, m2))
      return false;
   from = h1 * 60 + m1;
   to   = h2 * 60 + m2;
   return(from != to);
  }

bool ParseTime(const string s, int &h, int &m)
  {
   string t = s;
   StringTrimLeft(t);
   StringTrimRight(t);
   int colon = StringFind(t, ":");
   if(colon < 0)
      return false;
   h = (int)StringToInteger(StringSubstr(t, 0, colon));
   m = (int)StringToInteger(StringSubstr(t, colon + 1));
   if(h < 0 || h > 23 || m < 0 || m > 59)
      return false;
   return true;
  }
//+------------------------------------------------------------------+
