//+------------------------------------------------------------------+

//|                                           SessionTimeFilter.mqh  |

//|                                        Copyright 2026, Algosphere |

//|                                     https://algosphere-quant.com |

//+------------------------------------------------------------------+

#property copyright "Copyright 2026, Algosphere"

#property link      "https://algosphere-quant.com"

#property version   "1.00"

#property description "Session Time Filter Library"

#property description "Filter trades by trading sessions (London, NY, Tokyo, Sydney)"

#property library

//+------------------------------------------------------------------+

//| Session Definitions (Server Time)                                 |

//+------------------------------------------------------------------+

enum ENUM_SESSION

{

   SESSION_SYDNEY,      // Sydney Session

   SESSION_TOKYO,       // Tokyo Session

   SESSION_LONDON,      // London Session

   SESSION_NEWYORK,     // New York Session

   SESSION_LONDON_NY,   // London-NY Overlap

   SESSION_CUSTOM       // Custom Session

};

//+------------------------------------------------------------------+

//| Session Time Structure                                            |

//+------------------------------------------------------------------+

struct SSessionTime

{

   int startHour;

   int startMinute;

   int endHour;

   int endMinute;

   string name;

};

//+------------------------------------------------------------------+

//| CSessionFilter Class                                              |

//+------------------------------------------------------------------+

class CSessionFilter

{

private:

   SSessionTime  m_sessions[6];

   int           m_gmtOffset;

   bool          m_initialized;

   //--- Initialize default session times (GMT)

   void InitSessions()

   {

      // Sydney: 21:00 - 06:00 GMT

      m_sessions[SESSION_SYDNEY].startHour = 21;

      m_sessions[SESSION_SYDNEY].startMinute = 0;

      m_sessions[SESSION_SYDNEY].endHour = 6;

      m_sessions[SESSION_SYDNEY].endMinute = 0;

      m_sessions[SESSION_SYDNEY].name = "Sydney";

      // Tokyo: 00:00 - 09:00 GMT

      m_sessions[SESSION_TOKYO].startHour = 0;

      m_sessions[SESSION_TOKYO].startMinute = 0;

      m_sessions[SESSION_TOKYO].endHour = 9;

      m_sessions[SESSION_TOKYO].endMinute = 0;

      m_sessions[SESSION_TOKYO].name = "Tokyo";

      // London: 07:00 - 16:00 GMT

      m_sessions[SESSION_LONDON].startHour = 7;

      m_sessions[SESSION_LONDON].startMinute = 0;

      m_sessions[SESSION_LONDON].endHour = 16;

      m_sessions[SESSION_LONDON].endMinute = 0;

      m_sessions[SESSION_LONDON].name = "London";

      // New York: 12:00 - 21:00 GMT

      m_sessions[SESSION_NEWYORK].startHour = 12;

      m_sessions[SESSION_NEWYORK].startMinute = 0;

      m_sessions[SESSION_NEWYORK].endHour = 21;

      m_sessions[SESSION_NEWYORK].endMinute = 0;

      m_sessions[SESSION_NEWYORK].name = "New York";

      // London-NY Overlap: 12:00 - 16:00 GMT

      m_sessions[SESSION_LONDON_NY].startHour = 12;

      m_sessions[SESSION_LONDON_NY].startMinute = 0;

      m_sessions[SESSION_LONDON_NY].endHour = 16;

      m_sessions[SESSION_LONDON_NY].endMinute = 0;

      m_sessions[SESSION_LONDON_NY].name = "London-NY Overlap";

      // Custom: Default same as London

      m_sessions[SESSION_CUSTOM].startHour = 7;

      m_sessions[SESSION_CUSTOM].startMinute = 0;

      m_sessions[SESSION_CUSTOM].endHour = 16;

      m_sessions[SESSION_CUSTOM].endMinute = 0;

      m_sessions[SESSION_CUSTOM].name = "Custom";

   }

   //--- Convert time to minutes since midnight

   int TimeToMinutes(int hour, int minute)

   {

      return hour * 60 + minute;

   }

   //--- Adjust for GMT offset

   int AdjustForGMT(int minutes)

   {

      minutes += m_gmtOffset * 60;

      if(minutes < 0) minutes += 1440;

      if(minutes >= 1440) minutes -= 1440;

      return minutes;

   }

public:

   //--- Constructor

   CSessionFilter()

   {

      m_gmtOffset = 0;

      m_initialized = false;

      InitSessions();

   }

   //--- Initialize with GMT offset

   bool Init(int gmtOffset = 0)

   {

      m_gmtOffset = gmtOffset;

      m_initialized = true;

      return true;

   }

   //--- Set custom session times

   void SetCustomSession(int startHour, int startMin, int endHour, int endMin)

   {

      m_sessions[SESSION_CUSTOM].startHour = startHour;

      m_sessions[SESSION_CUSTOM].startMinute = startMin;

      m_sessions[SESSION_CUSTOM].endHour = endHour;

      m_sessions[SESSION_CUSTOM].endMinute = endMin;

   }

   //--- Check if current time is within session

   bool IsInSession(ENUM_SESSION session)

   {

      if(!m_initialized) Init();

      MqlDateTime dt;

      TimeToStruct(TimeCurrent(), dt);

      int currentMinutes = TimeToMinutes(dt.hour, dt.min);

      int startMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].startHour, 

                                                     m_sessions[session].startMinute));

      int endMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].endHour, 

                                                   m_sessions[session].endMinute));

      // Handle sessions that cross midnight

      if(startMinutes > endMinutes)

      {

         return (currentMinutes >= startMinutes || currentMinutes < endMinutes);

      }

      return (currentMinutes >= startMinutes && currentMinutes < endMinutes);

   }

   //--- Check if specific time is within session

   bool IsTimeInSession(datetime time, ENUM_SESSION session)

   {

      if(!m_initialized) Init();

      MqlDateTime dt;

      TimeToStruct(time, dt);

      int currentMinutes = TimeToMinutes(dt.hour, dt.min);

      int startMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].startHour, 

                                                     m_sessions[session].startMinute));

      int endMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].endHour, 

                                                   m_sessions[session].endMinute));

      if(startMinutes > endMinutes)

      {

         return (currentMinutes >= startMinutes || currentMinutes < endMinutes);

      }

      return (currentMinutes >= startMinutes && currentMinutes < endMinutes);

   }

   //--- Get current active session(s)

   string GetActiveSession()

   {

      string result = "";

      if(IsInSession(SESSION_SYDNEY))

         result += (result == "" ? "" : ", ") + m_sessions[SESSION_SYDNEY].name;

      if(IsInSession(SESSION_TOKYO))

         result += (result == "" ? "" : ", ") + m_sessions[SESSION_TOKYO].name;

      if(IsInSession(SESSION_LONDON))

         result += (result == "" ? "" : ", ") + m_sessions[SESSION_LONDON].name;

      if(IsInSession(SESSION_NEWYORK))

         result += (result == "" ? "" : ", ") + m_sessions[SESSION_NEWYORK].name;

      return (result == "" ? "No Major Session" : result);

   }

   //--- Get session name

   string GetSessionName(ENUM_SESSION session)

   {

      return m_sessions[session].name;

   }

   //--- Get time until session starts (in minutes)

   int MinutesUntilSession(ENUM_SESSION session)

   {

      if(IsInSession(session)) return 0;

      MqlDateTime dt;

      TimeToStruct(TimeCurrent(), dt);

      int currentMinutes = TimeToMinutes(dt.hour, dt.min);

      int startMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].startHour, 

                                                     m_sessions[session].startMinute));

      int diff = startMinutes - currentMinutes;

      if(diff < 0) diff += 1440;

      return diff;

   }

   //--- Get time remaining in session (in minutes)

   int MinutesRemainingInSession(ENUM_SESSION session)

   {

      if(!IsInSession(session)) return 0;

      MqlDateTime dt;

      TimeToStruct(TimeCurrent(), dt);

      int currentMinutes = TimeToMinutes(dt.hour, dt.min);

      int endMinutes = AdjustForGMT(TimeToMinutes(m_sessions[session].endHour, 

                                                   m_sessions[session].endMinute));

      int diff = endMinutes - currentMinutes;

      if(diff < 0) diff += 1440;

      return diff;

   }

   //--- Check if it's a weekday (Mon-Fri)

   bool IsWeekday()

   {

      MqlDateTime dt;

      TimeToStruct(TimeCurrent(), dt);

      return (dt.day_of_week >= 1 && dt.day_of_week <= 5);

   }

   //--- Check specific day of week

   bool IsDayOfWeek(int day)  // 0=Sunday, 1=Monday, etc.

   {

      MqlDateTime dt;

      TimeToStruct(TimeCurrent(), dt);

      return (dt.day_of_week == day);

   }

   //--- Filter by multiple sessions

   bool IsInAnySessions(bool sydney, bool tokyo, bool london, bool newyork)

   {

      if(sydney && IsInSession(SESSION_SYDNEY)) return true;

      if(tokyo && IsInSession(SESSION_TOKYO)) return true;

      if(london && IsInSession(SESSION_LONDON)) return true;

      if(newyork && IsInSession(SESSION_NEWYORK)) return true;

      return false;

   }

};

//+------------------------------------------------------------------+

//| Exported Functions for procedural usage                           |

//+------------------------------------------------------------------+

CSessionFilter g_sessionFilter;

//+------------------------------------------------------------------+

//| Initialize session filter                                         |

//+------------------------------------------------------------------+

bool SessionFilterInit(int gmtOffset = 0) export

{

   return g_sessionFilter.Init(gmtOffset);

}

//+------------------------------------------------------------------+

//| Check if in session                                               |

//+------------------------------------------------------------------+

bool IsInTradingSession(ENUM_SESSION session) export

{

   return g_sessionFilter.IsInSession(session);

}

//+------------------------------------------------------------------+

//| Get active sessions string                                        |

//+------------------------------------------------------------------+

string GetActiveTradingSessions() export

{

   return g_sessionFilter.GetActiveSession();

}

//+------------------------------------------------------------------+

//| Check if London-NY overlap                                        |

//+------------------------------------------------------------------+

bool IsLondonNYOverlap() export

{

   return g_sessionFilter.IsInSession(SESSION_LONDON_NY);

}

//+------------------------------------------------------------------+

//| Check if Asian session (Sydney + Tokyo)                           |

//+------------------------------------------------------------------+

bool IsAsianSession() export

{

   return g_sessionFilter.IsInSession(SESSION_SYDNEY) || 

          g_sessionFilter.IsInSession(SESSION_TOKYO);

}

//+------------------------------------------------------------------+

//| Check if European session                                         |

//+------------------------------------------------------------------+

bool IsEuropeanSession() export

{

   return g_sessionFilter.IsInSession(SESSION_LONDON);

}

//+------------------------------------------------------------------+

//| Check if American session                                         |

//+------------------------------------------------------------------+

bool IsAmericanSession() export

{

   return g_sessionFilter.IsInSession(SESSION_NEWYORK);

}

//+------------------------------------------------------------------+